fix(formula): fixed the issue of formula range reference calculating offset when moving rows, columns or ranges (#5412)

This commit is contained in:
wpxp123456
2025-06-20 11:41:29 +08:00
committed by GitHub
parent 37a3fe0e46
commit 563b215bf5
11 changed files with 937 additions and 39 deletions
@@ -379,6 +379,57 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({
},
},
},
sheet6: {
id: 'sheet6',
name: 'Sheet6',
cellData: {
0: {
0: {
v: 1,
t: 2,
},
1: {
f: '=A1',
v: 1,
t: 2,
},
},
1: {
0: {
v: 2,
t: 2,
},
1: {
f: '=A2',
si: 'W8Hdfc',
v: 2,
t: 2,
},
},
2: {
0: {
v: 3,
t: 2,
},
1: {
si: 'W8Hdfc',
v: 3,
t: 2,
},
},
3: {
0: {
v: 4,
t: 2,
},
1: {
si: 'W8Hdfc',
v: 4,
t: 2,
},
},
},
},
},
locale: LocaleType.ZH_CN,
name: '',
@@ -557,6 +608,235 @@ describe('Test update formula ', () => {
expect(valuesRedo).toStrictEqual([[null, { f: '=SUM(A1:B2)' }]]);
});
it('Move range, update reference, release si', async () => {
const workbook = get(IUniverInstanceService).getUnit<Workbook>('test');
const sheetId = 'sheet6';
const sheet6 = workbook?.getSheetBySheetId(sheetId);
if (!sheet6) {
throw new Error(`${sheetId}not found`);
}
workbook?.setActiveSheet(sheet6);
const params: IMoveRangeCommandParams = {
fromRange: {
startRow: 0,
startColumn: 1,
endRow: 3,
endColumn: 1,
rangeType: 0,
},
toRange: {
startRow: 2,
startColumn: 1,
endRow: 5,
endColumn: 1,
rangeType: 0,
},
};
expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy();
const values = getValues(0, 1, 5, 1, sheetId);
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 }],
]);
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 }],
[null],
[null],
]);
expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy();
const valuesRedo = getValues(0, 1, 5, 1, sheetId);
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 }],
]);
});
it('Move range with f/si', async () => {
const workbook = get(IUniverInstanceService).getUnit<Workbook>('test');
const sheetId = 'sheet6';
const sheet6 = workbook?.getSheetBySheetId(sheetId);
if (!sheet6) {
throw new Error(`${sheetId}not found`);
}
workbook?.setActiveSheet(sheet6);
const params: IMoveRangeCommandParams = {
fromRange: {
startRow: 1,
startColumn: 1,
endRow: 1,
endColumn: 1,
rangeType: 0,
},
toRange: {
startRow: 1,
startColumn: 3,
endRow: 1,
endColumn: 3,
rangeType: 0,
},
};
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 }]]);
const values2 = getValues(1, 1, 3, 1, sheetId);
expect(values2).toStrictEqual([
[null],
[{ f: '=A3', t: 2, v: 3 }],
[{ f: '=A4', t: 2, v: 4 }],
]);
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
const valuesUndo1 = getValues(1, 3, 1, 3, sheetId);
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 }],
]);
expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy();
const valuesRedo1 = getValues(1, 3, 1, 3, sheetId);
expect(valuesRedo1).toStrictEqual([[{ f: '=A2', t: 2, v: 2 }]]);
const valuesRedo2 = getValues(1, 1, 3, 1, sheetId);
expect(valuesRedo2).toStrictEqual([
[null],
[{ f: '=A3', t: 2, v: 3 }],
[{ f: '=A4', t: 2, v: 4 }],
]);
});
it('Move range with si only', async () => {
const workbook = get(IUniverInstanceService).getUnit<Workbook>('test');
const sheetId = 'sheet6';
const sheet6 = workbook?.getSheetBySheetId(sheetId);
if (!sheet6) {
throw new Error(`${sheetId}not found`);
}
workbook?.setActiveSheet(sheet6);
const params: IMoveRangeCommandParams = {
fromRange: {
startRow: 2,
startColumn: 1,
endRow: 2,
endColumn: 1,
rangeType: 0,
},
toRange: {
startRow: 2,
startColumn: 3,
endRow: 2,
endColumn: 3,
rangeType: 0,
},
};
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 }]]);
const values2 = getValues(1, 1, 3, 1, sheetId);
expect(values2).toStrictEqual([
[{ f: '=A2', t: 2, v: 2 }],
[null],
[{ f: '=A4', t: 2, v: 4 }],
]);
expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy();
const valuesUndo1 = getValues(2, 3, 2, 3, sheetId);
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 }],
]);
expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy();
const valuesRedo1 = getValues(2, 3, 2, 3, sheetId);
expect(valuesRedo1).toStrictEqual([[{ f: '=A3', t: 2, v: 3 }]]);
const valuesRedo2 = getValues(1, 1, 3, 1, sheetId);
expect(valuesRedo2).toStrictEqual([
[{ f: '=A2', t: 2, v: 2 }],
[null],
[{ f: '=A4', t: 2, v: 4 }],
]);
});
it('Move range with f/si and si', async () => {
const workbook = get(IUniverInstanceService).getUnit<Workbook>('test');
const sheetId = 'sheet6';
const sheet6 = workbook?.getSheetBySheetId(sheetId);
if (!sheet6) {
throw new Error(`${sheetId}not found`);
}
workbook?.setActiveSheet(sheet6);
const params: IMoveRangeCommandParams = {
fromRange: {
startRow: 1,
startColumn: 1,
endRow: 2,
endColumn: 1,
rangeType: 0,
},
toRange: {
startRow: 1,
startColumn: 3,
endRow: 2,
endColumn: 3,
rangeType: 0,
},
};
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 }]]);
const values2 = getValues(1, 1, 3, 1, sheetId);
expect(values2).toStrictEqual([
[null],
[null],
[{ f: '=A4', t: 2, v: 4 }],
]);
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 }],
]);
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 }]]);
const valuesRedo2 = getValues(1, 1, 3, 1, sheetId);
expect(valuesRedo2).toStrictEqual([
[null],
[null],
[{ f: '=A4', t: 2, v: 4 }],
]);
});
it('Move range, update reference with si ', async () => {
const params: IMoveRangeCommandParams = {
fromRange: {
@@ -577,15 +857,27 @@ 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)', si: 'id1', t: 2, v: 2 }], [null, { si: 'id1', t: 2, v: 3 }]]);
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 }],
]);
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]]);
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],
]);
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)', si: 'id1', t: 2, v: 2 }], [null, { si: 'id1', t: 2, v: 3 }]]);
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 }],
]);
});
it('Move rows, update reference', async () => {
@@ -514,6 +514,10 @@ export class UpdateFormulaController extends Disposable {
shouldModify = true;
refChangeIds.push(i);
// newRefString = ErrorType.REF;
} else if (type === FormulaReferenceMoveType.MoveRange && si) {
// If the operation is a move range and the formula has si, unpack the si to f.
// This is to ensure that the si formula can be recalculated correctly after the move.
shouldModify = true;
}
}
@@ -17,7 +17,7 @@
import type { ICellData, IMutationInfo, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core';
import type { IFormulaData, IFormulaDataItem, IRangeChange, ISequenceNode } from '@univerjs/engine-formula';
import type { ISetRangeValuesMutationParams } from '@univerjs/sheets';
import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix, Rectangle, Tools } from '@univerjs/core';
import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix, Rectangle } from '@univerjs/core';
import { deserializeRangeWithSheetWithCache, sequenceNodeType, serializeRangeToRefString } from '@univerjs/engine-formula';
import { EffectRefRangId, handleDeleteRangeMoveLeft, handleDeleteRangeMoveUp, handleInsertCol, handleInsertRangeMoveDown, handleInsertRangeMoveRight, handleInsertRow, handleIRemoveCol, handleIRemoveRow, handleMoveCols, handleMoveRange, handleMoveRows, runRefRangeMutations, SetRangeValuesMutation } from '@univerjs/sheets';
import { checkFormulaDataNull } from './offset-formula-data';
@@ -156,14 +156,6 @@ export function getFormulaReferenceSheet(oldFormulaData: IFormulaData, newFormul
export function getFormulaReferenceRange(oldFormulaData: IFormulaData, newFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) {
const { redoFormulaData, undoFormulaData } = refRangeFormula(oldFormulaData, newFormulaData, formulaReferenceMoveParam);
// If the formula data is the same, no operation is required
if (Tools.diffValue(redoFormulaData, undoFormulaData)) {
return {
undos: [],
redos: [],
};
}
const redos: IMutationInfo[] = [];
const undos: IMutationInfo[] = [];
@@ -528,22 +520,22 @@ function handleRefInsertMoveRight(range: IRange, oldCell: IRange) {
function getRedoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectMatrix<Nullable<IFormulaDataItem>>, newFormulaMatrix: ObjectMatrix<Nullable<IFormulaDataItem>>) {
const redoFormulaData = new ObjectMatrix<Nullable<ICellData>>({});
rangeList.forEach((item) => {
const { oldCell, newCell } = item;
for (let i = 0; i < rangeList.length; i++) {
const { oldCell, newCell } = rangeList[i];
const { startRow: oldStartRow, startColumn: oldStartColumn } = oldCell;
const newFormula = newFormulaMatrix.getValue(oldStartRow, oldStartColumn) || oldFormulaMatrix.getValue(oldStartRow, oldStartColumn);
// Use the formula result value to update the data to ensure accuracy, otherwise the new formula cannot be inferred from #REF
const newValue = formulaDataItemToCellData(newFormula);
redoFormulaData.setValue(oldStartRow, oldStartColumn, { f: null, si: null });
// Clear the old position formula data, if the position of redoFormulaData has set a new formula, no additional processing is required
if (!(redoFormulaData.getValue(oldCell.startRow, oldCell.startColumn)?.f || redoFormulaData.getValue(oldCell.startRow, oldCell.startColumn)?.si)) {
redoFormulaData.setValue(oldCell.startRow, oldCell.startColumn, { f: null, si: null });
}
if (newCell) {
const { startRow: newStartRow, startColumn: newStartColumn } = newCell;
redoFormulaData.setValue(newStartRow, newStartColumn, newValue);
// Save the new position formula data to redoFormulaData
// newFormulaMatrix.getValue(oldCell.startRow, oldCell.startColumn) may be null, it means that the formula is not offset, so use the old value
const newFormula = newFormulaMatrix.getValue(oldCell.startRow, oldCell.startColumn) ?? oldFormulaMatrix.getValue(oldCell.startRow, oldCell.startColumn);
const newValue = formulaDataItemToCellData(newFormula);
redoFormulaData.setValue(newCell.startRow, newCell.startColumn, newValue);
}
});
}
return redoFormulaData.getMatrix();
}
@@ -557,22 +549,22 @@ function getRedoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM
function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectMatrix<Nullable<IFormulaDataItem>>) {
const undoFormulaData = new ObjectMatrix<Nullable<ICellData>>({});
// Maintaining the correct assignment order prevents overwriting data
rangeList.reverse().forEach((item) => {
const { oldCell, newCell } = item;
for (let i = rangeList.length - 1; i >= 0; i--) {
const { oldCell, newCell } = rangeList[i];
const { startRow: oldStartRow, startColumn: oldStartColumn } = oldCell;
const oldFormula = oldFormulaMatrix.getValue(oldStartRow, oldStartColumn);
const oldValue = formulaDataItemToCellData(oldFormula);
// Save old position old formula data to undoFormulaData
const oldCellOldFormula = oldFormulaMatrix.getValue(oldCell.startRow, oldCell.startColumn);
const oldCellOldValue = formulaDataItemToCellData(oldCellOldFormula);
undoFormulaData.setValue(oldCell.startRow, oldCell.startColumn, oldCellOldValue);
if (newCell) {
const { startRow: newStartRow, startColumn: newStartColumn } = newCell;
undoFormulaData.setValue(newStartRow, newStartColumn, { f: null, si: null });
// If the newCell is not null, save new position old formula data to undoFormulaData.
// If the new position old formula data is null, clear the new position formula data
const newCellOldFormula = oldFormulaMatrix.getValue(newCell.startRow, newCell.startColumn);
const newCellOldValue = formulaDataItemToCellData(newCellOldFormula);
undoFormulaData.setValue(newCell.startRow, newCell.startColumn, newCellOldValue ?? { f: null, si: null });
}
undoFormulaData.setValue(oldStartRow, oldStartColumn, oldValue);
});
}
return undoFormulaData.getMatrix();
}
@@ -119,7 +119,10 @@ export function getNewRangeByMoveParam(
if (
remainRange == null &&
((from.endRow < sequenceRange.startRow && to.endRow < sequenceRange.startRow) || (from.startRow > sequenceRange.endRow && to.startRow > sequenceRange.endRow))
(
(from.endRow < sequenceRange.startRow && to.endRow <= sequenceRange.startRow) ||
(from.startRow > sequenceRange.endRow && to.startRow > sequenceRange.endRow)
)
) {
return;
}
@@ -157,7 +160,10 @@ export function getNewRangeByMoveParam(
if (
remainRange == null &&
((from.endColumn < sequenceRange.startColumn && to.endColumn < sequenceRange.startColumn) || (from.startColumn > sequenceRange.endColumn && to.startColumn > sequenceRange.endColumn))
(
(from.endColumn < sequenceRange.startColumn && to.endColumn <= sequenceRange.startColumn) ||
(from.startColumn > sequenceRange.endColumn && to.startColumn > sequenceRange.endColumn)
)
) {
return;
}
@@ -0,0 +1,109 @@
{
"id": "Czx9Y5",
"sheetOrder": [
"lXnRfcI9sd18sz0D_6OmT"
],
"name": "",
"appVersion": "0.8.1",
"locale": "zhCN",
"styles": {},
"sheets": {
"lXnRfcI9sd18sz0D_6OmT": {
"id": "lXnRfcI9sd18sz0D_6OmT",
"name": "Sheet1",
"tabColor": "",
"hidden": 0,
"rowCount": 1000,
"columnCount": 20,
"zoomRatio": 1,
"freeze": {
"xSplit": 0,
"ySplit": 0,
"startRow": -1,
"startColumn": -1
},
"scrollTop": 0,
"scrollLeft": 0,
"defaultColumnWidth": 88,
"defaultRowHeight": 24,
"mergeData": [],
"cellData": {
"3": {
"2": {
"v": 1,
"t": 2
}
},
"4": {
"2": {
"v": 2,
"t": 2
}
},
"5": {
"2": {
"v": 3,
"t": 2
},
"3": {
"f": "=C6",
"v": 3,
"t": 2
}
},
"12": {
"6": {
"f": "=C4",
"v": 1,
"t": 2
}
},
"13": {
"6": {
"f": "=C5",
"v": 2,
"t": 2
}
}
},
"rowData": {},
"columnData": {},
"showGridlines": 1,
"rowHeader": {
"width": 46,
"hidden": 0
},
"columnHeader": {
"height": 20,
"hidden": 0
},
"rightToLeft": 0
}
},
"resources": [
{
"name": "SHEET_RANGE_PROTECTION_PLUGIN",
"data": ""
},
{
"name": "SHEET_AuthzIoMockService_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_POINT_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_DEFINED_NAME_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_RANGE_THEME_MODEL_PLUGIN",
"data": "{}"
}
]
}
@@ -0,0 +1,105 @@
{
"id": "Czx9Y5",
"sheetOrder": [
"lXnRfcI9sd18sz0D_6OmT"
],
"name": "",
"appVersion": "0.8.1",
"locale": "zhCN",
"styles": {},
"sheets": {
"lXnRfcI9sd18sz0D_6OmT": {
"id": "lXnRfcI9sd18sz0D_6OmT",
"name": "Sheet1",
"tabColor": "",
"hidden": 0,
"rowCount": 1000,
"columnCount": 20,
"zoomRatio": 1,
"freeze": {
"xSplit": 0,
"ySplit": 0,
"startRow": -1,
"startColumn": -1
},
"scrollTop": 0,
"scrollLeft": 0,
"defaultColumnWidth": 88,
"defaultRowHeight": 24,
"mergeData": [],
"cellData": {
"3": {
"2": {
"v": 1,
"t": 2
},
"3": {
"f": "=C4",
"v": 1,
"t": 2
}
},
"4": {
"2": {
"v": 2,
"t": 2
},
"3": {
"f": "=C5",
"v": 2,
"t": 2
}
},
"5": {
"2": {
"v": 3,
"t": 2
},
"3": {
"f": "=C6",
"v": 3,
"t": 2
}
}
},
"rowData": {},
"columnData": {},
"showGridlines": 1,
"rowHeader": {
"width": 46,
"hidden": 0
},
"columnHeader": {
"height": 20,
"hidden": 0
},
"rightToLeft": 0
}
},
"resources": [
{
"name": "SHEET_RANGE_PROTECTION_PLUGIN",
"data": ""
},
{
"name": "SHEET_AuthzIoMockService_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_POINT_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_DEFINED_NAME_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_RANGE_THEME_MODEL_PLUGIN",
"data": "{}"
}
]
}
@@ -0,0 +1,105 @@
{
"id": "wcKMFW",
"sheetOrder": [
"XkNPQsSrdwlQrVOFgt9FM"
],
"name": "",
"appVersion": "0.8.1",
"locale": "zhCN",
"styles": {},
"sheets": {
"XkNPQsSrdwlQrVOFgt9FM": {
"id": "XkNPQsSrdwlQrVOFgt9FM",
"name": "Sheet1",
"tabColor": "",
"hidden": 0,
"rowCount": 1000,
"columnCount": 20,
"zoomRatio": 1,
"freeze": {
"xSplit": 0,
"ySplit": 0,
"startRow": -1,
"startColumn": -1
},
"scrollTop": 0,
"scrollLeft": 0,
"defaultColumnWidth": 88,
"defaultRowHeight": 24,
"mergeData": [],
"cellData": {
"2": {
"2": {
"v": 2,
"t": 2
},
"3": {
"f": "=C3",
"v": 2,
"t": 2
}
},
"3": {
"2": {
"v": 1,
"t": 2
},
"3": {
"f": "=C4",
"v": 1,
"t": 2
}
},
"4": {
"2": {
"v": 3,
"t": 2
},
"3": {
"f": "=C5",
"v": 3,
"t": 2
}
}
},
"rowData": {},
"columnData": {},
"showGridlines": 1,
"rowHeader": {
"width": 46,
"hidden": 0
},
"columnHeader": {
"height": 20,
"hidden": 0
},
"rightToLeft": 0
}
},
"resources": [
{
"name": "SHEET_RANGE_PROTECTION_PLUGIN",
"data": ""
},
{
"name": "SHEET_AuthzIoMockService_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_POINT_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_DEFINED_NAME_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_RANGE_THEME_MODEL_PLUGIN",
"data": "{}"
}
]
}
@@ -0,0 +1,105 @@
{
"id": "wcKMFW",
"sheetOrder": [
"XkNPQsSrdwlQrVOFgt9FM"
],
"name": "",
"appVersion": "0.8.1",
"locale": "zhCN",
"styles": {},
"sheets": {
"XkNPQsSrdwlQrVOFgt9FM": {
"id": "XkNPQsSrdwlQrVOFgt9FM",
"name": "Sheet1",
"tabColor": "",
"hidden": 0,
"rowCount": 1000,
"columnCount": 20,
"zoomRatio": 1,
"freeze": {
"xSplit": 0,
"ySplit": 0,
"startRow": -1,
"startColumn": -1
},
"scrollTop": 0,
"scrollLeft": 0,
"defaultColumnWidth": 88,
"defaultRowHeight": 24,
"mergeData": [],
"cellData": {
"2": {
"2": {
"v": 1,
"t": 2
},
"3": {
"f": "=C3",
"v": 1,
"t": 2
}
},
"3": {
"2": {
"v": 2,
"t": 2
},
"3": {
"f": "=C4",
"v": 2,
"t": 2
}
},
"4": {
"2": {
"v": 3,
"t": 2
},
"3": {
"f": "=C5",
"v": 3,
"t": 2
}
}
},
"rowData": {},
"columnData": {},
"showGridlines": 1,
"rowHeader": {
"width": 46,
"hidden": 0
},
"columnHeader": {
"height": 20,
"hidden": 0
},
"rightToLeft": 0
}
},
"resources": [
{
"name": "SHEET_RANGE_PROTECTION_PLUGIN",
"data": ""
},
{
"name": "SHEET_AuthzIoMockService_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_WORKSHEET_PROTECTION_POINT_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_DEFINED_NAME_PLUGIN",
"data": "{}"
},
{
"name": "SHEET_RANGE_THEME_MODEL_PLUGIN",
"data": "{}"
}
]
}
@@ -0,0 +1,152 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IWorkbookData } from '@univerjs/core';
import fs from 'node:fs';
import path from 'node:path';
import { IUniverInstanceService, LocaleType, Univer } from '@univerjs/core';
import { FUniver } from '@univerjs/core/facade';
import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula';
import zhCN from '@univerjs/mockdata/locales/zh-CN';
import { MoveRangeCommand, UniverSheetsPlugin } from '@univerjs/sheets';
import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula';
import { expect } from 'vitest';
import { getTestFilePath, getTestName } from './util';
function createTestBed() {
const univer = new Univer({
locale: LocaleType.ZH_CN,
locales: {
[LocaleType.ZH_CN]: zhCN,
},
});
univer.registerPlugin(UniverFormulaEnginePlugin);
univer.registerPlugin(UniverSheetsPlugin);
univer.registerPlugin(UniverSheetsFormulaPlugin);
const injector = univer.__getInjector();
return {
univer,
get: injector.get.bind(injector),
api: FUniver.newAPI(univer),
};
}
export async function expectMoveFormulaRowsResultMatchesSnapshot() {
const testBed = createTestBed();
const snapshotRootDir = path.join(import.meta.dirname, '../__snapshots__');
const testSnapshotPath = path.resolve(snapshotRootDir, `${getTestFilePath()}.json`);
if (!fs.existsSync(testSnapshotPath)) {
throw new Error(`Cannot find snapshot file for test "${getTestName()}".`);
}
const testSnapshotRaw = fs.readFileSync(testSnapshotPath, 'utf-8');
const testSnapshot = JSON.parse(testSnapshotRaw) as IWorkbookData;
const workbook = testBed.api.createWorkbook(testSnapshot);
const univerInstanceService = testBed.get(IUniverInstanceService);
univerInstanceService.focusUnit(workbook.getId());
const worksheet = workbook.getActiveSheet();
// move row 3 to before row 5
const rowSpec = worksheet.getRange('3:3');
worksheet.moveRows(rowSpec, 4);
const resultSnapshot = workbook.save();
const snapshotFilePath = path.resolve(snapshotRootDir, `${getTestFilePath()}-result.json`);
if (fs.existsSync(snapshotFilePath)) {
const resultSnapshotFileString = fs.readFileSync(snapshotFilePath, 'utf-8');
expect(resultSnapshot).toMatchObject(JSON.parse(resultSnapshotFileString));
} else {
fs.writeFileSync(snapshotFilePath, JSON.stringify(resultSnapshot, null, 4));
// eslint-disable-next-line no-console
console.log(`Snapshot file created at: ${snapshotFilePath}`);
}
// perform undo operation
await testBed.api.undo();
// compare the result with the snapshot
const resultSnapshot_undo = workbook.save();
const snapshotFilePath_undo = path.resolve(snapshotRootDir, `${getTestFilePath()}.json`);
if (fs.existsSync(snapshotFilePath_undo)) {
const resultSnapshotFileString = fs.readFileSync(snapshotFilePath_undo, 'utf-8');
expect(resultSnapshot_undo).toMatchObject(JSON.parse(resultSnapshotFileString));
} else {
fs.writeFileSync(snapshotFilePath_undo, JSON.stringify(resultSnapshot_undo, null, 4));
// eslint-disable-next-line no-console
console.log(`Snapshot file created at: ${snapshotFilePath_undo}`);
}
}
export async function expectMoveFormulaCellResultMatchesSnapshot() {
const testBed = createTestBed();
const snapshotRootDir = path.join(import.meta.dirname, '../__snapshots__');
const testSnapshotPath = path.resolve(snapshotRootDir, `${getTestFilePath()}.json`);
if (!fs.existsSync(testSnapshotPath)) {
throw new Error(`Cannot find snapshot file for test "${getTestName()}".`);
}
const testSnapshotRaw = fs.readFileSync(testSnapshotPath, 'utf-8');
const testSnapshot = JSON.parse(testSnapshotRaw) as IWorkbookData;
const workbook = testBed.api.createWorkbook(testSnapshot);
const univerInstanceService = testBed.get(IUniverInstanceService);
univerInstanceService.focusUnit(workbook.getId());
const worksheet = workbook.getActiveSheet();
// move D4:D5 to G13:G14
const fromRange = worksheet.getRange('D4:D5').getRange();
const toRange = worksheet.getRange('G13:G14').getRange();
await testBed.api.executeCommand(MoveRangeCommand.id, {
fromRange,
toRange,
});
const resultSnapshot = workbook.save();
const snapshotFilePath = path.resolve(snapshotRootDir, `${getTestFilePath()}-result.json`);
if (fs.existsSync(snapshotFilePath)) {
const resultSnapshotFileString = fs.readFileSync(snapshotFilePath, 'utf-8');
expect(resultSnapshot).toMatchObject(JSON.parse(resultSnapshotFileString));
} else {
fs.writeFileSync(snapshotFilePath, JSON.stringify(resultSnapshot, null, 4));
// eslint-disable-next-line no-console
console.log(`Snapshot file created at: ${snapshotFilePath}`);
}
// perform undo operation
await testBed.api.undo();
// compare the result with the snapshot
const resultSnapshot_undo = workbook.save();
const snapshotFilePath_undo = path.resolve(snapshotRootDir, `${getTestFilePath()}.json`);
if (fs.existsSync(snapshotFilePath_undo)) {
const resultSnapshotFileString = fs.readFileSync(snapshotFilePath_undo, 'utf-8');
expect(resultSnapshot_undo).toMatchObject(JSON.parse(resultSnapshotFileString));
} else {
fs.writeFileSync(snapshotFilePath_undo, JSON.stringify(resultSnapshot_undo, null, 4));
// eslint-disable-next-line no-console
console.log(`Snapshot file created at: ${snapshotFilePath_undo}`);
}
}
@@ -63,7 +63,7 @@ export async function expectRemoveRowsOfFilterRowsResultMatchesSnapshot() {
const workbook = testBed.api.createWorkbook(testSnapshot);
const univerInstanceService = testBed.get(IUniverInstanceService);
univerInstanceService.focusUnit('YoRIim');
univerInstanceService.focusUnit(workbook.getId());
const worksheet = workbook.getActiveSheet();
// remove rows 2 to 5, where the 3 to 4 rows are filtered rows
@@ -0,0 +1,28 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it } from 'vitest';
import { expectMoveFormulaCellResultMatchesSnapshot, expectMoveFormulaRowsResultMatchesSnapshot } from '../__testing__/test-formula-move';
describe('Test formula move', () => {
it('move formula rows', () => {
expectMoveFormulaRowsResultMatchesSnapshot();
});
it('move formula cell', () => {
expectMoveFormulaCellResultMatchesSnapshot();
});
});