@veltra/sheet 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/address.d.ts +60 -0
- package/dist/core/address.js +142 -0
- package/dist/core/address.js.map +1 -0
- package/dist/core/cell-store.d.ts +61 -0
- package/dist/core/cell-store.js +135 -0
- package/dist/core/cell-store.js.map +1 -0
- package/dist/core/command/default-registry.d.ts +10 -0
- package/dist/core/command/default-registry.js +18 -0
- package/dist/core/command/default-registry.js.map +1 -0
- package/dist/core/command/history.d.ts +64 -0
- package/dist/core/command/history.js +127 -0
- package/dist/core/command/history.js.map +1 -0
- package/dist/core/command/merge-cells.d.ts +21 -0
- package/dist/core/command/merge-cells.js +74 -0
- package/dist/core/command/merge-cells.js.map +1 -0
- package/dist/core/command/registry.d.ts +17 -0
- package/dist/core/command/registry.js +26 -0
- package/dist/core/command/registry.js.map +1 -0
- package/dist/core/command/set-cell-formula.d.ts +17 -0
- package/dist/core/command/set-cell-formula.js +29 -0
- package/dist/core/command/set-cell-formula.js.map +1 -0
- package/dist/core/command/set-cell-value.d.ts +22 -0
- package/dist/core/command/set-cell-value.js +35 -0
- package/dist/core/command/set-cell-value.js.map +1 -0
- package/dist/core/command/types.d.ts +68 -0
- package/dist/core/events.js +29 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/formula/ast.d.ts +58 -0
- package/dist/core/formula/ast.js +38 -0
- package/dist/core/formula/ast.js.map +1 -0
- package/dist/core/formula/dependency-graph.d.ts +70 -0
- package/dist/core/formula/dependency-graph.js +269 -0
- package/dist/core/formula/dependency-graph.js.map +1 -0
- package/dist/core/formula/errors.d.ts +25 -0
- package/dist/core/formula/errors.js +36 -0
- package/dist/core/formula/errors.js.map +1 -0
- package/dist/core/formula/evaluator.d.ts +41 -0
- package/dist/core/formula/evaluator.js +154 -0
- package/dist/core/formula/evaluator.js.map +1 -0
- package/dist/core/formula/functions.d.ts +30 -0
- package/dist/core/formula/functions.js +209 -0
- package/dist/core/formula/functions.js.map +1 -0
- package/dist/core/formula/parser.d.ts +7 -0
- package/dist/core/formula/parser.js +208 -0
- package/dist/core/formula/parser.js.map +1 -0
- package/dist/core/formula/tokenizer.d.ts +37 -0
- package/dist/core/formula/tokenizer.js +146 -0
- package/dist/core/formula/tokenizer.js.map +1 -0
- package/dist/core/merge-manager.d.ts +64 -0
- package/dist/core/merge-manager.js +111 -0
- package/dist/core/merge-manager.js.map +1 -0
- package/dist/core/selection.d.ts +31 -0
- package/dist/core/selection.js +62 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/sheet.d.ts +105 -0
- package/dist/core/sheet.js +202 -0
- package/dist/core/sheet.js.map +1 -0
- package/dist/core/workbook.d.ts +43 -0
- package/dist/core/workbook.js +77 -0
- package/dist/core/workbook.js.map +1 -0
- package/dist/grid/sheet-grid.d.ts +59 -0
- package/dist/grid/sheet-grid.js +230 -0
- package/dist/grid/sheet-grid.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +25 -0
- package/dist/tools/builtin.js +72 -0
- package/dist/tools/builtin.js.map +1 -0
- package/dist/tools/context.d.ts +57 -0
- package/dist/tools/context.js +43 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/registry.d.ts +68 -0
- package/dist/tools/registry.js +77 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/sheet.d.ts +43 -0
- package/dist/vue/index.d.ts +2 -0
- package/dist/vue/sheet.js +185 -0
- package/dist/vue/sheet.js.map +1 -0
- package/dist/vue/sheet.vue.d.ts +24 -0
- package/dist/vue/style.d.ts +1 -0
- package/dist/vue/style.js +1 -0
- package/dist/vue/style2.css +93 -0
- package/package.json +37 -0
- package/src/core/__test__/address.test.ts +113 -0
- package/src/core/__test__/cell-store.test.ts +97 -0
- package/src/core/__test__/command.test.ts +377 -0
- package/src/core/__test__/formula-adversarial.test.ts +265 -0
- package/src/core/__test__/formula-engine.test.ts +470 -0
- package/src/core/__test__/formula-functions.test.ts +178 -0
- package/src/core/__test__/formula-parser.test.ts +160 -0
- package/src/core/__test__/history.test.ts +225 -0
- package/src/core/__test__/merge-manager.test.ts +106 -0
- package/src/core/__test__/sheet.test.ts +142 -0
- package/src/core/address.ts +166 -0
- package/src/core/cell-store.ts +173 -0
- package/src/core/command/default-registry.ts +15 -0
- package/src/core/command/history.ts +167 -0
- package/src/core/command/index.ts +7 -0
- package/src/core/command/merge-cells.ts +74 -0
- package/src/core/command/registry.ts +30 -0
- package/src/core/command/set-cell-formula.ts +29 -0
- package/src/core/command/set-cell-value.ts +38 -0
- package/src/core/command/types.ts +73 -0
- package/src/core/events.ts +37 -0
- package/src/core/formula/ast.ts +66 -0
- package/src/core/formula/dependency-graph.ts +329 -0
- package/src/core/formula/errors.ts +41 -0
- package/src/core/formula/evaluator.ts +219 -0
- package/src/core/formula/functions.ts +266 -0
- package/src/core/formula/index.ts +7 -0
- package/src/core/formula/parser.ts +198 -0
- package/src/core/formula/tokenizer.ts +160 -0
- package/src/core/merge-manager.ts +158 -0
- package/src/core/selection.ts +66 -0
- package/src/core/sheet.ts +257 -0
- package/src/core/workbook.ts +99 -0
- package/src/env.d.ts +7 -0
- package/src/grid/__test__/canvas-mock.ts +49 -0
- package/src/grid/__test__/setup.ts +3 -0
- package/src/grid/__test__/sheet-grid-formula.test.ts +140 -0
- package/src/grid/__test__/sheet-grid.test.ts +177 -0
- package/src/grid/sheet-grid.ts +283 -0
- package/src/index.ts +16 -0
- package/src/tools/__test__/builtin.test.ts +128 -0
- package/src/tools/__test__/context.test.ts +157 -0
- package/src/tools/__test__/registry.test.ts +102 -0
- package/src/tools/builtin.ts +75 -0
- package/src/tools/context.ts +106 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/registry.ts +129 -0
- package/src/types/index.ts +1 -0
- package/src/types/sheet.ts +41 -0
- package/src/vue/__test__/sheet-component.test.ts +267 -0
- package/src/vue/index.ts +1 -0
- package/src/vue/sheet.vue +195 -0
- package/src/vue/style.scss +94 -0
- package/src/vue/style.ts +1 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { parseRange, type CellAddress } from '../address'
|
|
4
|
+
import { Sheet } from '../sheet'
|
|
5
|
+
import { Workbook } from '../workbook'
|
|
6
|
+
|
|
7
|
+
const A1 = { row: 0, col: 0 }
|
|
8
|
+
const B1 = { row: 0, col: 1 }
|
|
9
|
+
const C1 = { row: 0, col: 2 }
|
|
10
|
+
const D1 = { row: 0, col: 3 }
|
|
11
|
+
|
|
12
|
+
describe('依赖重算', () => {
|
|
13
|
+
it('改 A1 → 依赖它的 =A1*2 重算', () => {
|
|
14
|
+
const sheet = new Sheet()
|
|
15
|
+
sheet.setCellValue(A1, 3)
|
|
16
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
17
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 6 })
|
|
18
|
+
|
|
19
|
+
sheet.setCellValue(A1, 10)
|
|
20
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 20 })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('间接依赖按拓扑序重算:A1 → B1(=A1*2) → C1(=B1+1)', () => {
|
|
24
|
+
const sheet = new Sheet()
|
|
25
|
+
sheet.setCellValue(A1, 1)
|
|
26
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
27
|
+
sheet.setCellFormula(C1, '=B1+1')
|
|
28
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 3 })
|
|
29
|
+
|
|
30
|
+
sheet.setCellValue(A1, 5)
|
|
31
|
+
// 若顺序错误(C1 先于 B1 重算)会拿到旧 B1 的 2+1=3
|
|
32
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 10 })
|
|
33
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 11 })
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('菱形依赖(D1=B1+C1,B1/C1 同依赖 A1)只算一次且结果正确', () => {
|
|
37
|
+
const sheet = new Sheet()
|
|
38
|
+
sheet.setCellValue(A1, 2)
|
|
39
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
40
|
+
sheet.setCellFormula(C1, '=A1*3')
|
|
41
|
+
sheet.setCellFormula(D1, '=B1+C1')
|
|
42
|
+
expect(sheet.getCellData(D1)).toMatchObject({ v: 10 })
|
|
43
|
+
|
|
44
|
+
sheet.setCellValue(A1, 4)
|
|
45
|
+
expect(sheet.getCellData(D1)).toMatchObject({ v: 20 })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('连续改值,每次重算结果一致', () => {
|
|
49
|
+
const sheet = new Sheet()
|
|
50
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
51
|
+
sheet.setCellFormula(C1, '=B1+1')
|
|
52
|
+
for (const [input, b, c] of [
|
|
53
|
+
[1, 2, 3],
|
|
54
|
+
[2, 4, 5],
|
|
55
|
+
[3, 6, 7],
|
|
56
|
+
[2, 4, 5],
|
|
57
|
+
[0, 0, 1]
|
|
58
|
+
] as const) {
|
|
59
|
+
sheet.setCellValue(A1, input)
|
|
60
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: b })
|
|
61
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: c })
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('重算收敛时值未变的格不产生派生变更(不触发 cell-change)', () => {
|
|
66
|
+
const sheet = new Sheet()
|
|
67
|
+
sheet.setCellFormula(B1, '=A1*0+1')
|
|
68
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 1 })
|
|
69
|
+
|
|
70
|
+
const handler = vi.fn()
|
|
71
|
+
sheet.on('cell-change', handler)
|
|
72
|
+
sheet.setCellValue(A1, 99)
|
|
73
|
+
// 只有 A1 自身变更;B1 收敛(恒为 1)不应有事件
|
|
74
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
75
|
+
expect(handler).toHaveBeenCalledWith({ addr: A1 })
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('循环引用', () => {
|
|
80
|
+
it('A1==B1+1, B1==A1+1 → 双格 #CYCLE!;打破循环自动恢复', () => {
|
|
81
|
+
const sheet = new Sheet()
|
|
82
|
+
sheet.setCellFormula(A1, '=B1+1')
|
|
83
|
+
sheet.setCellFormula(B1, '=A1+1')
|
|
84
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
|
|
85
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
|
|
86
|
+
|
|
87
|
+
// 打破循环:B1 改为常量 → A1 恢复
|
|
88
|
+
sheet.setCellValue(B1, 5)
|
|
89
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: 6, t: 'n' })
|
|
90
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 5 })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('环外格引用环上格 → 错误传播(自身不入环)', () => {
|
|
94
|
+
const sheet = new Sheet()
|
|
95
|
+
sheet.setCellFormula(A1, '=B1+1')
|
|
96
|
+
sheet.setCellFormula(B1, '=A1+1')
|
|
97
|
+
sheet.setCellFormula(C1, '=A1*2')
|
|
98
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
|
|
99
|
+
|
|
100
|
+
// 打破循环后环外格一并恢复
|
|
101
|
+
sheet.setCellValue(B1, 2)
|
|
102
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: 3 })
|
|
103
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 6 })
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('自引用 A1==A1+1 → #CYCLE!,改常量后恢复', () => {
|
|
107
|
+
const sheet = new Sheet()
|
|
108
|
+
sheet.setCellFormula(A1, '=A1+1')
|
|
109
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
|
|
110
|
+
|
|
111
|
+
sheet.setCellValue(A1, 10)
|
|
112
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: 10 })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('跨表循环:Sheet1!A1 ↔ Sheet2!B1 → 双格 #CYCLE!,打破后恢复', () => {
|
|
116
|
+
const workbook = new Workbook()
|
|
117
|
+
const sheet1 = workbook.activeSheet
|
|
118
|
+
const sheet2 = workbook.addSheet('Sheet2')
|
|
119
|
+
sheet1.setCellFormula(A1, '=Sheet2!B1+1')
|
|
120
|
+
sheet2.setCellFormula(B1, '=Sheet1!A1+1')
|
|
121
|
+
expect(sheet1.getCellData(A1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
|
|
122
|
+
expect(sheet2.getCellData(B1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
|
|
123
|
+
|
|
124
|
+
sheet2.setCellValue(B1, 3)
|
|
125
|
+
expect(sheet1.getCellData(A1)).toMatchObject({ v: 4 })
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('循环期间其它无关变更不影响环状态', () => {
|
|
129
|
+
const sheet = new Sheet()
|
|
130
|
+
sheet.setCellFormula(A1, '=B1+1')
|
|
131
|
+
sheet.setCellFormula(B1, '=A1+D1')
|
|
132
|
+
sheet.setCellFormula(C1, '=A1+1')
|
|
133
|
+
// A1 → B1 → A1 成环(D1 是环上引用);C1 环外
|
|
134
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!' })
|
|
135
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#CYCLE!' })
|
|
136
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: '#CYCLE!' })
|
|
137
|
+
|
|
138
|
+
// 改 D1(环的输入)→ 重算后仍是环
|
|
139
|
+
sheet.setCellValue(D1, 10)
|
|
140
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!' })
|
|
141
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#CYCLE!' })
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('跨表引用', () => {
|
|
146
|
+
it('Sheet2 值变更 → Sheet1 引用格实时重算;undo 一并还原', () => {
|
|
147
|
+
const workbook = new Workbook()
|
|
148
|
+
const sheet1 = workbook.activeSheet
|
|
149
|
+
const sheet2 = workbook.addSheet('Sheet2')
|
|
150
|
+
sheet2.setCellValue(A1, 10)
|
|
151
|
+
sheet1.setCellFormula(B1, '=Sheet2!A1*2')
|
|
152
|
+
expect(sheet1.getCellData(B1)).toMatchObject({ v: 20 })
|
|
153
|
+
|
|
154
|
+
sheet2.setCellValue(A1, 7)
|
|
155
|
+
expect(sheet1.getCellData(B1)).toMatchObject({ v: 14 })
|
|
156
|
+
|
|
157
|
+
// 变更发生在 Sheet2,单次 undo 同时还原 Sheet2 源格与 Sheet1 派生格
|
|
158
|
+
expect(sheet2.undo()).toBe(true)
|
|
159
|
+
expect(sheet2.getCellData(A1)).toMatchObject({ v: 10 })
|
|
160
|
+
expect(sheet1.getCellData(B1)).toMatchObject({ v: 20 })
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('引用不存在的 sheet → #REF!', () => {
|
|
164
|
+
const sheet = new Sheet()
|
|
165
|
+
sheet.setCellFormula(A1, '=Nope!B2')
|
|
166
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: '#REF!', t: 'e' })
|
|
167
|
+
sheet.setCellFormula(B1, "=SUM('No Sheet'!A1:B2)")
|
|
168
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#REF!', t: 'e' })
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it("跨表区域:'S2'!A1:B2 聚合", () => {
|
|
172
|
+
const workbook = new Workbook()
|
|
173
|
+
const sheet1 = workbook.activeSheet
|
|
174
|
+
const s2 = workbook.addSheet('S2')
|
|
175
|
+
s2.setCellValue(A1, 1)
|
|
176
|
+
s2.setCellValue({ row: 1, col: 1 }, 2)
|
|
177
|
+
sheet1.setCellFormula(A1, "=SUM('S2'!A1:B2)")
|
|
178
|
+
expect(sheet1.getCellData(A1)).toMatchObject({ v: 3 })
|
|
179
|
+
|
|
180
|
+
// 区域内空格变更也触发重算
|
|
181
|
+
s2.setCellValue({ row: 0, col: 1 }, 10)
|
|
182
|
+
expect(sheet1.getCellData(A1)).toMatchObject({ v: 13 })
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('删除被引用的 sheet:缓存仍旧值,下次重算 → #REF!(已知限制行为)', () => {
|
|
186
|
+
const workbook = new Workbook()
|
|
187
|
+
const sheet1 = workbook.activeSheet
|
|
188
|
+
const sheet2 = workbook.addSheet('Sheet2')
|
|
189
|
+
sheet2.setCellValue(A1, 10)
|
|
190
|
+
sheet1.setCellFormula(B1, '=Sheet2!A1+D1')
|
|
191
|
+
sheet1.setCellValue(D1, 1)
|
|
192
|
+
expect(sheet1.getCellData(B1)).toMatchObject({ v: 11 })
|
|
193
|
+
|
|
194
|
+
workbook.removeSheet('Sheet2')
|
|
195
|
+
expect(workbook.formulaGraph.getSheet('Sheet2')).toBeUndefined()
|
|
196
|
+
// 删除本身不触发重算(已知限制):缓存仍旧值
|
|
197
|
+
expect(sheet1.getCellData(B1)).toMatchObject({ v: 11 })
|
|
198
|
+
// 任意触发源变更 → 重算时表已不存在 → #REF!
|
|
199
|
+
sheet1.setCellValue(D1, 2)
|
|
200
|
+
expect(sheet1.getCellData(B1)).toMatchObject({ v: '#REF!', t: 'e' })
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
describe('错误值体系', () => {
|
|
205
|
+
it('三类错误:除零 #DIV/0!、类型错误 #VALUE!、未知函数 #NAME?', () => {
|
|
206
|
+
const sheet = new Sheet()
|
|
207
|
+
sheet.setCellFormula(A1, '=1/0')
|
|
208
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: '#DIV/0!', t: 'e' })
|
|
209
|
+
sheet.setCellFormula(B1, '=1+"abc"')
|
|
210
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
211
|
+
sheet.setCellFormula(C1, '=NOSUCHFN(1)')
|
|
212
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: '#NAME?', t: 'e' })
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('0 作除数与 0 的负幂 → #DIV/0!;非法幂 → #VALUE!', () => {
|
|
216
|
+
const sheet = new Sheet()
|
|
217
|
+
sheet.setCellFormula(A1, '=0/0')
|
|
218
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: '#DIV/0!' })
|
|
219
|
+
sheet.setCellFormula(B1, '=0^-1')
|
|
220
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#DIV/0!' })
|
|
221
|
+
sheet.setCellFormula(C1, '=(-1)^0.5')
|
|
222
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: '#VALUE!' })
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('错误沿依赖链传播,源修复后恢复', () => {
|
|
226
|
+
const sheet = new Sheet()
|
|
227
|
+
sheet.setCellFormula(A1, '=1/0')
|
|
228
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
229
|
+
sheet.setCellFormula(C1, '=B1+1')
|
|
230
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#DIV/0!' })
|
|
231
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: '#DIV/0!' })
|
|
232
|
+
|
|
233
|
+
sheet.setCellValue(A1, 5)
|
|
234
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 10 })
|
|
235
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 11 })
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('区域直接作为公式结果 → #VALUE!', () => {
|
|
239
|
+
const sheet = new Sheet()
|
|
240
|
+
sheet.setCellValue(A1, 1)
|
|
241
|
+
sheet.setCellFormula(B1, '=A1:A3')
|
|
242
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
describe('undo/redo 集成', () => {
|
|
247
|
+
it('改值波及 10 格公式链 → 单次 undo 全部还原', () => {
|
|
248
|
+
const sheet = new Sheet()
|
|
249
|
+
sheet.setCellValue(A1, 1)
|
|
250
|
+
// B1..K1 共 10 格链:B1=A1+1, C1=B1+1, …
|
|
251
|
+
const chain: CellAddress[] = []
|
|
252
|
+
for (let col = 1; col <= 10; col++) {
|
|
253
|
+
const prev = { row: 0, col: col - 1 }
|
|
254
|
+
const addr = { row: 0, col }
|
|
255
|
+
sheet.setCellFormula(addr, `=${String.fromCharCode(64 + col)}1+1`)
|
|
256
|
+
chain.push(addr)
|
|
257
|
+
}
|
|
258
|
+
expect(sheet.getCellData(chain[9]!)).toMatchObject({ v: 11 })
|
|
259
|
+
sheet.history.clear()
|
|
260
|
+
|
|
261
|
+
sheet.setCellValue(A1, 100)
|
|
262
|
+
expect(sheet.getCellData(chain[9]!)).toMatchObject({ v: 110 })
|
|
263
|
+
// 源变更 + 10 格派生 = 一个 undo 单元
|
|
264
|
+
expect(sheet.history.undoSize).toBe(1)
|
|
265
|
+
|
|
266
|
+
expect(sheet.undo()).toBe(true)
|
|
267
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: 1 })
|
|
268
|
+
for (const [i, addr] of chain.entries()) {
|
|
269
|
+
expect(sheet.getCellData(addr)).toMatchObject({ v: i + 2 })
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
expect(sheet.redo()).toBe(true)
|
|
273
|
+
expect(sheet.getCellData(chain[9]!)).toMatchObject({ v: 110 })
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('undo 公式输入:公式、缓存与波及变更一步还原', () => {
|
|
277
|
+
const sheet = new Sheet()
|
|
278
|
+
sheet.setCellValue(A1, 5)
|
|
279
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
280
|
+
sheet.setCellFormula(C1, '=B1+1')
|
|
281
|
+
sheet.history.clear()
|
|
282
|
+
|
|
283
|
+
sheet.setCellFormula(D1, '=C1*10')
|
|
284
|
+
expect(sheet.getCellData(D1)).toMatchObject({ v: 110 })
|
|
285
|
+
|
|
286
|
+
sheet.undo()
|
|
287
|
+
expect(sheet.getCellData(D1)).toBeUndefined()
|
|
288
|
+
|
|
289
|
+
sheet.redo()
|
|
290
|
+
expect(sheet.getCellData(D1)).toMatchObject({ f: 'C1*10', v: 110 })
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('undo 公式删除 → 依赖图复原,再次改值照常重算', () => {
|
|
294
|
+
const sheet = new Sheet()
|
|
295
|
+
sheet.setCellValue(A1, 5)
|
|
296
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
297
|
+
sheet.setCellFormula(C1, '=B1+1')
|
|
298
|
+
const nodesBefore = sheet.formulaGraph.nodeCount
|
|
299
|
+
|
|
300
|
+
// 删除 B1 的公式(写常量覆盖)→ C1 按 B1 新值重算
|
|
301
|
+
sheet.setCellValue(B1, 0)
|
|
302
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 0 })
|
|
303
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 1 })
|
|
304
|
+
expect(sheet.formulaGraph.nodeCount).toBe(nodesBefore - 1)
|
|
305
|
+
|
|
306
|
+
// undo 删除 → 公式、缓存、派生值、图节点全部复原
|
|
307
|
+
sheet.undo()
|
|
308
|
+
expect(sheet.getCellData(B1)).toMatchObject({ f: 'A1*2', v: 10 })
|
|
309
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 11 })
|
|
310
|
+
expect(sheet.formulaGraph.nodeCount).toBe(nodesBefore)
|
|
311
|
+
|
|
312
|
+
// 图确实复原:再改 A1 仍触发 B1/C1 重算
|
|
313
|
+
sheet.setCellValue(A1, 7)
|
|
314
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 14 })
|
|
315
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 15 })
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
describe('公式与合并单元格(对抗场景)', () => {
|
|
320
|
+
it('引用合并格:锚点有值、被覆盖格按空(原始存储语义)', () => {
|
|
321
|
+
const sheet = new Sheet()
|
|
322
|
+
sheet.setCellValue({ row: 1, col: 1 }, 10) // B2
|
|
323
|
+
sheet.mergeCells(parseRange('B2:C3')!)
|
|
324
|
+
|
|
325
|
+
sheet.setCellFormula(D1, '=B2')
|
|
326
|
+
expect(sheet.getCellData(D1)).toMatchObject({ v: 10 })
|
|
327
|
+
// C3 是被覆盖格 → 空 → 0
|
|
328
|
+
sheet.setCellFormula({ row: 0, col: 4 }, '=C3+1')
|
|
329
|
+
expect(sheet.getCellData({ row: 0, col: 4 })).toMatchObject({ v: 1 })
|
|
330
|
+
// 区域含合并:只锚点计数一次
|
|
331
|
+
sheet.setCellFormula({ row: 0, col: 5 }, '=SUM(B2:C3)')
|
|
332
|
+
expect(sheet.getCellData({ row: 0, col: 5 })).toMatchObject({ v: 10 })
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('公式格作为合并锚点:合并后公式保留且继续重算', () => {
|
|
336
|
+
const sheet = new Sheet()
|
|
337
|
+
sheet.setCellValue(A1, 5)
|
|
338
|
+
sheet.setCellFormula({ row: 1, col: 1 }, '=A1+1') // B2
|
|
339
|
+
sheet.mergeCells(parseRange('B2:C3')!)
|
|
340
|
+
|
|
341
|
+
expect(sheet.getCellData({ row: 1, col: 1 })).toMatchObject({ f: 'A1+1', v: 6 })
|
|
342
|
+
sheet.setCellValue(A1, 7)
|
|
343
|
+
expect(sheet.getCellData({ row: 1, col: 1 })).toMatchObject({ v: 8 })
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('被覆盖格上的公式被合并清除:节点移除 + 依赖者重算', () => {
|
|
347
|
+
const sheet = new Sheet()
|
|
348
|
+
sheet.setCellValue({ row: 1, col: 1 }, 10) // B2 锚点
|
|
349
|
+
sheet.setCellFormula({ row: 2, col: 2 }, '=B2+1') // C3 将被覆盖
|
|
350
|
+
sheet.setCellFormula(D1, '=C3*2')
|
|
351
|
+
expect(sheet.getCellData(D1)).toMatchObject({ v: 22 })
|
|
352
|
+
const nodesBefore = sheet.formulaGraph.nodeCount
|
|
353
|
+
|
|
354
|
+
sheet.mergeCells(parseRange('B2:C3')!)
|
|
355
|
+
// C3 数据被清空 → 公式节点移除;D1 按 C3 空值重算
|
|
356
|
+
expect(sheet.getCellData({ row: 2, col: 2 })).toBeUndefined()
|
|
357
|
+
expect(sheet.formulaGraph.nodeCount).toBe(nodesBefore - 1)
|
|
358
|
+
expect(sheet.getCellData(D1)).toMatchObject({ v: 0 })
|
|
359
|
+
|
|
360
|
+
// undo 合并 → C3 公式恢复,D1 回到 22
|
|
361
|
+
sheet.undo()
|
|
362
|
+
expect(sheet.getCellData({ row: 2, col: 2 })).toMatchObject({ f: 'B2+1', v: 11 })
|
|
363
|
+
expect(sheet.getCellData(D1)).toMatchObject({ v: 22 })
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
describe('公式生命周期(对抗场景)', () => {
|
|
368
|
+
it('删除公式后依赖图清理:原依赖格改动不再触发重算', () => {
|
|
369
|
+
const sheet = new Sheet()
|
|
370
|
+
sheet.setCellValue(A1, 5)
|
|
371
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
372
|
+
sheet.setCellFormula(C1, '=B1+1')
|
|
373
|
+
expect(sheet.formulaGraph.nodeCount).toBe(2)
|
|
374
|
+
|
|
375
|
+
// 清空 B1(删除公式)→ C1 按空值重算
|
|
376
|
+
sheet.setCellValue(B1, null)
|
|
377
|
+
expect(sheet.getCellData(B1)).toBeUndefined()
|
|
378
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 1 })
|
|
379
|
+
expect(sheet.formulaGraph.nodeCount).toBe(1)
|
|
380
|
+
|
|
381
|
+
// A1 改动不再有任何依赖者
|
|
382
|
+
const handler = vi.fn()
|
|
383
|
+
sheet.on('cell-change', handler)
|
|
384
|
+
sheet.setCellValue(A1, 99)
|
|
385
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
386
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 1 })
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('公式改公式:依赖边切换(旧依赖不再触发,新依赖生效)', () => {
|
|
390
|
+
const sheet = new Sheet()
|
|
391
|
+
sheet.setCellValue(A1, 1)
|
|
392
|
+
sheet.setCellValue(B1, 10)
|
|
393
|
+
sheet.setCellFormula(C1, '=A1+1')
|
|
394
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 2 })
|
|
395
|
+
|
|
396
|
+
sheet.setCellFormula(C1, '=B1+1')
|
|
397
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 11 })
|
|
398
|
+
|
|
399
|
+
// 旧依赖 A1 改动不再影响 C1
|
|
400
|
+
sheet.setCellValue(A1, 100)
|
|
401
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 11 })
|
|
402
|
+
// 新依赖 B1 生效
|
|
403
|
+
sheet.setCellValue(B1, 20)
|
|
404
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 21 })
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('同公式重复输入 = 无操作(不入历史)', () => {
|
|
408
|
+
const sheet = new Sheet()
|
|
409
|
+
sheet.setCellValue(A1, 1)
|
|
410
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
411
|
+
const depth = sheet.history.undoSize
|
|
412
|
+
|
|
413
|
+
sheet.setCellFormula(B1, '=A1*2')
|
|
414
|
+
expect(sheet.history.undoSize).toBe(depth)
|
|
415
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 2 })
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it("'=' 单独输入 = 清除单元格", () => {
|
|
419
|
+
const sheet = new Sheet()
|
|
420
|
+
sheet.setCellValue(A1, 5)
|
|
421
|
+
sheet.setCellValue(A1, '=')
|
|
422
|
+
expect(sheet.getCellData(A1)).toBeUndefined()
|
|
423
|
+
// 空格输入 '=' 也是无操作
|
|
424
|
+
expect(sheet.canUndo).toBe(true) // 仍有上面两条历史
|
|
425
|
+
sheet.setCellValue(B1, '=')
|
|
426
|
+
expect(sheet.getCellData(B1)).toBeUndefined()
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('A1:A1 单格区域', () => {
|
|
430
|
+
const sheet = new Sheet()
|
|
431
|
+
sheet.setCellValue(A1, 42)
|
|
432
|
+
sheet.setCellFormula(B1, '=SUM(A1:A1)')
|
|
433
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 42 })
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it('公式引用空区域 → SUM 为 0、COUNT 为 0、AVERAGE #DIV/0!', () => {
|
|
437
|
+
const sheet = new Sheet()
|
|
438
|
+
sheet.setCellFormula(A1, '=SUM(D1:E9)')
|
|
439
|
+
sheet.setCellFormula(B1, '=COUNT(D1:E9)')
|
|
440
|
+
sheet.setCellFormula(C1, '=AVERAGE(D1:E9)')
|
|
441
|
+
expect(sheet.getCellData(A1)).toMatchObject({ v: 0 })
|
|
442
|
+
expect(sheet.getCellData(B1)).toMatchObject({ v: 0 })
|
|
443
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: '#DIV/0!' })
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('批量写入(粘贴场景):重算派生并入同一 undo 单元', () => {
|
|
447
|
+
const sheet = new Sheet()
|
|
448
|
+
sheet.setCellFormula(C1, '=A1+B1')
|
|
449
|
+
sheet.history.clear()
|
|
450
|
+
|
|
451
|
+
sheet.setCells([
|
|
452
|
+
{ addr: A1, data: { v: 1, t: 'n' } },
|
|
453
|
+
{ addr: B1, data: { v: 2, t: 'n' } }
|
|
454
|
+
])
|
|
455
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 3 })
|
|
456
|
+
expect(sheet.history.undoSize).toBe(1)
|
|
457
|
+
|
|
458
|
+
sheet.undo()
|
|
459
|
+
expect(sheet.getCellData(A1)).toBeUndefined()
|
|
460
|
+
expect(sheet.getCellData(B1)).toBeUndefined()
|
|
461
|
+
expect(sheet.getCellData(C1)).toMatchObject({ v: 0 })
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
it('setCell 直接写带 f 的 CellData 同样走重算', () => {
|
|
465
|
+
const sheet = new Sheet()
|
|
466
|
+
sheet.setCellValue(A1, 6)
|
|
467
|
+
sheet.setCell(B1, { f: 'A1*2' })
|
|
468
|
+
expect(sheet.getCellData(B1)).toMatchObject({ f: 'A1*2', v: 12, t: 'n' })
|
|
469
|
+
})
|
|
470
|
+
})
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { CellAddress } from '../address'
|
|
4
|
+
import { Sheet } from '../sheet'
|
|
5
|
+
|
|
6
|
+
const A1 = { row: 0, col: 0 }
|
|
7
|
+
|
|
8
|
+
/** 写入公式并读回计算缓存值 */
|
|
9
|
+
function calcValue(sheet: Sheet, formula: string, addr: CellAddress = { row: 20, col: 20 }) {
|
|
10
|
+
sheet.setCellFormula(addr, formula)
|
|
11
|
+
return sheet.getCellData(addr)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('函数集:SUM / AVERAGE / MAX / MIN', () => {
|
|
15
|
+
it('SUM:直接参数求和、数字文本强转、非法文本 #VALUE!', () => {
|
|
16
|
+
const sheet = new Sheet()
|
|
17
|
+
expect(calcValue(sheet, '=SUM(1,2,3)')).toMatchObject({ v: 6 })
|
|
18
|
+
expect(calcValue(sheet, '=SUM("5",3)')).toMatchObject({ v: 8 })
|
|
19
|
+
expect(calcValue(sheet, '=SUM("abc")')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('SUM:区域内文本/布尔忽略,空格按 0', () => {
|
|
23
|
+
const sheet = new Sheet()
|
|
24
|
+
sheet.setCellValue(A1, 2)
|
|
25
|
+
sheet.setCellValue({ row: 1, col: 0 }, 'text')
|
|
26
|
+
sheet.setCellValue({ row: 2, col: 0 }, true)
|
|
27
|
+
expect(calcValue(sheet, '=SUM(A1:A5)')).toMatchObject({ v: 2 })
|
|
28
|
+
// 全空区域 → 0
|
|
29
|
+
expect(calcValue(sheet, '=SUM(C1:C9)')).toMatchObject({ v: 0 })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('AVERAGE:均值;无数字 → #DIV/0!', () => {
|
|
33
|
+
const sheet = new Sheet()
|
|
34
|
+
expect(calcValue(sheet, '=AVERAGE(2,4)')).toMatchObject({ v: 3 })
|
|
35
|
+
expect(calcValue(sheet, '=AVERAGE(C1:C9)')).toMatchObject({ v: '#DIV/0!', t: 'e' })
|
|
36
|
+
sheet.setCellValue(A1, 2)
|
|
37
|
+
sheet.setCellValue({ row: 1, col: 0 }, 'text')
|
|
38
|
+
// 区域内文本忽略,只按数字格求均值
|
|
39
|
+
expect(calcValue(sheet, '=AVERAGE(A1:A2)')).toMatchObject({ v: 2 })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('MAX / MIN:极值;无数字 → 0', () => {
|
|
43
|
+
const sheet = new Sheet()
|
|
44
|
+
expect(calcValue(sheet, '=MAX(1,9,3)')).toMatchObject({ v: 9 })
|
|
45
|
+
expect(calcValue(sheet, '=MIN(1,-9,3)')).toMatchObject({ v: -9 })
|
|
46
|
+
expect(calcValue(sheet, '=MAX(C1:C9)')).toMatchObject({ v: 0 })
|
|
47
|
+
expect(calcValue(sheet, '=MIN(C1:C9)')).toMatchObject({ v: 0 })
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('函数集:COUNT / COUNTA', () => {
|
|
52
|
+
it('COUNT 只数数字(区域内文本/空格不计;直接参数可强转即计)', () => {
|
|
53
|
+
const sheet = new Sheet()
|
|
54
|
+
sheet.setCellValue(A1, 1)
|
|
55
|
+
sheet.setCellValue({ row: 1, col: 0 }, 'text')
|
|
56
|
+
sheet.setCellValue({ row: 2, col: 0 }, true)
|
|
57
|
+
expect(calcValue(sheet, '=COUNT(A1:A5)')).toMatchObject({ v: 1 })
|
|
58
|
+
expect(calcValue(sheet, '=COUNT(1,"x","5",TRUE)')).toMatchObject({ v: 3 })
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('COUNTA 数非空(文本/布尔/空串结果都计,空格不计)', () => {
|
|
62
|
+
const sheet = new Sheet()
|
|
63
|
+
sheet.setCellValue(A1, 1)
|
|
64
|
+
sheet.setCellValue({ row: 1, col: 0 }, 'text')
|
|
65
|
+
sheet.setCellValue({ row: 2, col: 0 }, true)
|
|
66
|
+
expect(calcValue(sheet, '=COUNTA(A1:A5)')).toMatchObject({ v: 3 })
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('函数集:IF / AND / OR / NOT', () => {
|
|
71
|
+
it('IF:分支选择;缺省 else → FALSE;条件非法 → #VALUE!', () => {
|
|
72
|
+
const sheet = new Sheet()
|
|
73
|
+
expect(calcValue(sheet, '=IF(1>0,"y","n")')).toMatchObject({ v: 'y' })
|
|
74
|
+
expect(calcValue(sheet, '=IF(0,1,2)')).toMatchObject({ v: 2 })
|
|
75
|
+
expect(calcValue(sheet, '=IF(1=1,7)')).toMatchObject({ v: 7 })
|
|
76
|
+
expect(calcValue(sheet, '=IF(0,7)')).toMatchObject({ v: false, t: 'b' })
|
|
77
|
+
expect(calcValue(sheet, '=IF("abc",1,2)')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('IF 惰性求值:未选分支的除零不产生错误', () => {
|
|
81
|
+
const sheet = new Sheet()
|
|
82
|
+
expect(calcValue(sheet, '=IF(FALSE,1/0,2)')).toMatchObject({ v: 2 })
|
|
83
|
+
expect(calcValue(sheet, '=IF(TRUE,1,1/0)')).toMatchObject({ v: 1 })
|
|
84
|
+
// 条件本身是错误则传播
|
|
85
|
+
expect(calcValue(sheet, '=IF(1/0,1,2)')).toMatchObject({ v: '#DIV/0!', t: 'e' })
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('AND / OR / NOT:布尔逻辑与强转', () => {
|
|
89
|
+
const sheet = new Sheet()
|
|
90
|
+
expect(calcValue(sheet, '=AND(1,TRUE)')).toMatchObject({ v: true })
|
|
91
|
+
expect(calcValue(sheet, '=AND(1,0)')).toMatchObject({ v: false })
|
|
92
|
+
expect(calcValue(sheet, '=OR(0,FALSE)')).toMatchObject({ v: false })
|
|
93
|
+
expect(calcValue(sheet, '=OR(0,1)')).toMatchObject({ v: true })
|
|
94
|
+
expect(calcValue(sheet, '=NOT(0)')).toMatchObject({ v: true })
|
|
95
|
+
expect(calcValue(sheet, '=NOT(TRUE)')).toMatchObject({ v: false })
|
|
96
|
+
// 无有效操作数 / 非法文本 → #VALUE!
|
|
97
|
+
expect(calcValue(sheet, '=AND(C1:C9)')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
98
|
+
expect(calcValue(sheet, '=OR("x")')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
99
|
+
expect(calcValue(sheet, '=NOT("x")')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
100
|
+
// 区域内只取布尔格
|
|
101
|
+
sheet.setCellValue(A1, true)
|
|
102
|
+
sheet.setCellValue({ row: 1, col: 0 }, 5)
|
|
103
|
+
expect(calcValue(sheet, '=AND(A1:A2)')).toMatchObject({ v: true })
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe('函数集:ROUND / ABS / CONCATENATE', () => {
|
|
108
|
+
it('ROUND:精度、负数位数、远离零取整', () => {
|
|
109
|
+
const sheet = new Sheet()
|
|
110
|
+
expect(calcValue(sheet, '=ROUND(3.14159,2)')).toMatchObject({ v: 3.14 })
|
|
111
|
+
expect(calcValue(sheet, '=ROUND(2.5,0)')).toMatchObject({ v: 3 })
|
|
112
|
+
expect(calcValue(sheet, '=ROUND(-2.5,0)')).toMatchObject({ v: -3 })
|
|
113
|
+
expect(calcValue(sheet, '=ROUND(1234.567,-2)')).toMatchObject({ v: 1200 })
|
|
114
|
+
// 浮点表示误差补偿(2.675 实际存储为 2.67499…)
|
|
115
|
+
expect(calcValue(sheet, '=ROUND(2.675,2)')).toMatchObject({ v: 2.68 })
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('ABS:绝对值;非法参数 → #VALUE!', () => {
|
|
119
|
+
const sheet = new Sheet()
|
|
120
|
+
expect(calcValue(sheet, '=ABS(-3)')).toMatchObject({ v: 3 })
|
|
121
|
+
expect(calcValue(sheet, '=ABS("x")')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('CONCATENATE:文本拼接(数字/布尔强转,区域展开,空格按空串)', () => {
|
|
125
|
+
const sheet = new Sheet()
|
|
126
|
+
expect(calcValue(sheet, '=CONCATENATE("a",1,TRUE)')).toMatchObject({ v: 'a1TRUE' })
|
|
127
|
+
sheet.setCellValue(A1, 'x')
|
|
128
|
+
sheet.setCellValue({ row: 2, col: 0 }, 'z')
|
|
129
|
+
// A2 为空格 → 空串
|
|
130
|
+
expect(calcValue(sheet, '=CONCATENATE(A1:A3)')).toMatchObject({ v: 'xz' })
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('函数集:通用语义', () => {
|
|
135
|
+
it('嵌套函数与多层括号', () => {
|
|
136
|
+
const sheet = new Sheet()
|
|
137
|
+
sheet.setCellValue(A1, 1)
|
|
138
|
+
sheet.setCellValue({ row: 1, col: 0 }, 2)
|
|
139
|
+
sheet.setCellValue({ row: 2, col: 0 }, 3)
|
|
140
|
+
sheet.setCellValue({ row: 0, col: 1 }, 8)
|
|
141
|
+
sheet.setCellValue({ row: 0, col: 2 }, 4)
|
|
142
|
+
expect(calcValue(sheet, '=SUM(A1:A3, MAX(B1, MIN(C1, 10))) * 2')).toMatchObject({ v: 28 })
|
|
143
|
+
expect(calcValue(sheet, '=((1+2)*(3+4))/(7-2)')).toMatchObject({ v: 4.2 })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('参数个数非法 → #VALUE!;未知函数 → #NAME?', () => {
|
|
147
|
+
const sheet = new Sheet()
|
|
148
|
+
expect(calcValue(sheet, '=IF(1)')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
149
|
+
expect(calcValue(sheet, '=ROUND(1)')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
150
|
+
expect(calcValue(sheet, '=ABS(1,2)')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
151
|
+
expect(calcValue(sheet, '=SUM()')).toMatchObject({ v: '#VALUE!', t: 'e' })
|
|
152
|
+
expect(calcValue(sheet, '=NOSUCHFN(1)')).toMatchObject({ v: '#NAME?', t: 'e' })
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('函数名大小写不敏感', () => {
|
|
156
|
+
const sheet = new Sheet()
|
|
157
|
+
expect(calcValue(sheet, '=sum(1,2)')).toMatchObject({ v: 3 })
|
|
158
|
+
expect(calcValue(sheet, '=If(1,2,3)')).toMatchObject({ v: 2 })
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('区域内错误值传播', () => {
|
|
162
|
+
const sheet = new Sheet()
|
|
163
|
+
sheet.setCellFormula(A1, '=1/0')
|
|
164
|
+
sheet.setCellValue({ row: 1, col: 0 }, 5)
|
|
165
|
+
expect(calcValue(sheet, '=SUM(A1:A2)')).toMatchObject({ v: '#DIV/0!', t: 'e' })
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('稀疏性:大区域内只有少量格参与迭代', () => {
|
|
169
|
+
const sheet = new Sheet()
|
|
170
|
+
sheet.setCellValue(A1, 1)
|
|
171
|
+
sheet.setCellValue({ row: 9999, col: 0 }, 2)
|
|
172
|
+
// A1:A10000 只有 2 个真实格;结果与迭代规模无关
|
|
173
|
+
expect(sheet.store.size).toBe(2)
|
|
174
|
+
expect(calcValue(sheet, '=SUM(A1:A10000)')).toMatchObject({ v: 3 })
|
|
175
|
+
expect(calcValue(sheet, '=COUNT(A1:A10000)')).toMatchObject({ v: 2 })
|
|
176
|
+
expect(calcValue(sheet, '=AVERAGE(A1:A10000)')).toMatchObject({ v: 1.5 })
|
|
177
|
+
})
|
|
178
|
+
})
|