import { recalculate, evalCellValueRecusively, cleanDeps } from './recalculate'; import { treeArray as ta } from 'valor-app-utils'; describe('evalCellValueRecusively', () => { it('case0: 单级依赖', () => { const state = { columns: [], rows: [{ id: 1, level: 1, cellIds: [11, 12, 13, 14] }], cells: { 11: { id: 11, value: 1 }, 12: { id: 12, value: 1 }, 13: { id: 13, value: 1, formula: '{{id11}}+{{id12}}' }, 14: { id: 14, value: 1, formula: '{{id15}}' }, // 不存在 }, }; const expected = { 13: { error: null, result: 2, deps: { cells: [11, 12], variables: [] } }, }; expect(evalCellValueRecusively(state as any, 13)).toEqual(expected); expect(evalCellValueRecusively(state as any, 14)).toEqual({ 14: { error: '#REF!', result: null, deps: { cells: [], variables: [] } }, }); }); it('case1: 多级依赖', () => { const state = { columns: [], rows: [{ id: 1, level: 1, cellIds: [11, 12, 13, 14] }], cells: { 11: { id: 11, value: 1 }, 12: { id: 12, value: 1 }, 13: { id: 13, value: 1, formula: '{{id11}}+{{id12}}' }, 14: { id: 14, value: 1, formula: '{{id13}}' }, }, }; const expected = { 13: { error: null, result: 2, deps: { cells: [11, 12], variables: [] } }, 14: { error: null, result: 2, deps: { cells: [13], variables: [] } }, }; expect(evalCellValueRecusively(state as any, 14)).toEqual(expected); }); it('case2: sumLeft', () => { const state = { columns: [], rows: [{ id: 1, level: 1, i: 0, cellIds: [11, 12, 13, 14] }], cells: { 11: { id: 11, value: 1, i: 0, j: 0, rowId: 1 }, 12: { id: 12, value: 1, i: 0, j: 1, rowId: 1 }, 13: { id: 13, value: 1, i: 0, j: 2, formula: 'sumLeft(2)', rowId: 1 }, 14: { id: 14, value: 1, i: 0, j: 3, formula: 'sumLeft(3)', rowId: 1 }, }, }; const expected = { 13: { error: null, result: 2, deps: { cells: [11, 12], variables: [] } }, }; expect(evalCellValueRecusively(state as any, 13)).toEqual(expected); const expected1 = { 13: { error: null, result: 2, deps: { cells: [11, 12], variables: [] } }, 14: { error: null, result: 4, deps: { cells: [11, 12, 13], variables: [] } }, }; expect(evalCellValueRecusively(state as any, 14)).toEqual(expected1); }); it('case3: sumChildren', () => { const _state = { columns: [], rows: [ { id: 1, level: 1, i: 0, cellIds: [11] }, { id: 2, level: 2, i: 1, cellIds: [21] }, { id: 3, level: 3, i: 2, cellIds: [31] }, ], cells: { 11: { id: 11, value: 1, i: 0, j: 0, rowId: 1, formula: 'sumChildren()' }, 21: { id: 21, value: 1, i: 1, j: 0, rowId: 2, formula: 'sumChildren()' }, 31: { id: 31, value: 2, i: 2, j: 0, rowId: 3 }, }, }; const state = { ..._state, treeContext: ta.getTreeContexts(_state.rows) }; const expected = { 21: { error: null, result: 2, deps: { cells: [31], variables: [] } }, }; expect(evalCellValueRecusively(state as any, 21)).toEqual(expected); const expected1 = { 21: { error: null, result: 2, deps: { cells: [31], variables: [] } }, 11: { error: null, result: 2, deps: { cells: [21], variables: [] } }, }; expect(evalCellValueRecusively(state as any, 11)).toEqual(expected1); }); it('case4: sumSiblings', () => { const _state = { columns: [], rows: [ { id: 1, level: 1, i: 0, type: 'body', cellIds: [11] }, { id: 2, level: 2, i: 1, type: 'body', cellIds: [21] }, { id: 3, level: 1, i: 2, type: 'body', cellIds: [31] }, { id: 4, level: 1, i: 3, type: 'body', cellIds: [41] }, ], cells: { 11: { id: 11, value: 1, i: 0, j: 0, rowId: 1 }, 21: { id: 21, value: 1, i: 1, j: 0, rowId: 2 }, 31: { id: 31, value: 2, i: 2, j: 0, rowId: 3 }, 41: { id: 41, value: 2, i: 3, j: 0, rowId: 4, formula: 'sumSiblings()' }, }, }; const state = { ..._state, treeContext: ta.getTreeContexts(_state.rows) }; const expected = { 41: { error: null, result: 3, deps: { cells: [11, 31], variables: [] } }, }; expect(evalCellValueRecusively(state as any, 41)).toEqual(expected); }); }); describe('recalculate', () => { it('case0: 单层, 单个依赖, 由parserResult转换为正常state格式', () => { const state = { columns: [], rows: [{ id: 1, level: 1, cellIds: [11, 12, 13, 14] }], cells: { 11: { id: 11, value: 1, rowId: 1 }, 12: { id: 12, value: 1, rowId: 1 }, 13: { id: 13, value: 1, formula: '{{id11}}+{{id12}}', rowId: 1 }, 14: { id: 14, value: 1, rowId: 1 }, }, }; const expected = { cells: { ...state.cells, 13: { ...state.cells[13], value: 2, error: null }, }, cellDeps: { // 注意是从{{id13}}中将解析出字符串, 而不是数字 // 程序统一使用字符串 11: [13], 12: [13], }, varDeps: {}, }; expect(recalculate(state as any)).toEqual(expected); }); it('case1: 单层, 多个', () => { const state = { columns: [], rows: [{ id: 1, level: 1, cellIds: [11, 12, 13, 14] }], cells: { 11: { id: 11, value: 1, rowId: 1 }, 12: { id: 12, value: 1, rowId: 1 }, 13: { id: 13, value: 1, formula: '{{id11}}+{{id12}}', rowId: 1 }, 14: { id: 14, value: 1, formula: '{{id13}}', rowId: 1 }, }, }; const expected = { cells: { ...state.cells, 13: { ...state.cells[13], value: 2, error: null }, 14: { ...state.cells[14], value: 2, error: null }, // 注意: 这里的value一定是1而不是2!!!, 原因是直接取的13.value(而非最新value) }, cellDeps: { // 注意是从{{id13}}中将解析出字符串, 而不是数字 // 程序统一使用字符串 11: [13], 12: [13], 13: [14], }, varDeps: {}, }; expect(recalculate(state as any)).toEqual(expected); }); it('case1: 变量/参数', () => { const state = { columns: [], rows: [{ id: 1, level: 1, cellIds: [11, 12, 13, 14] }], cells: { 11: { id: 11, value: 1, rowId: 1 }, 12: { id: 12, value: 1, formula: 'jj+jj', rowId: 1 }, 13: { id: 13, value: 1, formula: 'kk*{{id11}}+{{id12}}', rowId: 1 }, 14: { id: 14, value: 1, formula: 'kk*{{id13}} + jj', rowId: 1 }, }, variables: { kk: 1, jj: 0, }, }; const expected = { cells: { ...state.cells, 12: { ...state.cells[12], value: 0, error: null }, 13: { ...state.cells[13], value: 1, error: null }, 14: { ...state.cells[14], value: 1, error: null }, // 注意: 这里的value一定是1而不是2!!!, 原因是直接取的13.value(而非最新value) }, cellDeps: { // 注意是从{{id13}}中将解析出字符串, 而不是数字 // 程序统一使用字符串 11: [13], 12: [13], 13: [14], }, varDeps: { kk: [13, 14], jj: [12, 14], }, }; expect(recalculate(state as any)).toEqual(expected); }); const _state = { // 第1列: 建安费 = 单价*数量 // 第2列: 设备费 = 单价*数量 // 第3列: 总费用 = 建安费 + 设备费 // 第4列: 单价 = value columns: [], rows: [ // 工程费 { id: 0, i: 0, level: 1, cellIds: [11, 12, 13, 14] }, { id: 1, i: 1, level: 2, cellIds: [21, 22, 23, 24] }, { id: 2, i: 2, level: 3, cellIds: [31, 32, 33, 34] }, { id: 3, i: 3, level: 3, cellIds: [41, 42, 43, 44] }, { id: 4, i: 4, level: 2, cellIds: [51, 52, 53, 54] }, // 其它费 { id: 5, i: 5, level: 1, cellIds: [61, 62, 63, 64] }, { id: 6, i: 6, level: 2, cellIds: [71, 72, 73, 74] }, { id: 7, i: 7, level: 2, cellIds: [81, 82, 83, 84] }, // 预备费 { id: 8, i: 8, level: 1, cellIds: [91, 92, 93, 94] }, // 总投资 { id: 9, i: 9, level: 1, cellIds: [101, 102, 103, 104] }, ].map(it => ({ ...it, type: 'body' })), cells: { // 第1行 ( 列定义见上面 ) 11: { id: 11, value: 1, rowId: 0, i: 0, j: 0, formula: 'sumChildren()' }, 12: { id: 12, value: 1, rowId: 0, i: 0, j: 1, formula: 'sumChildren()' }, 13: { id: 13, value: 1, rowId: 0, i: 0, j: 2, formula: 'sumLeft(2)' }, 14: { id: 14, value: 1, rowId: 0, i: 0, j: 3 }, // 第2行 ( 列定义见上面 ) 21: { id: 21, value: 1, rowId: 1, i: 1, j: 0, formula: 'sumChildren()' }, 22: { id: 22, value: 1, rowId: 1, i: 1, j: 1, formula: 'sumChildren()' }, 23: { id: 23, value: 1, rowId: 1, i: 1, j: 2, formula: 'sumLeft(2)' }, 24: { id: 24, value: 1, rowId: 1, i: 1, j: 3 }, // 第3行 leaf ( 列定义见上面 ) 31: { id: 31, value: 1, rowId: 2, i: 2, j: 0 }, 32: { id: 32, value: 1, rowId: 2, i: 2, j: 1 }, 33: { id: 33, value: 1, rowId: 2, i: 2, j: 2, formula: 'sumLeft(2)' }, 34: { id: 34, value: 1, rowId: 2, i: 2, j: 3 }, // 第4行 leaf ( 列定义见上面 ) 41: { id: 41, value: 1, rowId: 3, i: 3, j: 0 }, 42: { id: 42, value: 1, rowId: 3, i: 3, j: 1 }, 43: { id: 43, value: 1, rowId: 3, i: 3, j: 2, formula: 'sumLeft(2)' }, 44: { id: 44, value: 1, rowId: 3, i: 3, j: 3 }, // 第5行 leaf ( 列定义见上面 ) 51: { id: 51, value: 1, rowId: 4, i: 4, j: 0 }, 52: { id: 52, value: 1, rowId: 4, i: 4, j: 1 }, 53: { id: 53, value: 1, rowId: 4, i: 4, j: 2, formula: 'sumLeft(2)' }, 54: { id: 54, value: 1, rowId: 4, i: 4, j: 3 }, // 第6行 ( 列定义见上面 ) 61: { id: 61, value: 1, rowId: 5, i: 5, j: 0, formula: 'sumChildren()' }, 62: { id: 62, value: 1, rowId: 5, i: 5, j: 1, formula: 'sumChildren()' }, 63: { id: 63, value: 1, rowId: 5, i: 5, j: 2, formula: 'sumLeft(2)' }, 64: { id: 64, value: 1, rowId: 5, i: 5, j: 3 }, // 第7行 leaf ( 列定义见上面 ) 71: { id: 71, value: 1, rowId: 6, i: 6, j: 0 }, 72: { id: 72, value: 1, rowId: 6, i: 6, j: 1 }, 73: { id: 73, value: 1, rowId: 6, i: 6, j: 2, formula: 'sumLeft(2)' }, 74: { id: 74, value: 1, rowId: 6, i: 6, j: 3 }, // 第8行 leaf ( 列定义见上面 ) 81: { id: 81, value: 1, rowId: 7, i: 7, j: 0 }, 82: { id: 82, value: 1, rowId: 7, i: 7, j: 1 }, 83: { id: 83, value: 1, rowId: 7, i: 7, j: 2, formula: 'sumLeft(2)' }, 84: { id: 84, value: 1, rowId: 7, i: 7, j: 3 }, // 第9行 leaf ( 列定义见上面 ) 91: { id: 91, value: 1, rowId: 8, i: 8, j: 0 }, 92: { id: 92, value: 1, rowId: 8, i: 8, j: 1, formula: '{{id12}}+{{id62}}' }, 93: { id: 93, value: 1, rowId: 8, i: 8, j: 2, formula: 'sumLeft(2)' }, 94: { id: 94, value: 1, rowId: 8, i: 8, j: 3 }, // 第10行 leaf ( 列定义见上面 ) 101: { id: 101, value: 1, rowId: 9, i: 9, j: 0, formula: 'sumSiblings()' }, 102: { id: 102, value: 1, rowId: 9, i: 9, j: 1, formula: 'sumSiblings()' }, 103: { id: 103, value: 1, rowId: 9, i: 9, j: 2, formula: 'sumLeft(2)' }, 104: { id: 104, value: 1, rowId: 9, i: 9, j: 3 }, }, }; const state = { ..._state, treeContext: ta.getTreeContexts(_state.rows.filter(it => it.type !== 'header')), }; const expected1 = { ...state, cells: { // 第1行 ( 列定义见上面 ) 11: { id: 11, value: 3, rowId: 0, i: 0, j: 0, error: null, formula: 'sumChildren()' }, 12: { id: 12, value: 3, rowId: 0, i: 0, j: 1, error: null, formula: 'sumChildren()' }, 13: { id: 13, value: 6, rowId: 0, i: 0, j: 2, error: null, formula: 'sumLeft(2)' }, 14: { id: 14, value: 1, rowId: 0, i: 0, j: 3 }, // 第2行 ( 列定义见上面 ) 21: { id: 21, value: 2, rowId: 1, i: 1, j: 0, error: null, formula: 'sumChildren()' }, 22: { id: 22, value: 2, rowId: 1, i: 1, j: 1, error: null, formula: 'sumChildren()' }, 23: { id: 23, value: 4, rowId: 1, i: 1, j: 2, error: null, formula: 'sumLeft(2)' }, 24: { id: 24, value: 1, rowId: 1, i: 1, j: 3 }, // 第3行 leaf ( 列定义见上面 ) 31: { id: 31, value: 1, rowId: 2, i: 2, j: 0 }, 32: { id: 32, value: 1, rowId: 2, i: 2, j: 1 }, 33: { id: 33, value: 2, rowId: 2, i: 2, j: 2, error: null, formula: 'sumLeft(2)' }, 34: { id: 34, value: 1, rowId: 2, i: 2, j: 3 }, // 第4行 leaf ( 列定义见上面 ) 41: { id: 41, value: 1, rowId: 3, i: 3, j: 0 }, 42: { id: 42, value: 1, rowId: 3, i: 3, j: 1 }, 43: { id: 43, value: 2, rowId: 3, i: 3, j: 2, error: null, formula: 'sumLeft(2)' }, 44: { id: 44, value: 1, rowId: 3, i: 3, j: 3 }, // 第5行 leaf ( 列定义见上面 ) 51: { id: 51, value: 1, rowId: 4, i: 4, j: 0 }, 52: { id: 52, value: 1, rowId: 4, i: 4, j: 1 }, 53: { id: 53, value: 2, rowId: 4, i: 4, j: 2, error: null, formula: 'sumLeft(2)' }, 54: { id: 54, value: 1, rowId: 4, i: 4, j: 3 }, // 第6行 ( 列定义见上面 ) 61: { id: 61, value: 2, rowId: 5, i: 5, j: 0, error: null, formula: 'sumChildren()' }, 62: { id: 62, value: 2, rowId: 5, i: 5, j: 1, error: null, formula: 'sumChildren()' }, 63: { id: 63, value: 4, rowId: 5, i: 5, j: 2, error: null, formula: 'sumLeft(2)' }, 64: { id: 64, value: 1, rowId: 5, i: 5, j: 3 }, // 第7行 leaf ( 列定义见上面 ) 71: { id: 71, value: 1, rowId: 6, i: 6, j: 0 }, 72: { id: 72, value: 1, rowId: 6, i: 6, j: 1 }, 73: { id: 73, value: 2, rowId: 6, i: 6, j: 2, error: null, formula: 'sumLeft(2)' }, 74: { id: 74, value: 1, rowId: 6, i: 6, j: 3 }, // 第8行 leaf ( 列定义见上面 ) 81: { id: 81, value: 1, rowId: 7, i: 7, j: 0 }, 82: { id: 82, value: 1, rowId: 7, i: 7, j: 1 }, 83: { id: 83, value: 2, rowId: 7, i: 7, j: 2, error: null, formula: 'sumLeft(2)' }, 84: { id: 84, value: 1, rowId: 7, i: 7, j: 3 }, // 第9行 leaf ( 列定义见上面 ) 91: { id: 91, value: 1, rowId: 8, i: 8, j: 0 }, 92: { id: 92, value: 5, rowId: 8, i: 8, j: 1, error: null, formula: '{{id12}}+{{id62}}' }, 93: { id: 93, value: 6, rowId: 8, i: 8, j: 2, error: null, formula: 'sumLeft(2)' }, 94: { id: 94, value: 1, rowId: 8, i: 8, j: 3 }, // 第10行 leaf ( 列定义见上面 ) 101: { id: 101, value: 6, rowId: 9, i: 9, j: 0, error: null, formula: 'sumSiblings()' }, 102: { id: 102, value: 10, rowId: 9, i: 9, j: 1, error: null, formula: 'sumSiblings()' }, 103: { id: 103, value: 16, rowId: 9, i: 9, j: 2, error: null, formula: 'sumLeft(2)' }, 104: { id: 104, value: 1, rowId: 9, i: 9, j: 3 }, }, cellDeps: { 11: [13, 101], 12: [13, 92, 102], 21: [11, 23], 22: [12, 23], 31: [21, 33], 32: [22, 33], 41: [21, 43], 42: [22, 43], 51: [11, 53], 52: [12, 53], 61: [63, 101], 62: [63, 92, 102], 71: [61, 73], 72: [62, 73], 81: [61, 83], 82: [62, 83], 91: [93, 101], 92: [93, 102], 101: [103], 102: [103], }, varDeps: {}, }; it('case0:综合', () => { const expected = expected1; const result = recalculate(state as any); expect(result.cells).toEqual(expected.cells); expect(result.cellDeps).toEqual(expected.cellDeps); expect(result.varDeps).toEqual(expected.varDeps); }); it('case1: 局部计算: 模拟2.5行被删除', () => { const expected = { ...state, cells: { ...expected1.cells, // 第1行 ( 列定义见上面 ) 11: { id: 11, value: 3, rowId: 0, i: 0, j: 0, error: null, formula: 'sumChildren()' }, 12: { id: 12, value: 3, rowId: 0, i: 0, j: 1, error: null, formula: 'sumChildren()' }, 13: { id: 13, value: 6, rowId: 0, i: 0, j: 2, error: null, formula: 'sumLeft(2)' }, 14: { id: 14, value: 1, rowId: 0, i: 0, j: 3 }, // 第2行 ( 列定义见上面 ) 21: { id: 21, value: 2, rowId: 1, i: 1, j: 0, error: null, formula: 'sumChildren()' }, 22: { id: 22, value: 2, rowId: 1, i: 1, j: 1, error: null, formula: 'sumChildren()' }, 23: { id: 23, value: 4, rowId: 1, i: 1, j: 2, error: null, formula: 'sumLeft(2)' }, 24: { id: 24, value: 1, rowId: 1, i: 1, j: 3 }, // 第10行 leaf ( 列定义见上面 ) 101: { id: 101, value: 6, rowId: 9, i: 9, j: 0, error: null, formula: 'sumSiblings()' }, 102: { id: 102, value: 10, rowId: 9, i: 9, j: 1, error: null, formula: 'sumSiblings()' }, 103: { id: 103, value: 16, rowId: 9, i: 9, j: 2, error: null, formula: 'sumLeft(2)' }, 104: { id: 104, value: 1, rowId: 9, i: 9, j: 3 }, }, cellDeps: { 11: [13, 101], 12: [13, 92, 102], 21: [11, 23], 22: [12, 23], 31: [33, 21], 32: [33, 22], 41: [43, 21], 42: [43, 22], 51: [53, 11], 52: [53, 12], 61: [63, 101], 62: [63, 92, 102], 71: [73, 61], 72: [73, 62], 81: [83, 61], 82: [83, 62], 91: [93, 101], 92: [93, 102], 101: [103], 102: [103], }, varDeps: {}, }; // 如何正确使用recalculate ? // 1. 若整体重算, 则不要提供整个extra // 2. 若更新部分行, 一定要计算清楚: // targetRowIds: 有哪些行受影响, 必须要全部列出来!( 要根据cellDeps 进行严格推算, 不要使用sumChildren等方法近似推算 ) // cellDeps: 将受影响的行(包含被删除的行), 统统从cellDeps中清除掉, 防止重算时重算加入 // 例: {33: [61, 41], 81: [41, 61]}, 并且31,33属于第3行, 41属于第4行, 现在targetRowIds=[3,4], 则必须清除为 {81:[61]} const result = recalculate(expected1 as any, { // 2.5行被删除, 因此重算以下行 targetRowIds: [1, 0, 8, 9], }); expect(result.cells).toEqual(expected.cells); expect(result.cellDeps).toEqual(expected.cellDeps); expect(result.varDeps).toEqual(expected.varDeps); }); }); describe('cleanDeps', () => { const rows = [ { id: 1, cellIds: [11, 12] }, { id: 2, cellIds: [21] }, { id: 3, cellIds: [31] }, { id: 4, cellIds: [41] }, ]; const cells = { 11: { id: 11 }, 12: { id: 12 }, 21: { id: 21 }, 31: { id: 31 }, 41: { id: 41 }, }; const state = { rows, cells, cellDeps: { 21: [11], 31: [11], 11: [41], 41: [51, 41], // = > 41: [41] 51: [31], // => undefined 12: [51], // => undefined }, varDeps: { a: [11, 21, 51], // => [11,21] b: [31, 21], }, }; expect(cleanDeps(state as any, [])).toEqual({ varDeps: { a: [11, 21], b: [31, 21] }, cellDeps: { 21: [11], 31: [11], 11: [41], 41: [41], // = > 41: [41] }, }); expect(cleanDeps(state as any, [{ id: 3, cellIds: [31] }])).toEqual({ varDeps: { a: [11, 21], b: [21] }, cellDeps: { 21: [11], 11: [41], 41: [41], // = > 41: [41] }, }); expect(cleanDeps(state as any, [{ id: 1, cellIds: [11, 12] }])).toEqual({ cellDeps: { 41: [41], }, varDeps: { a: [21], b: [31, 21], }, }); });