import * as R from 'rambda'; import { isCellLabel, isCellIdBraced, getIJByLabel, getCellByLabel, extractIdFromBracedIdStr, evalCellValue, sumLeft, sumChildren, sumSiblings, getLabelByIj, getRangeValue, sumSamePosition, getCellRelativeRefs, realizeFormulaByRelativeRefs, updateFormula, } from '.'; import { IStoreState, ICell, ID } from '../../index.data'; import { ERROR, ERROR_REF } from '../../formula-parser/error'; import Parser from '../../formula-parser/parser'; import { ok, failure } from '../../formula-parser/helper/result'; import { FormulaManagerConfig } from '../../formula-manager/index.data'; import { getExternalCellIdByLabel } from './externalCell'; import { RootNodeId } from 'valor-app-utils/dist/tree/interface'; import { array2tree_byLevel, getTreeContexts } from 'valor-app-utils'; describe('isCellLabel', () => { it('case0', () => { expect(isCellLabel('A')).toBe(false); expect(isCellLabel('A1')).toBe(true); expect(isCellLabel('BB')).toBe(false); expect(isCellLabel('BB13')).toBe(true); expect(isCellLabel('BB13:A15')).toBe(false); expect(isCellLabel('A!A1')).toBe(true); expect(isCellLabel('测试sheet!A1')).toBe(true); expect(isCellLabel('!A1')).toBe(false); }); }); describe('isCellId', () => { it('case0', () => { expect(isCellIdBraced('{}}')).toBe(false); expect(isCellIdBraced('{{}}')).toBe(false); expect(isCellIdBraced('{{3}}')).toBe(false); expect(isCellIdBraced('idaaa')).toBe(false); expect(isCellIdBraced('{{id}}')).toBe(false); expect(isCellIdBraced('{{id1a}}')).toBe(true); expect(isCellIdBraced('{{id1a:id2b}}')).toBe(false); }); }); describe('getCellByLabel', () => { it('case0', () => { const state = { rows: [ { id: 1, cellIds: [1, null] }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5, cellIds: [null, 2] }, { id: 6 }, { id: 7 }, { id: 8 }, { id: 9 }, { id: 10 }, ], collumns: { length: 10 }, cells: { 1: { id: 1, rowId: 1, i: 0, j: 0 }, 2: { id: 2, rowId: 5, i: 4, j: 1 }, }, } as any; expect(getCellByLabel(state, 'A1')!.id).toEqual(1); expect(getCellByLabel(state, 'B5')!.id).toEqual(2); expect(() => getCellByLabel(state, 'R5')!.id).toThrow(); }); }); describe('extractIdFromBracedIdStr', () => { it('case0', () => { expect(extractIdFromBracedIdStr('{{id123}}')).toEqual('123'); expect(extractIdFromBracedIdStr('{{id12_a3}}')).toEqual('12_a3'); expect(extractIdFromBracedIdStr('{{id12!a3}}')).toEqual('12!a3'); }); }); const state = { rows: [ { id: 1, i: 0, type: 'body', cellIds: [1, 2, 3] }, { id: 2, i: 1, type: 'body', cellIds: [4, 5, 6] }, { id: 3, i: 2, type: 'body', cellIds: [7, 8, 9] }, { id: 4, i: 3, type: 'body', area: 'flatten', cellIds: [11, 12, 13] }, ], columns: { length: 3 }, cells: { 1: { id: 1, i: 0, j: 0, value: 1, rowId: 1 }, 2: { id: 2, i: 0, j: 1, value: 1, rowId: 1 }, 3: { id: 3, i: 0, j: 2, value: 1, rowId: 1 }, 4: { id: 4, i: 1, j: 0, value: 1, rowId: 2 }, 5: { id: 5, i: 1, j: 1, value: 1, rowId: 2 }, 6: { id: 6, i: 1, j: 2, value: 1, rowId: 2 }, 7: { id: 7, i: 2, j: 0, value: 1, rowId: 3 }, 8: { id: 8, i: 2, j: 1, value: 1, rowId: 3 }, 9: { id: 9, i: 2, j: 2, value: 1, rowId: 3 }, 11: { id: 11, i: 3, j: 0, value: 1, rowId: 4 }, 12: { id: 12, i: 3, j: 1, value: 1, rowId: 4 }, 13: { id: 13, i: 3, j: 2, value: 1, rowId: 4 }, }, treeContext: { 1: { parentId: 0, level: 1, childrenIds: [2, 3] }, 2: { parentId: 1, level: 2 }, 3: { parentId: 1, level: 2 }, }, }; describe('sumSibling', () => { const state1 = { ...state, treeContext: { 0: { parentId: undefined, level: 0, childrenIds: [1] }, 1: { parentId: 0, level: 1, childrenIds: [2, 3] }, 2: { parentId: 1, level: 1 }, 3: { parentId: 1, level: 1 }, }, }; it('case0: 没有兄弟, 返回0', () => { // 第1行第1个单元格 expect(sumSiblings(state1 as any, 1).result).toEqual(0); }); const state2 = { ...state, treeContext: { 0: { parentId: undefined, level: 0, childrenIds: [1, 2, 3] }, 1: { parentId: 0, level: 1 }, 2: { parentId: 0, level: 1 }, 3: { parentId: 0, level: 1 }, }, }; it('case1: 正常', () => { // 第1行第1个单元格 expect(sumSiblings(state2 as any, 1).result).toEqual(2); // 第3行第1个单元格 expect(sumSiblings(state2 as any, 3).result).toEqual(2); }); }); describe('sumChildren', () => { const state1 = { ...state, treeContext: { 1: { parentId: 0, level: 1, childrenIds: [2, 3] }, 2: { parentId: 1, level: 1 }, 3: { parentId: 1, level: 1 }, }, }; it('case0, 没有下级, 返回0 ', () => { // 第2行第1个单元格 expect(sumChildren(state1 as any, 4).result).toEqual(0); // 第3行第3个单元格 expect(sumChildren(state1 as any, 9).result).toEqual(0); }); it('case1, 两个下级', () => { // 第1行第1个单元格 expect(sumChildren(state1 as any, 1).result).toEqual(2); }); }); describe('sumLeft', () => { it('case0: 如果推断出的列号小于0则抛错', () => { expect(() => sumLeft(state as any, 1, 0, 1)).toThrow(); expect(() => sumLeft(state as any, 1, 1, 1)).toThrow(); expect(() => sumLeft(state as any, 3, 3, 1)).toThrow(); }); it('case1:正常', () => { expect(sumLeft(state as any, 2, 1, 1).result).toEqual(1); expect(sumLeft(state as any, 9, 2, 1).result).toEqual(2); }); }); describe('getRangeValue', () => { it('case1 起始相同: range(1,1) => [1]', () => { expect(getRangeValue(state as any, 1, 1)).toEqual({ result: [[1]], error: null, deps: { cells: [1] }, }); }); it('case2 单行: range(1,3) => []', () => { expect(getRangeValue(state as any, 1, 3)).toEqual({ result: [[1, 1, 1]], error: null, deps: { cells: [1, 2, 3] }, }); }); it('case3 单列: range(2,8) => [2,5,8]', () => { expect(getRangeValue(state as any, 2, 8)).toEqual({ result: [[1], [1], [1]], error: null, deps: { cells: [2, 5, 8] }, }); }); it('case4 common: range(1,5) => [[1,2],[4,5]]', () => { expect(getRangeValue(state as any, 1, 5)).toEqual({ result: [[1, 1], [1, 1]], error: null, deps: { cells: [1, 2, 4, 5] }, }); }); it('case5 反向: range(5,1) => [[1,2],[4,5]]', () => { expect(getRangeValue(state as any, 5, 1)).toEqual({ result: [[1, 1], [1, 1]], error: null, deps: { cells: [1, 2, 4, 5] }, }); }); it('case6从右上向左下: range(2,4) => [[1,2],[4,5]]', () => { expect(getRangeValue(state as any, 2, 4)).toEqual({ result: [[1, 1], [1, 1]], error: null, deps: { cells: [1, 2, 4, 5] }, }); }); }); describe('evalCellValue', () => { const parser = new Parser({ getCellValue: (sCell: string) => { let result = 0; if (sCell === 'A1') { result = 1; } else if (sCell === 'a2') { result = 2; } else { throw Error(ERROR_REF); } return { result, error: null, deps: {} }; }, getRangeValue: (args: any) => { return { result: [[1]], error: null, deps: {} }; }, }); it('case0: 无公式', () => { expect(evalCellValue({} as any, { value: 1 } as ICell, parser)).toEqual(ok(1)); }); it('case1: 有公式', () => { expect(evalCellValue({} as any, { formula: '1+1' } as ICell, parser).result).toEqual( ok(2).result, ); expect(evalCellValue({} as any, { formula: 'A1+1' } as ICell, parser).result).toEqual( ok(2).result, ); expect(evalCellValue({} as any, { formula: 'A1+a2' } as ICell, parser).result).toEqual( ok(3).result, ); expect(evalCellValue({} as any, { formula: 'A1:A1' } as ICell, parser).result).toEqual( ok([[1]]).result, ); }); it('case2: 函数', () => { expect(evalCellValue({} as any, { formula: 'not_exists(1,1)' } as ICell, parser).error).toEqual( failure('#NAME?').error, ); expect(evalCellValue({} as any, { formula: 'sum(1,1)' } as ICell, parser).result).toEqual( ok(2).result, ); expect(evalCellValue({} as any, { formula: 'sum(A1:A1)' } as ICell, parser).result).toEqual( ok(1).result, ); expect( evalCellValue({} as any, { formula: 'round(100.21,1)' } as ICell, parser).result, ).toEqual(ok(100.2).result); expect(evalCellValue({} as any, { formula: 'if(true,1, 0)' } as ICell, parser).result).toEqual( ok(1).result, ); expect(evalCellValue({} as any, { formula: 'not(false)' } as ICell, parser).result).toEqual( ok(true).result, ); expect( evalCellValue({} as any, { formula: 'or(false, true)' } as ICell, parser).result, ).toEqual(ok(true).result); expect( evalCellValue({} as any, { formula: 'and(false, true)' } as ICell, parser).result, ).toEqual(ok(false).result); expect(evalCellValue({} as any, { formula: 'and(1=1, 2=2)' } as ICell, parser).result).toEqual( ok(true).result, ); expect(evalCellValue({} as any, { formula: 'and(1>1, 2=2)' } as ICell, parser).result).toEqual( ok(false).result, ); }); //异常 it('case4: 异常处理', () => { expect(evalCellValue({} as any, { formula: 'A8' } as ICell, parser).error).toEqual( failure('#REF!').error, ); expect(evalCellValue({} as any, { formula: 'A1/0' } as ICell, parser).error).toEqual( failure('#DIV/0!').error, ); }); // 简单性能 it('case10: 性能', async () => { console.time('case10'); const xs = R.range(0, 10000).map( _ => new Promise(resolve => { setTimeout(() => { const x = evalCellValue({} as any, { formula: 'if(true,1, 0)' } as ICell, parser); resolve(x); }, 0); }), ); await Promise.all(xs); console.timeEnd('case10'); expect(true).toEqual(true); }); }); describe('getIJByLabel', () => { it('case0', () => { expect(getIJByLabel('A1')).toEqual({ i: 0, j: 0 }); expect(getIJByLabel('B5')).toEqual({ i: 4, j: 1 }); expect(getIJByLabel('Z1')).toEqual({ i: 0, j: 25 }); expect(getIJByLabel('AA1')).toEqual({ i: 0, j: 26 }); // A => 0, X => 25, AA => 26, AX => 51, BA => 52, BB => 53 expect(getIJByLabel('BB5')).toEqual({ i: 4, j: 53 }); expect(getIJByLabel('AB1')).toEqual({ i: 0, j: 27 }); expect(getIJByLabel('BA1')).toEqual({ i: 0, j: 52 }); }); }); describe('getLabelByIj', () => { it('case0', () => { expect(getLabelByIj(0, 0)).toEqual('A1'); expect(getLabelByIj(0, 1)).toEqual('B1'); expect(getLabelByIj(4, 1)).toEqual('B5'); expect(getLabelByIj(0, 25)).toEqual('Z1'); expect(getLabelByIj(0, 26)).toEqual('AA1'); expect(getLabelByIj(0, 27)).toEqual('AB1'); expect(getLabelByIj(0, 52)).toEqual('BA1'); expect(getLabelByIj(4, 53)).toEqual('BB5'); }); it('case1', () => { expect(getLabelByIj(110, 0)).toEqual('A111'); }); }); describe('getExternalCellId', () => { const config = { getSheetIdByCode: (code: string) => { if (code === 'A') return '1'; return null; }, getExternalCellIdByLabel: (sheetCode: string, cellLabel: string) => { return 'ext11'; }, } as FormulaManagerConfig; it('case0', () => { expect(getExternalCellIdByLabel('A!A1', config)).toEqual('1!ext11'); expect(() => getExternalCellIdByLabel('B!A1', config)).toThrow(); }); }); const state1 = ({ //0 // 1 // 2 // 3 // 4 // 5 // 6 // 7 // 8 // 9 // 10 // 11 // 12 // 13 // 14 rows: [ { id: 0, level: 1, i: 0, type: 'body', cellIds: [0] }, { id: 1, level: 2, i: 0, type: 'body', cellIds: [1] }, { id: 2, level: 3, i: 1, type: 'body', cellIds: [2] }, { id: 3, level: 4, i: 2, type: 'body', cellIds: [3] }, { id: 4, level: 4, i: 3, type: 'body', cellIds: [4] }, { id: 5, level: 3, i: 4, type: 'body', cellIds: [5] }, { id: 6, level: 4, i: 5, type: 'body', cellIds: [6] }, { id: 7, level: 4, i: 6, type: 'body', cellIds: [7] }, { id: 8, level: 2, i: 7, type: 'body', cellIds: [8] }, { id: 9, level: 3, i: 8, type: 'body', cellIds: [9] }, { id: 10, level: 4, i: 9, type: 'body', cellIds: [10] }, { id: 11, level: 4, i: 10, type: 'body', cellIds: [11] }, { id: 12, level: 3, i: 11, type: 'body', cellIds: [12] }, { id: 13, level: 4, i: 12, type: 'body', cellIds: [13] }, { id: 14, level: 4, i: 13, type: 'body', cellIds: [14] }, ], columns: { length: 1 }, cells: { 0: { id: 0, i: 0, j: 0, value: 1, rowId: 0 }, 1: { id: 1, i: 1, j: 0, value: 1, rowId: 1 }, 2: { id: 2, i: 2, j: 0, value: 1, rowId: 2 }, 3: { id: 3, i: 3, j: 0, value: 1, rowId: 3 }, 4: { id: 4, i: 4, j: 0, value: 1, rowId: 4 }, 5: { id: 5, i: 5, j: 0, value: 1, rowId: 5 }, 6: { id: 6, i: 6, j: 0, value: 1, rowId: 6 }, 7: { id: 7, i: 7, j: 0, value: 1, rowId: 7 }, 8: { id: 8, i: 8, j: 0, value: 1, rowId: 8 }, 9: { id: 9, i: 9, j: 0, value: 1, rowId: 9 }, 10: { id: 10, i: 10, j: 0, value: 1, rowId: 10 }, 11: { id: 11, i: 11, j: 0, value: 1, rowId: 11 }, 12: { id: 12, i: 12, j: 0, value: 1, rowId: 12 }, 13: { id: 13, i: 13, j: 0, value: 1, rowId: 13 }, 14: { id: 14, i: 14, j: 0, value: 1, rowId: 14 }, }, } as any) as IStoreState; state1.treeContext = getTreeContexts(array2tree_byLevel(state1.rows as any)); describe('sumByPosition', () => { it('case0: 第A11格, 跨1层', () => { // 第1行第1个单元格 expect(sumSamePosition(state1 as any, 3, 1)).toEqual({ result: 1, error: null, deps: { cells: [6] }, }); }); it('case1: 第11格, 跨2层', () => { expect(sumSamePosition(state1 as any, 3, 2)).toEqual({ result: 3, error: null, deps: { cells: [6, 10, 13] }, }); }); }); describe('getCellRelativeRefs', () => { /** * 11 12 13(11+22) * 21 22 23 * 在最后插入一行, 变成: * 11 12 13(11+22) <= 引用显然不变, 与excel相同, 所以最后可能需手改成 11+32 * 31 32 33(11+22) => (31+22) * 21 22 23 */ const stateBefore = ({ rows: [{ id: 1, cellIds: [11, 12, 13] }, { id: 2, cellIds: [21, 22, 23] }], cells: { 11: { id: 11, rowId: 1 }, 12: { id: 12, rowId: 1 }, 13: { id: 13, rowId: 1, formula: '{{id11}}+{{id22}}+{{idAA!A1}}' }, 21: { id: 21, rowId: 2 }, 22: { id: 22, rowId: 2 }, 23: { id: 23, rowId: 2 }, }, } as any) as IStoreState; it('case0', () => { const result1 = getCellRelativeRefs(stateBefore, 13); const expected1 = { 11: { di: 0, dj: -2 }, 22: { di: 1, dj: -1 } }; expect(result1).toEqual(expected1); }); const stateAfter = ({ rows: [ { id: 1, cellIds: [11, 12, 13] }, { id: 3, cellIds: [31, 32, 33] }, { id: 2, cellIds: [21, 22, 23] }, ], cells: { 11: { id: 11, rowId: 1 }, 12: { id: 12, rowId: 1 }, 13: { id: 13, rowId: 1, formula: '{{id11}}+{{id22}}+{{idAA!A1}}' }, 31: { id: 31, rowId: 3 }, 32: { id: 32, rowId: 3 }, 33: { id: 33, rowId: 3, formula: '{{id11}}+{{id22}}+{{idAA!A1}}' }, // <== 复制的公式 21: { id: 21, rowId: 2 }, 22: { id: 22, rowId: 2 }, 23: { id: 23, rowId: 2 }, }, } as any) as IStoreState; it('case1', () => { const refs = { 11: { di: 0, dj: -2 }, 22: { di: 1, dj: -1 } }; const result1 = realizeFormulaByRelativeRefs(stateAfter, 33, refs); const expected1 = '{{id31}}+{{id22}}+{{idAA!A1}}'; expect(result1).toEqual(expected1); }); it('case2', () => { const baseCell2newCell = { 11: 31, 12: 32, 13: 33, }; const result = updateFormula(stateBefore, stateAfter, baseCell2newCell); const expected = [{ id: 33, rowId: 3, formula: '{{id31}}+{{id22}}+{{idAA!A1}}' }]; expect(result).toEqual(expected); }); });