diff --git a/packages/engine-formula/src/facade/f-formula.ts b/packages/engine-formula/src/facade/f-formula.ts index c5ec4254e5..dd408e0d84 100644 --- a/packages/engine-formula/src/facade/f-formula.ts +++ b/packages/engine-formula/src/facade/f-formula.ts @@ -166,20 +166,7 @@ export class FFormula extends FBase { } /** - * Wait for computing in the Univer instance to complete. Please note that this does not only include formula calculation, - * but also other computing tasks, e.g. pivot table calculation. - * @param {number} [timeout] The maximum time to wait for the computing to complete, in milliseconds. The default - * value is 30,000 milliseconds. - * @returns {Promise} This method returns `true` if the computing is complete. If the timeout is reached, this - * method returns `false`. - * - * @example - * ```ts - * const formulaEngine = univerAPI.getFormula(); - * formulaEngine.whenComputingCompleteAsync(3000).then((isComplete) => { - * console.log('Computing complete:', isComplete); - * }); - * ``` + * @deprecated Use `onCalculationEnd` instead. */ whenComputingCompleteAsync(timeout?: number): Promise { const gcss = this._injector.get(GlobalComputingStatusService); @@ -192,7 +179,7 @@ export class FFormula extends FBase { } /** - * @deprecated Use `whenComputingCompleteAsync` instead. + * Waits for the formula calculation to complete. * @returns {Promise} This method returns a promise that resolves when the calculation is complete. */ onCalculationEnd(): Promise { diff --git a/packages/engine-formula/src/models/__tests__/formula-data.model.spec.ts b/packages/engine-formula/src/models/__tests__/formula-data.model.spec.ts index 2818a70226..680a072eb4 100644 --- a/packages/engine-formula/src/models/__tests__/formula-data.model.spec.ts +++ b/packages/engine-formula/src/models/__tests__/formula-data.model.spec.ts @@ -200,20 +200,6 @@ describe('Test formula data model', () => { }; const result = { - 1: { - 3: { - f: '=SUM(A2)', - si: 'OSPtzm', - }, - }, - 2: { - 3: { - f: '=SUM(A2)', - si: 'OSPtzm', - x: 0, - y: 1, - }, - }, 3: { 3: null, }, diff --git a/packages/engine-formula/src/models/utils/formula-data-util.ts b/packages/engine-formula/src/models/utils/formula-data-util.ts index fb9b6c4359..64da1a2a6c 100644 --- a/packages/engine-formula/src/models/utils/formula-data-util.ts +++ b/packages/engine-formula/src/models/utils/formula-data-util.ts @@ -29,12 +29,14 @@ export function updateFormulaDataByCellValue(sheetFormulaDataMatrix: ObjectMatri const currentFormulaInfo = sheetFormulaDataMatrix.getValue(r, c); const f = currentFormulaInfo?.f || ''; const si = currentFormulaInfo?.si || ''; + const x = currentFormulaInfo?.x || 0; + const y = currentFormulaInfo?.y || 0; // Any data update may destroy the original correspondence between f and si, and the relationship between f and si needs to be re-bound. function clearFormulaData() { // The id that needs to be offset // When the cell containing the formulas f and si is deleted, f and si lose their association, and f needs to be moved to the next cell containing the same si. - if (isFormulaString(f) && isFormulaId(si)) { + if (isFormulaString(f) && isFormulaId(si) && x === 0 && y === 0) { const updatedFormula = formulaIdMap?.[si]?.f; // The formula may have been updated. For example, when you delete a column referenced by a formula, it will become #REF and cannot take the original value. diff --git a/packages/sheets-formula-ui/src/controllers/__tests__/update-formula.controller.spec.ts b/packages/sheets-formula-ui/src/controllers/__tests__/update-formula.controller.spec.ts index d60cdab25f..039cb7c5c1 100644 --- a/packages/sheets-formula-ui/src/controllers/__tests__/update-formula.controller.spec.ts +++ b/packages/sheets-formula-ui/src/controllers/__tests__/update-formula.controller.spec.ts @@ -639,19 +639,19 @@ describe('Test update formula ', () => { expect(values).toStrictEqual([ [null], [null], - [{ f: '=A1', t: 2, v: 1 }], - [{ f: '=A2', t: 2, v: 2 }], - [{ f: '=A3', t: 2, v: 3 }], - [{ f: '=A4', t: 2, v: 4 }], + [{ f: '=A1' }], + [{ f: '=A2' }], + [{ f: '=A3' }], + [{ f: '=A4' }], ]); expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); const valuesUndo = getValues(0, 1, 5, 1, sheetId); expect(valuesUndo).toStrictEqual([ - [{ f: '=A1', t: 2, v: 1 }], - [{ f: '=A2', si: 'W8Hdfc', t: 2, v: 2 }], - [{ si: 'W8Hdfc', t: 2, v: 3 }], - [{ si: 'W8Hdfc', t: 2, v: 4 }], + [{ f: '=A1' }], + [{ f: '=A2', si: 'W8Hdfc' }], + [{ si: 'W8Hdfc' }], + [{ si: 'W8Hdfc' }], [null], [null], ]); @@ -661,10 +661,10 @@ describe('Test update formula ', () => { expect(valuesRedo).toStrictEqual([ [null], [null], - [{ f: '=A1', t: 2, v: 1 }], - [{ f: '=A2', t: 2, v: 2 }], - [{ f: '=A3', t: 2, v: 3 }], - [{ f: '=A4', t: 2, v: 4 }], + [{ f: '=A1' }], + [{ f: '=A2' }], + [{ f: '=A3' }], + [{ f: '=A4' }], ]); }); @@ -696,12 +696,12 @@ describe('Test update formula ', () => { expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); const values1 = getValues(1, 3, 1, 3, sheetId); - expect(values1).toStrictEqual([[{ f: '=A2', t: 2, v: 2 }]]); + expect(values1).toStrictEqual([[{ f: '=A2' }]]); const values2 = getValues(1, 1, 3, 1, sheetId); expect(values2).toStrictEqual([ [null], - [{ f: '=A3', t: 2, v: 3 }], - [{ f: '=A4', t: 2, v: 4 }], + [{ f: '=A3' }], + [{ f: '=A4' }], ]); expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); @@ -709,19 +709,19 @@ describe('Test update formula ', () => { expect(valuesUndo1).toStrictEqual([[null]]); const valuesUndo2 = getValues(1, 1, 3, 1, sheetId); expect(valuesUndo2).toStrictEqual([ - [{ f: '=A2', si: 'W8Hdfc', t: 2, v: 2 }], - [{ si: 'W8Hdfc', t: 2, v: 3 }], - [{ si: 'W8Hdfc', t: 2, v: 4 }], + [{ f: '=A2', si: 'W8Hdfc' }], + [{ si: 'W8Hdfc' }], + [{ si: 'W8Hdfc' }], ]); expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); const valuesRedo1 = getValues(1, 3, 1, 3, sheetId); - expect(valuesRedo1).toStrictEqual([[{ f: '=A2', t: 2, v: 2 }]]); + expect(valuesRedo1).toStrictEqual([[{ f: '=A2' }]]); const valuesRedo2 = getValues(1, 1, 3, 1, sheetId); expect(valuesRedo2).toStrictEqual([ [null], - [{ f: '=A3', t: 2, v: 3 }], - [{ f: '=A4', t: 2, v: 4 }], + [{ f: '=A3' }], + [{ f: '=A4' }], ]); }); @@ -753,7 +753,7 @@ describe('Test update formula ', () => { expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); const values1 = getValues(2, 3, 2, 3, sheetId); - expect(values1).toStrictEqual([[{ f: '=A3', t: 2, v: 3 }]]); + expect(values1).toStrictEqual([[{ f: '=A3' }]]); const values2 = getValues(1, 1, 3, 1, sheetId); expect(values2).toStrictEqual([ [{ f: '=A2', si: 'W8Hdfc', t: 2, v: 2 }], @@ -767,13 +767,13 @@ describe('Test update formula ', () => { const valuesUndo2 = getValues(1, 1, 3, 1, sheetId); expect(valuesUndo2).toStrictEqual([ [{ f: '=A2', si: 'W8Hdfc', t: 2, v: 2 }], - [{ si: 'W8Hdfc', t: 2, v: 3 }], + [{ si: 'W8Hdfc' }], [{ si: 'W8Hdfc', t: 2, v: 4 }], ]); expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); const valuesRedo1 = getValues(2, 3, 2, 3, sheetId); - expect(valuesRedo1).toStrictEqual([[{ f: '=A3', t: 2, v: 3 }]]); + expect(valuesRedo1).toStrictEqual([[{ f: '=A3' }]]); const valuesRedo2 = getValues(1, 1, 3, 1, sheetId); expect(valuesRedo2).toStrictEqual([ [{ f: '=A2', si: 'W8Hdfc', t: 2, v: 2 }], @@ -810,30 +810,30 @@ describe('Test update formula ', () => { expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); const values1 = getValues(1, 3, 2, 3, sheetId); - expect(values1).toStrictEqual([[{ f: '=A2', t: 2, v: 2 }], [{ f: '=A3', t: 2, v: 3 }]]); + expect(values1).toStrictEqual([[{ f: '=A2' }], [{ f: '=A3' }]]); const values2 = getValues(1, 1, 3, 1, sheetId); expect(values2).toStrictEqual([ [null], [null], - [{ f: '=A4', t: 2, v: 4 }], + [{ f: '=A4' }], ]); expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); const valuesUndo = getValues(1, 1, 3, 1, sheetId); expect(valuesUndo).toStrictEqual([ - [{ f: '=A2', si: 'W8Hdfc', t: 2, v: 2 }], - [{ si: 'W8Hdfc', t: 2, v: 3 }], - [{ si: 'W8Hdfc', t: 2, v: 4 }], + [{ f: '=A2', si: 'W8Hdfc' }], + [{ si: 'W8Hdfc' }], + [{ si: 'W8Hdfc' }], ]); expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); const valuesRedo1 = getValues(1, 3, 2, 3, sheetId); - expect(valuesRedo1).toStrictEqual([[{ f: '=A2', t: 2, v: 2 }], [{ f: '=A3', t: 2, v: 3 }]]); + expect(valuesRedo1).toStrictEqual([[{ f: '=A2' }], [{ f: '=A3' }]]); const valuesRedo2 = getValues(1, 1, 3, 1, sheetId); expect(valuesRedo2).toStrictEqual([ [null], [null], - [{ f: '=A4', t: 2, v: 4 }], + [{ f: '=A4' }], ]); }); @@ -858,25 +858,25 @@ describe('Test update formula ', () => { expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); const values = getValues(18, 1, 20, 2); expect(values).toStrictEqual([ - [null, { f: '=SUM(A19)', t: 2, v: 1 }], - [null, { f: '=SUM(A20)', t: 2, v: 2 }], - [null, { f: '=SUM(A21)', t: 2, v: 3 }], + [null, { f: '=SUM(A19)' }], + [null, { f: '=SUM(A20)' }], + [null, { f: '=SUM(A21)' }], ]); expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); const valuesUndo = getValues(18, 1, 20, 2); expect(valuesUndo).toStrictEqual([ - [{ f: '=SUM(A19)', t: 2, v: 1 }, null], - [{ f: '=SUM(A20)', si: 'id1', t: 2, v: 2 }, null], - [{ si: 'id1', t: 2, v: 3 }, null], + [{ f: '=SUM(A19)' }, null], + [{ f: '=SUM(A20)', si: 'id1' }, null], + [{ si: 'id1' }, null], ]); expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); const valuesRedo = getValues(18, 1, 20, 2); expect(valuesRedo).toStrictEqual([ - [null, { f: '=SUM(A19)', t: 2, v: 1 }], - [null, { f: '=SUM(A20)', t: 2, v: 2 }], - [null, { f: '=SUM(A21)', t: 2, v: 3 }], + [null, { f: '=SUM(A19)' }], + [null, { f: '=SUM(A20)' }], + [null, { f: '=SUM(A21)' }], ]); }); @@ -1586,52 +1586,21 @@ describe('Test update formula ', () => { const values = getValues(21, 0, 21, 4); // Ignore the calculation results and only verify the offset information - expect(values).toStrictEqual([[{ - f: '=OFFSET(#REF!,1,1)', - v: 0, - t: 2, - }, { - f: '=OFFSET(#REF!,1,1)', - v: 1, - t: 2, - }, { - f: '=OFFSET(A1,1,1)', - v: 1, - t: 2, - }, null, null]]); + expect(values).toStrictEqual([ + [{ f: '=OFFSET(#REF!,1,1)' }, { f: '=OFFSET(#REF!,1,1)' }, { f: '=OFFSET(A1,1,1)' }, null, null], + ]); expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); const valuesUndo = getValues(21, 0, 21, 4); - expect(valuesUndo).toStrictEqual([[null, null, { - f: '=OFFSET(A1,1,1)', - v: 0, - t: 2, - }, { - f: '=OFFSET(B1,1,1)', - si: 'id2', - v: 1, - t: 2, - }, { - si: 'id2', - v: 1, - t: 2, - }]]); + expect(valuesUndo).toStrictEqual([ + [null, null, { f: '=OFFSET(A1,1,1)' }, { f: '=OFFSET(B1,1,1)', si: 'id2' }, { si: 'id2' }], + ]); expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); const valuesRedo = getValues(21, 0, 21, 4); - expect(valuesRedo).toStrictEqual([[{ - f: '=OFFSET(#REF!,1,1)', - v: 0, - t: 2, - }, { - f: '=OFFSET(#REF!,1,1)', - v: 1, - t: 2, - }, { - f: '=OFFSET(A1,1,1)', - v: 1, - t: 2, - }, null, null]]); + expect(valuesRedo).toStrictEqual([ + [{ f: '=OFFSET(#REF!,1,1)' }, { f: '=OFFSET(#REF!,1,1)' }, { f: '=OFFSET(A1,1,1)' }, null, null], + ]); }); it('Remove column, removed column contains formula', async () => { diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index 8d8e5ce3fa..65c4bba007 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -158,7 +158,7 @@ export class UpdateFormulaController extends Disposable { { unitId, subUnitId: sheetId, - cellValue: formulaDataToCellData(newSheetFormulaData), + cellValue: formulaDataToCellData(newSheetFormulaData, cellValue), }, { onlyLocal: true, diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 6cf4676b28..c824c0f01a 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -589,13 +589,18 @@ function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM * └──────────────────┴─────┴───┴───┴───────────┴─────┘ */ export function formulaDataItemToCellData(formulaDataItem: Nullable): Nullable { - if (formulaDataItem == null) { + if (formulaDataItem === undefined) { + return; + } + + if (formulaDataItem === null) { // null presents clearing cell content return { f: null, si: null, }; } + const { f, si, x = 0, y = 0 } = formulaDataItem; const checkFormulaString = isFormulaString(f); const checkFormulaId = isFormulaId(si); @@ -633,7 +638,7 @@ export function formulaDataItemToCellData(formulaDataItem: Nullable): IObjectMatrixPrimitiveType> { +export function formulaDataToCellData(formulaData: IObjectMatrixPrimitiveType, changedCellValue?: IObjectMatrixPrimitiveType>): IObjectMatrixPrimitiveType> { const cellData = new ObjectMatrix>({}); const formulaDataMatrix = new ObjectMatrix(formulaData); @@ -646,6 +651,14 @@ export function formulaDataToCellData(formulaData: IObjectMatrixPrimitiveType setTimeout(resolve, 500)); + const resultSnapshot = workbook.save(); const snapshotFilePath = path.resolve(snapshotRootDir, `${getTestFilePath()}-result.json`); if (fs.existsSync(snapshotFilePath)) { @@ -84,6 +87,8 @@ export async function expectMoveFormulaRowsResultMatchesSnapshot() { // perform undo operation await testBed.api.undo(); + await testBed.api.getFormula().onCalculationResultApplied(); + await new Promise((resolve) => setTimeout(resolve, 500)); // compare the result with the snapshot const resultSnapshot_undo = workbook.save(); @@ -120,6 +125,9 @@ export async function expectMoveFormulaSiRowsResultMatchesSnapshot() { const rowSpec = worksheet.getRange('3:3'); worksheet.moveRows(rowSpec, 4); + await testBed.api.getFormula().onCalculationResultApplied(); + await new Promise((resolve) => setTimeout(resolve, 500)); + const resultSnapshot = workbook.save(); const snapshotFilePath = path.resolve(snapshotRootDir, `${getTestFilePath()}-result.json`); if (fs.existsSync(snapshotFilePath)) { @@ -134,6 +142,8 @@ export async function expectMoveFormulaSiRowsResultMatchesSnapshot() { // perform undo operation await testBed.api.undo(); + await testBed.api.getFormula().onCalculationResultApplied(); + await new Promise((resolve) => setTimeout(resolve, 500)); // compare the result with the snapshot const resultSnapshot_undo = workbook.save(); @@ -174,6 +184,9 @@ export async function expectMoveFormulaCellResultMatchesSnapshot() { toRange, }); + await testBed.api.getFormula().onCalculationResultApplied(); + await new Promise((resolve) => setTimeout(resolve, 500)); + const resultSnapshot = workbook.save(); const snapshotFilePath = path.resolve(snapshotRootDir, `${getTestFilePath()}-result.json`); if (fs.existsSync(snapshotFilePath)) { @@ -188,6 +201,8 @@ export async function expectMoveFormulaCellResultMatchesSnapshot() { // perform undo operation await testBed.api.undo(); + await testBed.api.getFormula().onCalculationResultApplied(); + await new Promise((resolve) => setTimeout(resolve, 500)); // compare the result with the snapshot const resultSnapshot_undo = workbook.save(); diff --git a/tests/formula-integration/src/__testing__/test-remove-rows-of-filter-rows.ts b/tests/formula-integration/src/__testing__/test-remove-rows-of-filter-rows.ts index bc2a618fec..261851afaf 100644 --- a/tests/formula-integration/src/__testing__/test-remove-rows-of-filter-rows.ts +++ b/tests/formula-integration/src/__testing__/test-remove-rows-of-filter-rows.ts @@ -69,6 +69,9 @@ export async function expectRemoveRowsOfFilterRowsResultMatchesSnapshot() { // remove rows 2 to 5, where the 3 to 4 rows are filtered rows worksheet.deleteRows(1, 4); + await testBed.api.getFormula().onCalculationResultApplied(); + await new Promise((resolve) => setTimeout(resolve, 500)); + const resultSnapshot = workbook.save(); const snapshotFilePath = path.resolve(snapshotRootDir, `${getTestFilePath()}-result.json`); if (fs.existsSync(snapshotFilePath)) { @@ -83,6 +86,8 @@ export async function expectRemoveRowsOfFilterRowsResultMatchesSnapshot() { // perform undo operation await testBed.api.undo(); + await testBed.api.getFormula().onCalculationResultApplied(); + await new Promise((resolve) => setTimeout(resolve, 500)); // compare the result with the snapshot const resultSnapshot_undo = workbook.save(); diff --git a/tests/formula-integration/src/__testing__/util.ts b/tests/formula-integration/src/__testing__/util.ts index 84cb27f940..f69626202a 100644 --- a/tests/formula-integration/src/__testing__/util.ts +++ b/tests/formula-integration/src/__testing__/util.ts @@ -47,8 +47,8 @@ export async function expectCalculationResultMatchesSnapshot() { const testSnapshot = JSON.parse(testSnapshotRaw) as IWorkbookData; const workbook = testBed.api.createWorkbook(testSnapshot); - const formula = testBed.api.getFormula(); - await formula.onCalculationEnd(); + + await testBed.api.getFormula().onCalculationResultApplied(); const resultSnapshot = workbook.save(); const snapshotFilePath = path.resolve(snapshotRootDir, `${getTestFilePath()}-result.json`);