import getMachine from '.'; import { ID, IStoreState } from '../../index.data'; import { useEventBus } from 'valor-hooks'; import { EDITOR_MACHINE_EVENT_BUS } from './constants'; import { getActivedCell } from '../../store/selectors'; function getSelectionByActivedCell(activedCell: ID) { return { selectionType: 'cell', selectedCellRange: [activedCell, activedCell], isActiving: true, }; } /** * 初始, 状态为 idle * * idle时, 点击一个单元格A1(mouse down), 进入selecting(type=cell) * idle时, 点击一行1(mouse down), 进入selecting(type=cell, 注意不存在type=row) * * selecting A1时, mouse move / mouse up将追加选择 * selecting A1时, 点击A2(mouse down), 则重新选择(清除之前选择) * selecting 多选时, 点击A1(mouse down), 也重新selecting * selecting A1时, 点击A1(mouse down), 则进入editing * selecting A1时, 按下enter按钮(key down), 则进入editing * selecting 多选时,按下enter按钮(key down), 忽略 * selecting A1时, 点击外部( 相当于未选单元格 ), 忽略 * selecting 单选时, 按下 上下左右键(key down), 可选其它单元格 * * editing A1时, 点击A1, 忽略 * editing A1时, 点击A2(mouse down), 则退回selecting模式(选择A2) * editing A1时, 点击行(mouse down), 则退回selecting模式 ( 选择整行 ) * editing A1时, 点击外部(mouse down), 则退回 selecting A1 模式(选择A1) * editing A1时, send('picking'), 则进入 picking子状态 * editing A1时, send('exitEditing'), 则返回selecting A1 模式(选择A1) * (在实际操作中, 可使用enter确认, esc取消) * * editing.picking A1时, mousedown时开始记录范围, mouseup时发送pickingCells( 比如eventBus.send([11,11])) * editing.picking A1时, 可send('exitPicking') 返回editing状态 */ const state = ({ columns: [{}, {}], rows: [ { id: 1, i: 1, cellIds: [11, 12] }, { id: 2, i: 2, cellIds: [21, 22] }, { id: 3, i: 3, cellIds: [31, 32] }, { id: 4, i: 4, cellIds: [41, 42] }, ], cells: { 11: { id: 11, i: 1, j: 0, rowId: 1 }, 12: { id: 12, i: 1, j: 1, rowId: 1 }, 21: { id: 21, i: 2, j: 0, rowId: 2 }, 22: { id: 22, i: 2, j: 1, rowId: 2 }, 31: { id: 31, i: 2, j: 0, rowId: 3, formula: '{id11}', formulaDisabled: false }, 32: { id: 32, i: 2, j: 1, rowId: 3 }, 41: { id: 41, i: 2, j: 0, rowId: 4 }, 42: { id: 42, i: 2, j: 1, rowId: 4 }, }, } as any) as IStoreState; describe('toggle state anyway(forced)', () => { // 确保任意状态下, 都能切换为选择模式 it('anyway -> 选择第1个单元格', () => { const machine = getMachine(); const machineState = machine.transition('selecting.row', { type: 'SELECT.CELL', selectedCell: 11, state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context).toEqual({ isActiving: false, usage: 'editor', selectionType: 'cell', selectedCellRange: [11, 11], }); }); it('anyway -> 选择第1个单元格', () => { const machine = getMachine(); // 因为 editing.picking.on 状态下, 没有SELECT.CELL触发器, 所以自动冒泡到顶部 const machineState = machine.transition('editing.picking.on', { type: 'SELECT.CELL', selectedCell: 11, state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context).toEqual({ isActiving: false, usage: 'editor', selectionType: 'cell', selectedCellRange: [11, 11], }); }); }); describe('machine as editor, selecting', () => { it('idle -> 点击第1行', () => { const machine = getMachine(); const machineState = machine.transition('idle', { type: 'MOUSE.DOWN', selectedRow: 1, state, }); expect(machineState.value).toEqual({ selecting: 'row' }); expect(machineState.context).toEqual({ isActiving: false, usage: 'editor', selectionType: 'row', selectedRowRange: [1, 1], }); }); it('idle -> 点击第21单元格', () => { const machine = getMachine(); const machineState = machine.transition('idle', { type: 'MOUSE.DOWN', selectedCell: 21, state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context).toEqual({ isActiving: false, usage: 'editor', selectionType: 'cell', selectedCellRange: [21, 21], }); }); it('selecting.row -> 继续选择行', () => { const machine = getMachine({ selectionType: 'row', selectedRowRange: [1, 1] }); const machineState = machine.transition('selecting.row', { type: 'MOUSE.MOVE', selectedRow: 2, state, }); expect(machineState.value).toEqual({ selecting: 'row' }); expect(machineState.context.selectedRowRange).toEqual([1, 2]); }); it('selecting.row -> 继续选单元格(推断行)', () => { const machine = getMachine({ selectionType: 'row', selectedRowRange: [1, 1] }); const machineState = machine.transition('selecting.row', { type: 'MOUSE.MOVE', selectedCell: 21, state, }); expect(machineState.value).toEqual({ selecting: 'row' }); expect(machineState.context.selectedRowRange).toEqual([1, 2]); }); it('selecting.cell -> 继续选单元格', () => { const machine = getMachine({ selectionType: 'cell', selectedCellRange: [11, 11] }); const machineState = machine.transition('selecting.cell', { type: 'MOUSE.MOVE', selectedCell: 21, state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context.selectedCellRange).toEqual([11, 21]); }); it('selecting.cell -> 继续选行(无效果)', () => { const machine = getMachine({ selectionType: 'cell', selectedCellRange: [11, 11] }); const machineState = machine.transition('selecting.cell', { type: 'MOUSE.MOVE', selectedRow: 2, state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context.selectedCellRange).toEqual([11, 11]); }); it('selecting.cell -> 重新选择', () => { const machine = getMachine({ selectionType: 'cell', selectedCellRange: [11, 11] }); const machineState = machine.transition('selecting.cell', { type: 'MOUSE.DOWN', selectedRow: 2, state, }); expect(machineState.value).toEqual({ selecting: 'row' }); expect(machineState.context.selectedRowRange).toEqual([2, 2]); }); it('selecting.row -> 重新选择', () => { const machine = getMachine({ selectionType: 'row', selectedRowRange: [1, 1] }); const machineState = machine.transition('selecting.row', { type: 'MOUSE.DOWN', selectedCell: 21, state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context.selectedCellRange).toEqual([21, 21]); }); }); /* 当前光标移动, 有可能是在actived/selected两种模式下, 甚至: 如果单元格初始无数据, 支持 上下左右 移动, 有数据则不支持 情况复杂, 最好的情况就是在 input 中直接解决,并发送SELECT.CELL事件 describe('machine as editor, 光标上下左右移动', () => { it('selecting.cell -> 单选单元格, 可上移光标', () => { const machine = getMachine({ selectionType: 'cell', selectedCellRange: [21, 21] }); const machineState = machine.transition('selecting.cell', { type: 'KEY.ARROW', key: 'ArrowUp', state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context.selectedCellRange).toEqual([11, 11]); }); }); */ describe('machine as editor, 进入 editing模式', () => { /* 不再需要, 仿照excel, 必须双击才能编辑, 也可直接输入文字 it('selecting.cell -> 通过再次点击相同单元格进入编辑', () => { const machine = getMachine({ selectionType: 'cell', selectedCellRange: [21, 21] }); const machineState = machine.transition('selecting.cell', { type: 'MOUSE.DOWN', selectedCell: 21, state, }); expect(machineState.value).toEqual({ editing: { default: {}, picking: 'off' } }); expect(machineState.context.selectionType).toEqual(null); expect(machineState.context.activedCell).toEqual(21); }); */ /* 不再需要, 仿照excel, enter是向下或向右 it('selecting.cell -> 通过enter按钮也可以进入编辑', () => { const machine = getMachine({ selectionType: 'cell', selectedCellRange: [21, 21] }); const machineState = machine.transition('selecting.cell', { type: 'KEY.ENTER', selectedCell: 21, state, }); expect(machineState.value).toEqual({ editing: { default: {}, picking: 'off' } }); expect(machineState.context.selectionType).toEqual(null); expect(machineState.context.activedCell).toEqual(21); }); */ /* 点击外部在input内处理 it('editing -> 点击sheet外部(不选择单元格和行)返回selecting', () => { const machine = getMachine({ activedCell: 21 }); const machineState = machine.transition('editing', { type: 'MOUSE.DOWN', state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context.selectionType).toEqual('cell'); expect(machineState.context.selectedCellRange).toEqual([21, 21]); expect(machineState.context.activedCell).toEqual(null); }); 点击内部单元格也在input内处理 it('editing -> 点击其它单元格 返回selecting 并重选', () => { const machine = getMachine({ activedCell: 21 }); const machineState = machine.transition('editing', { type: 'MOUSE.DOWN', selectedCell: 22, state, }); expect(machineState.value).toEqual({ selecting: 'cell' }); expect(machineState.context.selectionType).toEqual('cell'); expect(machineState.context.selectedCellRange).toEqual([22, 22]); expect(machineState.context.activedCell).toEqual(null); }); */ }); describe('machine as editor, 进入 editing.picking模式', () => { it('edit -> 进入picking模式', () => { const machine = getMachine(getSelectionByActivedCell(21)); const machineState = machine.transition('editing.default', { type: 'PICK.ENTER', state, }); expect(machineState.value).toEqual({ editing: { default: {}, picking: 'on' } }); expect(getActivedCell(machineState.context as any)).toEqual(21); }); it('edit -> 在picking模式下按下鼠标, 选单元格', () => { const machine = getMachine({ pickingCells: [30] }); const machineState = machine.transition('editing.picking.on', { type: 'MOUSE.DOWN', selectedCell: 22, state, }); expect((machineState.context.pickedCells = [22])); }); it('edit -> 在picking模式移动鼠标, 继续选择', () => { const machine = getMachine({ pickedCells: [22] }); const machineState = machine.transition('editing.picking.on', { type: 'MOUSE.MOVE', selectedCell: 23, state, }); expect((machineState.context.pickedCells = [22, 23])); }); it('edit -> 在picking模式鼠标抬起', () => { const { addEventListener } = useEventBus(EDITOR_MACHINE_EVENT_BUS); addEventListener('pickedCell', cells => expect(cells).toEqual([22, 24])); const machine = getMachine({ pickedCells: [22] }); const machineState = machine.transition('editing.picking.on', { type: 'MOUSE.UP', selectedCell: 24, state, }); expect((machineState.context.pickedCells = [])); }); it('edit -> 退出picking模式, 总是返回到editing', () => { const machine = getMachine({}); const machineState = machine.transition('editing.picking.on', { type: 'PICK.EXIT', state, }); expect(machineState.value).toEqual({ editing: { default: {}, picking: 'off' } }); }); });