fix: ignore custom metadata when checking empty cells (#6957)

This commit is contained in:
wpxp123456
2026-05-26 16:10:02 +08:00
committed by GitHub
parent ceaed825d2
commit 79e2c9ff4b
7 changed files with 115 additions and 57 deletions
@@ -102,6 +102,7 @@ describe('Test cell data', () => {
expect(isNullCell({ si: null })).toBeTruthy();
expect(isNullCell({ si: undefined })).toBeTruthy();
expect(isNullCell({ si: '' })).toBeTruthy();
expect(isNullCell({ custom: { key: 'value' } })).toBeTruthy();
expect(isNullCell({ v: 1 })).toBeFalsy();
expect(isNullCell({ p: DOCUMENT_DATA })).toBeFalsy();
+1 -5
View File
@@ -356,7 +356,7 @@ export function isNullCell(cell: Nullable<ICellData>) {
return true;
}
const { v, f, si, p, custom } = cell;
const { v, f, si, p } = cell;
if (!(v == null || (typeof v === 'string' && v.length === 0))) {
return false;
@@ -370,10 +370,6 @@ export function isNullCell(cell: Nullable<ICellData>) {
return false;
}
if (custom != null) {
return false;
}
return true;
}
@@ -1,44 +0,0 @@
/**
* 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 { ICellData, Nullable } from '@univerjs/core';
/**
* Examine if a cell is empty (null) in the formula-related modules. This is not
* interchangable with {@link isNullCell} from the core package, because for
* formulas, "custom" field is meaningless.
*/
export function isNullCellForFormula(cell: Nullable<ICellData>) {
if (cell == null) {
return true;
}
const { v, f, si, p } = cell;
if (!(v == null || (typeof v === 'string' && v.length === 0))) {
return false;
}
if ((f != null && f.length > 0) || (si != null && si.length > 0)) {
return false;
}
if (p != null) {
return false;
}
return true;
}
@@ -17,10 +17,9 @@
import type { ICellData, IRange, Nullable } from '@univerjs/core';
import type { IArrayFormulaRangeType, IRuntimeUnitDataType, IUnitData, IUnitSheetNameMap, IUnitStylesData } from '../../basics/common';
import type { BaseValueObject, IArrayValueObject } from '../value-object/base-value-object';
import { CellValueType, isTextFormat, moveRangeByOffset, ObjectMatrix } from '@univerjs/core';
import { CellValueType, isNullCell, isTextFormat, moveRangeByOffset, ObjectMatrix } from '@univerjs/core';
import { FormulaAstLRU } from '../../basics/cache-lru';
import { ERROR_TYPE_SET, ErrorType } from '../../basics/error-type';
import { isNullCellForFormula } from '../../basics/is-null-cell';
import { ObjectClassType } from '../../basics/object-class-type';
import { getCellValue } from '../utils/cell';
import { getRuntimeFeatureCell } from '../utils/get-runtime-feature-cell';
@@ -173,7 +172,7 @@ export class BaseReferenceObject extends ObjectClassType {
const cell = this.getCellData(r, c)!;
let result: Nullable<boolean> = false;
if (isNullCellForFormula(cell)) {
if (isNullCell(cell)) {
result = callback(null, r, c);
continue;
}
@@ -29,11 +29,10 @@ import type { BaseReferenceObject, FunctionVariantType } from '../engine/referen
import type { ArrayValueObject } from '../engine/value-object/array-value-object';
import type { BaseValueObject } from '../engine/value-object/base-value-object';
import type { StringValueObject } from '../engine/value-object/primitive-object';
import { createIdentifier, Disposable, ObjectMatrix } from '@univerjs/core';
import { createIdentifier, Disposable, isNullCell, ObjectMatrix } from '@univerjs/core';
import { isInDirtyRange } from '../basics/dirty';
import { ErrorType } from '../basics/error-type';
import { CELL_INVERTED_INDEX_CACHE } from '../basics/inverted-index-cache';
import { isNullCellForFormula } from '../basics/is-null-cell';
import { FORMULA_REF_TO_ARRAY_CACHE } from '../engine/reference-object/base-reference-object';
import { getRuntimeFeatureCell } from '../engine/utils/get-runtime-feature-cell';
import { clearNumberFormatTypeCache, clearStringToNumberPatternCache } from '../engine/utils/numfmt-kit';
@@ -853,10 +852,10 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime
const isPreviousCellOfCurrentArrayFormula = this._arrayCellHasData(arrayDataCell) &&
this._isInArrayFormulaRange(previousArrayFormulaRange, r, c) &&
(currentCell == null || this._isSameCellValue(currentCell, arrayDataCell));
const hasRuntimeCell = !isNullCellForFormula(cell);
const hasRuntimeCell = !isNullCell(cell);
const isInOtherArrayFormulaRange = this._isInOtherArrayFormulaRange(formulaUnitId, formulaSheetId, formulaRow, formulaColumn, r, c);
const currentCellBlocks = !isNullCellForFormula(currentCell) && !isPreviousCellOfCurrentArrayFormula;
const featureCellBlocks = !isNullCellForFormula(featureCell);
const currentCellBlocks = !isNullCell(currentCell) && !isPreviousCellOfCurrentArrayFormula;
const featureCellBlocks = !isNullCell(featureCell);
// arrayDataCell may display 0 as {v: null}. Although it is an empty cell, it is considered to have a value.
if (
@@ -91,6 +91,7 @@ const workbookDataFactory = (): IWorkbookData => ({
0: { v: 'A1' },
1: { v: 'very-long-text-for-overflow-path', s: 'style-bg-border' },
2: { v: 'wrapped line text', s: 'style-bg-border' },
4: { s: 'style-bg-border', custom: { key: 'value' } },
},
1: {
1: { v: 'rotate-text', s: 'style-rotate' },
@@ -255,6 +256,9 @@ describe('spreadsheet integration', () => {
}));
expect(skeleton.rowColumnSegment.endRow).toBeGreaterThanOrEqual(0);
expect(skeleton.stylesCache.fontMatrix.getSizeOf()).toBeGreaterThan(0);
expect(skeleton.stylesCache.border?.getValue(0, 4)).toBeTruthy();
expect(skeleton.stylesCache.fontMatrix.getValue(0, 4)).toBeUndefined();
expect(skeleton.overflowCache.getValue(0, 4)).toBeUndefined();
const autoHeights = skeleton.calculateAutoHeightInRange([{ startRow: 0, endRow: 6, startColumn: 0, endColumn: 3, rangeType: RANGE_TYPE.NORMAL }]);
expect(autoHeights.length).toBeGreaterThan(0);
@@ -0,0 +1,103 @@
/**
* 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, Univer } from '@univerjs/core';
import { BorderStyleTypes, CellValueType, ICommandService, IUniverInstanceService, LocaleType, RANGE_TYPE } from '@univerjs/core';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { SetRangeValuesMutation } from '../../mutations/set-range-values.mutation';
import { SetRangeCustomMetadataCommand } from '../set-range-custom-metadata.command';
import { createCommandTestBed } from './create-command-test-bed';
const WORKBOOK_DATA: IWorkbookData = {
id: 'test',
appVersion: '3.0.0-alpha',
locale: LocaleType.ZH_CN,
name: '',
sheetOrder: ['sheet1'],
styles: {},
sheets: {
sheet1: {
id: 'sheet1',
name: 'sheet1',
cellData: {
0: {
2: {
s: {
bd: {
t: { s: BorderStyleTypes.THIN, cl: { rgb: '#000000' } },
r: { s: BorderStyleTypes.THIN, cl: { rgb: '#000000' } },
b: { s: BorderStyleTypes.THIN, cl: { rgb: '#000000' } },
l: { s: BorderStyleTypes.THIN, cl: { rgb: '#000000' } },
},
},
t: CellValueType.STRING,
},
},
},
},
},
};
describe('SetRangeCustomMetadataCommand', () => {
let univer: Univer;
let commandService: ICommandService;
let univerInstanceService: IUniverInstanceService;
beforeEach(() => {
const testBed = createCommandTestBed(WORKBOOK_DATA);
univer = testBed.univer;
commandService = testBed.get(ICommandService);
univerInstanceService = testBed.get(IUniverInstanceService);
commandService.registerCommand(SetRangeCustomMetadataCommand);
commandService.registerCommand(SetRangeValuesMutation);
});
afterEach(() => univer.dispose());
it('preserves existing cell type and border style when setting metadata on a styled empty cell', async () => {
const result = await commandService.executeCommand(SetRangeCustomMetadataCommand.id, {
unitId: 'test',
subUnitId: 'sheet1',
range: {
startRow: 0,
endRow: 0,
startColumn: 2,
endColumn: 2,
rangeType: RANGE_TYPE.NORMAL,
},
customMetadata: {
custom: { key: 'value' },
},
});
const workbook = univerInstanceService.getUniverSheetInstance('test')!;
const cell = workbook.getSheetBySheetId('sheet1')!.getCellRaw(0, 2);
const style = workbook.getStyles().getStyleByCell(cell);
expect(result).toBe(true);
expect(cell?.custom).toEqual({ key: 'value' });
expect(cell?.t).toBe(CellValueType.STRING);
expect(style).toMatchObject({
bd: {
t: { s: BorderStyleTypes.THIN },
r: { s: BorderStyleTypes.THIN },
b: { s: BorderStyleTypes.THIN },
l: { s: BorderStyleTypes.THIN },
},
});
});
});