@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.
Files changed (137) hide show
  1. package/dist/core/address.d.ts +60 -0
  2. package/dist/core/address.js +142 -0
  3. package/dist/core/address.js.map +1 -0
  4. package/dist/core/cell-store.d.ts +61 -0
  5. package/dist/core/cell-store.js +135 -0
  6. package/dist/core/cell-store.js.map +1 -0
  7. package/dist/core/command/default-registry.d.ts +10 -0
  8. package/dist/core/command/default-registry.js +18 -0
  9. package/dist/core/command/default-registry.js.map +1 -0
  10. package/dist/core/command/history.d.ts +64 -0
  11. package/dist/core/command/history.js +127 -0
  12. package/dist/core/command/history.js.map +1 -0
  13. package/dist/core/command/merge-cells.d.ts +21 -0
  14. package/dist/core/command/merge-cells.js +74 -0
  15. package/dist/core/command/merge-cells.js.map +1 -0
  16. package/dist/core/command/registry.d.ts +17 -0
  17. package/dist/core/command/registry.js +26 -0
  18. package/dist/core/command/registry.js.map +1 -0
  19. package/dist/core/command/set-cell-formula.d.ts +17 -0
  20. package/dist/core/command/set-cell-formula.js +29 -0
  21. package/dist/core/command/set-cell-formula.js.map +1 -0
  22. package/dist/core/command/set-cell-value.d.ts +22 -0
  23. package/dist/core/command/set-cell-value.js +35 -0
  24. package/dist/core/command/set-cell-value.js.map +1 -0
  25. package/dist/core/command/types.d.ts +68 -0
  26. package/dist/core/events.js +29 -0
  27. package/dist/core/events.js.map +1 -0
  28. package/dist/core/formula/ast.d.ts +58 -0
  29. package/dist/core/formula/ast.js +38 -0
  30. package/dist/core/formula/ast.js.map +1 -0
  31. package/dist/core/formula/dependency-graph.d.ts +70 -0
  32. package/dist/core/formula/dependency-graph.js +269 -0
  33. package/dist/core/formula/dependency-graph.js.map +1 -0
  34. package/dist/core/formula/errors.d.ts +25 -0
  35. package/dist/core/formula/errors.js +36 -0
  36. package/dist/core/formula/errors.js.map +1 -0
  37. package/dist/core/formula/evaluator.d.ts +41 -0
  38. package/dist/core/formula/evaluator.js +154 -0
  39. package/dist/core/formula/evaluator.js.map +1 -0
  40. package/dist/core/formula/functions.d.ts +30 -0
  41. package/dist/core/formula/functions.js +209 -0
  42. package/dist/core/formula/functions.js.map +1 -0
  43. package/dist/core/formula/parser.d.ts +7 -0
  44. package/dist/core/formula/parser.js +208 -0
  45. package/dist/core/formula/parser.js.map +1 -0
  46. package/dist/core/formula/tokenizer.d.ts +37 -0
  47. package/dist/core/formula/tokenizer.js +146 -0
  48. package/dist/core/formula/tokenizer.js.map +1 -0
  49. package/dist/core/merge-manager.d.ts +64 -0
  50. package/dist/core/merge-manager.js +111 -0
  51. package/dist/core/merge-manager.js.map +1 -0
  52. package/dist/core/selection.d.ts +31 -0
  53. package/dist/core/selection.js +62 -0
  54. package/dist/core/selection.js.map +1 -0
  55. package/dist/core/sheet.d.ts +105 -0
  56. package/dist/core/sheet.js +202 -0
  57. package/dist/core/sheet.js.map +1 -0
  58. package/dist/core/workbook.d.ts +43 -0
  59. package/dist/core/workbook.js +77 -0
  60. package/dist/core/workbook.js.map +1 -0
  61. package/dist/grid/sheet-grid.d.ts +59 -0
  62. package/dist/grid/sheet-grid.js +230 -0
  63. package/dist/grid/sheet-grid.js.map +1 -0
  64. package/dist/index.d.ts +28 -0
  65. package/dist/index.js +25 -0
  66. package/dist/tools/builtin.js +72 -0
  67. package/dist/tools/builtin.js.map +1 -0
  68. package/dist/tools/context.d.ts +57 -0
  69. package/dist/tools/context.js +43 -0
  70. package/dist/tools/context.js.map +1 -0
  71. package/dist/tools/registry.d.ts +68 -0
  72. package/dist/tools/registry.js +77 -0
  73. package/dist/tools/registry.js.map +1 -0
  74. package/dist/types/index.d.ts +2 -0
  75. package/dist/types/sheet.d.ts +43 -0
  76. package/dist/vue/index.d.ts +2 -0
  77. package/dist/vue/sheet.js +185 -0
  78. package/dist/vue/sheet.js.map +1 -0
  79. package/dist/vue/sheet.vue.d.ts +24 -0
  80. package/dist/vue/style.d.ts +1 -0
  81. package/dist/vue/style.js +1 -0
  82. package/dist/vue/style2.css +93 -0
  83. package/package.json +37 -0
  84. package/src/core/__test__/address.test.ts +113 -0
  85. package/src/core/__test__/cell-store.test.ts +97 -0
  86. package/src/core/__test__/command.test.ts +377 -0
  87. package/src/core/__test__/formula-adversarial.test.ts +265 -0
  88. package/src/core/__test__/formula-engine.test.ts +470 -0
  89. package/src/core/__test__/formula-functions.test.ts +178 -0
  90. package/src/core/__test__/formula-parser.test.ts +160 -0
  91. package/src/core/__test__/history.test.ts +225 -0
  92. package/src/core/__test__/merge-manager.test.ts +106 -0
  93. package/src/core/__test__/sheet.test.ts +142 -0
  94. package/src/core/address.ts +166 -0
  95. package/src/core/cell-store.ts +173 -0
  96. package/src/core/command/default-registry.ts +15 -0
  97. package/src/core/command/history.ts +167 -0
  98. package/src/core/command/index.ts +7 -0
  99. package/src/core/command/merge-cells.ts +74 -0
  100. package/src/core/command/registry.ts +30 -0
  101. package/src/core/command/set-cell-formula.ts +29 -0
  102. package/src/core/command/set-cell-value.ts +38 -0
  103. package/src/core/command/types.ts +73 -0
  104. package/src/core/events.ts +37 -0
  105. package/src/core/formula/ast.ts +66 -0
  106. package/src/core/formula/dependency-graph.ts +329 -0
  107. package/src/core/formula/errors.ts +41 -0
  108. package/src/core/formula/evaluator.ts +219 -0
  109. package/src/core/formula/functions.ts +266 -0
  110. package/src/core/formula/index.ts +7 -0
  111. package/src/core/formula/parser.ts +198 -0
  112. package/src/core/formula/tokenizer.ts +160 -0
  113. package/src/core/merge-manager.ts +158 -0
  114. package/src/core/selection.ts +66 -0
  115. package/src/core/sheet.ts +257 -0
  116. package/src/core/workbook.ts +99 -0
  117. package/src/env.d.ts +7 -0
  118. package/src/grid/__test__/canvas-mock.ts +49 -0
  119. package/src/grid/__test__/setup.ts +3 -0
  120. package/src/grid/__test__/sheet-grid-formula.test.ts +140 -0
  121. package/src/grid/__test__/sheet-grid.test.ts +177 -0
  122. package/src/grid/sheet-grid.ts +283 -0
  123. package/src/index.ts +16 -0
  124. package/src/tools/__test__/builtin.test.ts +128 -0
  125. package/src/tools/__test__/context.test.ts +157 -0
  126. package/src/tools/__test__/registry.test.ts +102 -0
  127. package/src/tools/builtin.ts +75 -0
  128. package/src/tools/context.ts +106 -0
  129. package/src/tools/index.ts +2 -0
  130. package/src/tools/registry.ts +129 -0
  131. package/src/types/index.ts +1 -0
  132. package/src/types/sheet.ts +41 -0
  133. package/src/vue/__test__/sheet-component.test.ts +267 -0
  134. package/src/vue/index.ts +1 -0
  135. package/src/vue/sheet.vue +195 -0
  136. package/src/vue/style.scss +94 -0
  137. package/src/vue/style.ts +1 -0
@@ -0,0 +1,265 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { parseRange } from '../address'
4
+ import { registerFormulaFunction } from '../formula/functions'
5
+ import { Sheet } from '../sheet'
6
+ import { Workbook } from '../workbook'
7
+
8
+ const A1 = { row: 0, col: 0 }
9
+ const B1 = { row: 0, col: 1 }
10
+ const C1 = { row: 0, col: 2 }
11
+ const D1 = { row: 0, col: 3 }
12
+
13
+ describe('对抗:区域依赖', () => {
14
+ it('删除区域公式后,区域内改动不再触发重算(ranged 反向索引清理)', () => {
15
+ const sheet = new Sheet()
16
+ sheet.setCellValue(A1, 1)
17
+ sheet.setCellFormula(D1, '=SUM(A1:A10)')
18
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 1 })
19
+
20
+ sheet.setCellValue(D1, 0) // 删除公式
21
+ const handler = vi.fn()
22
+ sheet.on('cell-change', handler)
23
+ sheet.setCellValue({ row: 5, col: 0 }, 99)
24
+ // 只有 A6 自身变更;D1 已删公式不应重算
25
+ expect(handler).toHaveBeenCalledTimes(1)
26
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 0 })
27
+ })
28
+
29
+ it('跨表区域公式改写后,Sheet2 改动不再触发 Sheet1 重算', () => {
30
+ const workbook = new Workbook()
31
+ const sheet1 = workbook.activeSheet
32
+ const sheet2 = workbook.addSheet('Sheet2')
33
+ sheet2.setCellValue(B1, 5)
34
+ sheet1.setCellFormula(A1, '=SUM(Sheet2!B1:B10)')
35
+ expect(sheet1.getCellData(A1)).toMatchObject({ v: 5 })
36
+
37
+ // 改写为不引用 Sheet2 的公式 → 旧跨表区域依赖解除
38
+ sheet1.setCellFormula(A1, '=1')
39
+ const handler = vi.fn()
40
+ sheet1.on('cell-change', handler)
41
+ sheet2.setCellValue(B1, 99)
42
+ expect(handler).not.toHaveBeenCalled()
43
+ expect(sheet1.getCellData(A1)).toMatchObject({ v: 1 })
44
+ })
45
+
46
+ it('经由区域形成的循环:A1=SUM(B1:B2),B1=A1+1 → 双格 #CYCLE!', () => {
47
+ const sheet = new Sheet()
48
+ sheet.setCellFormula(A1, '=SUM(B1:B2)')
49
+ sheet.setCellFormula(B1, '=A1+1')
50
+ expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
51
+ expect(sheet.getCellData(B1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
52
+
53
+ sheet.setCellValue(B1, 4)
54
+ expect(sheet.getCellData(A1)).toMatchObject({ v: 4 })
55
+ })
56
+
57
+ it('自包含区域:A1=SUM(A1:A3) → #CYCLE!', () => {
58
+ const sheet = new Sheet()
59
+ sheet.setCellFormula(A1, '=SUM(A1:A3)')
60
+ expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!', t: 'e' })
61
+ })
62
+
63
+ it('区域内的脏公式格:区域聚合拿到的是本轮新值(拓扑序)', () => {
64
+ const sheet = new Sheet()
65
+ sheet.setCellValue(B1, 2)
66
+ sheet.setCellFormula({ row: 1, col: 0 }, '=B1*2') // A2,位于 A1:A3 区域内
67
+ sheet.setCellFormula(D1, '=SUM(A1:A3)')
68
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 4 })
69
+
70
+ sheet.setCellValue(B1, 10)
71
+ expect(sheet.getCellData({ row: 1, col: 0 })).toMatchObject({ v: 20 })
72
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 20 })
73
+ })
74
+ })
75
+
76
+ describe('对抗:循环的生命周期', () => {
77
+ it('循环建立的 undo/redo:状态精确还原', () => {
78
+ const sheet = new Sheet()
79
+ sheet.setCellFormula(A1, '=B1+1')
80
+ expect(sheet.getCellData(A1)).toMatchObject({ v: 1 }) // B1 空 → 1
81
+ sheet.history.clear()
82
+
83
+ sheet.setCellFormula(B1, '=A1+1') // 成环
84
+ expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!' })
85
+ expect(sheet.getCellData(B1)).toMatchObject({ v: '#CYCLE!' })
86
+
87
+ sheet.undo() // B1 公式撤销 → A1 复原
88
+ expect(sheet.getCellData(B1)).toBeUndefined()
89
+ expect(sheet.getCellData(A1)).toMatchObject({ v: 1 })
90
+
91
+ sheet.redo() // 再次成环
92
+ expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!' })
93
+ expect(sheet.getCellData(B1)).toMatchObject({ v: '#CYCLE!' })
94
+ })
95
+
96
+ it('删除环上一格 → 另一格自动恢复', () => {
97
+ const sheet = new Sheet()
98
+ sheet.setCellFormula(A1, '=B1+1')
99
+ sheet.setCellFormula(B1, '=A1+1')
100
+ expect(sheet.getCellData(A1)).toMatchObject({ v: '#CYCLE!' })
101
+
102
+ sheet.setCellValue(A1, null)
103
+ expect(sheet.getCellData(A1)).toBeUndefined()
104
+ expect(sheet.getCellData(B1)).toMatchObject({ v: 1 })
105
+ })
106
+
107
+ it('IF 未选分支中的静态自引用不算环(求值期动态检测)', () => {
108
+ const sheet = new Sheet()
109
+ sheet.setCellFormula(A1, '=IF(FALSE,A1+1,5)')
110
+ expect(sheet.getCellData(A1)).toMatchObject({ v: 5 })
111
+ })
112
+ })
113
+
114
+ describe('对抗:事务与回滚', () => {
115
+ it('事务内公式写入 + 重算:提交后是一个 undo 单元', () => {
116
+ const sheet = new Sheet()
117
+ sheet.setCellFormula(C1, '=A1+B1')
118
+ sheet.history.clear()
119
+
120
+ sheet.beginTransaction()
121
+ sheet.setCellValue(A1, 3)
122
+ sheet.setCellFormula(D1, '=C1*10')
123
+ sheet.commit()
124
+ expect(sheet.getCellData(C1)).toMatchObject({ v: 3 })
125
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 30 })
126
+ expect(sheet.history.undoSize).toBe(1)
127
+
128
+ sheet.undo()
129
+ expect(sheet.getCellData(A1)).toBeUndefined()
130
+ expect(sheet.getCellData(C1)).toMatchObject({ v: 0 })
131
+ expect(sheet.getCellData(D1)).toBeUndefined()
132
+ })
133
+
134
+ it('事务回滚:公式与派生值全部还原,图节点清理', () => {
135
+ const sheet = new Sheet()
136
+ sheet.setCellValue(A1, 1)
137
+ const nodesBefore = sheet.formulaGraph.nodeCount
138
+
139
+ sheet.beginTransaction()
140
+ sheet.setCellFormula(B1, '=A1*2')
141
+ expect(sheet.getCellData(B1)).toMatchObject({ v: 2 })
142
+ sheet.rollback()
143
+
144
+ expect(sheet.getCellData(B1)).toBeUndefined()
145
+ expect(sheet.formulaGraph.nodeCount).toBe(nodesBefore)
146
+ // 图已清理:改 A1 不再有任何重算
147
+ const handler = vi.fn()
148
+ sheet.on('cell-change', handler)
149
+ sheet.setCellValue(A1, 9)
150
+ expect(handler).toHaveBeenCalledTimes(1)
151
+ })
152
+ })
153
+
154
+ describe('对抗:合并与公式交互', () => {
155
+ it('合并清除被覆盖格的值后,区域聚合同步变小', () => {
156
+ const sheet = new Sheet()
157
+ sheet.setCellValue({ row: 1, col: 1 }, 10) // B2
158
+ sheet.setCellValue({ row: 2, col: 2 }, 5) // C3
159
+ sheet.setCellFormula(D1, '=SUM(B2:C3)')
160
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 15 })
161
+
162
+ sheet.mergeCells(parseRange('B2:C3')!) // C3 被清空
163
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 10 })
164
+
165
+ sheet.undo() // C3 恢复
166
+ expect(sheet.getCellData(D1)).toMatchObject({ v: 15 })
167
+ })
168
+
169
+ it('合并把非锚点公式格的值(含公式)搬迁到锚点,公式继续求值', () => {
170
+ const sheet = new Sheet()
171
+ sheet.setCellValue(A1, 5)
172
+ sheet.setCellFormula({ row: 2, col: 2 }, '=A1+1') // C3:包围盒内唯一有值格
173
+ sheet.mergeCells(parseRange('B2:C3')!)
174
+
175
+ // 值保留规则:C3 的数据(含公式原文)迁到锚点 B2
176
+ expect(sheet.getCellData({ row: 1, col: 1 })).toMatchObject({ f: 'A1+1', v: 6 })
177
+ sheet.setCellValue(A1, 7)
178
+ expect(sheet.getCellData({ row: 1, col: 1 })).toMatchObject({ v: 8 })
179
+ })
180
+ })
181
+
182
+ describe('对抗:其它边界', () => {
183
+ it('批量写入同一地址两次 + 公式依赖:undo 按逆序还原', () => {
184
+ const sheet = new Sheet()
185
+ sheet.setCellFormula(B1, '=A1*2')
186
+ sheet.setCells([
187
+ { addr: A1, data: { v: 1, t: 'n' } },
188
+ { addr: A1, data: { v: 2, t: 'n' } }
189
+ ])
190
+ expect(sheet.getCellData(A1)).toMatchObject({ v: 2 })
191
+ expect(sheet.getCellData(B1)).toMatchObject({ v: 4 })
192
+
193
+ sheet.undo()
194
+ expect(sheet.getCellData(A1)).toBeUndefined()
195
+ expect(sheet.getCellData(B1)).toMatchObject({ v: 0 })
196
+ })
197
+
198
+ it('数字文本参与算术(Excel 强转)', () => {
199
+ const sheet = new Sheet()
200
+ sheet.setCellValue(A1, '5')
201
+ sheet.setCellFormula(B1, '=A1+1')
202
+ expect(sheet.getCellData(B1)).toMatchObject({ v: 6 })
203
+ })
204
+
205
+ it('比较运算:文本大小写不敏感;混合类型 数字 < 文本 < 布尔', () => {
206
+ const sheet = new Sheet()
207
+ sheet.setCellFormula(A1, '="apple"="APPLE"')
208
+ expect(sheet.getCellData(A1)).toMatchObject({ v: true })
209
+ sheet.setCellFormula(B1, '=1<"a"')
210
+ expect(sheet.getCellData(B1)).toMatchObject({ v: true })
211
+ sheet.setCellFormula(C1, '="a"<TRUE')
212
+ expect(sheet.getCellData(C1)).toMatchObject({ v: true })
213
+ sheet.setCellFormula(D1, '=A1=B1')
214
+ expect(sheet.getCellData(D1)).toMatchObject({ v: true })
215
+ })
216
+
217
+ it('百分号作用于引用与表达式', () => {
218
+ const sheet = new Sheet()
219
+ sheet.setCellValue(A1, 50)
220
+ sheet.setCellFormula(B1, '=A1%')
221
+ expect(sheet.getCellData(B1)).toMatchObject({ v: 0.5 })
222
+ sheet.setCellFormula(C1, '=(20+30)%')
223
+ expect(sheet.getCellData(C1)).toMatchObject({ v: 0.5 })
224
+ })
225
+
226
+ it('removeSheet 清理被删表的公式节点', () => {
227
+ const workbook = new Workbook()
228
+ const sheet2 = workbook.addSheet('Sheet2')
229
+ sheet2.setCellFormula(A1, '=1+1')
230
+ sheet2.setCellFormula(B1, '=A1*2')
231
+ const before = workbook.formulaGraph.nodeCount
232
+ expect(before).toBe(2)
233
+
234
+ workbook.removeSheet('Sheet2')
235
+ expect(workbook.formulaGraph.nodeCount).toBe(0)
236
+ })
237
+
238
+ it('求值期抛错的自定义函数 → #ERROR!(不击穿重算)', () => {
239
+ registerFormulaFunction('__BOOM__', {
240
+ impl() {
241
+ throw new Error('boom')
242
+ }
243
+ })
244
+ const sheet = new Sheet()
245
+ sheet.setCellFormula(A1, '=__BOOM__()')
246
+ expect(sheet.getCellData(A1)).toMatchObject({ v: '#ERROR!', t: 'e' })
247
+ // 其它公式格不受影响
248
+ sheet.setCellFormula(B1, '=1+1')
249
+ expect(sheet.getCellData(B1)).toMatchObject({ v: 2 })
250
+ })
251
+
252
+ it('扩展注册表:自定义函数可正常使用', () => {
253
+ registerFormulaFunction('__DOUBLE__', {
254
+ minArgs: 1,
255
+ maxArgs: 1,
256
+ impl(args) {
257
+ const n = typeof args[0] === 'number' ? args[0] : 0
258
+ return n * 2
259
+ }
260
+ })
261
+ const sheet = new Sheet()
262
+ sheet.setCellFormula(A1, '=__DOUBLE__(21)')
263
+ expect(sheet.getCellData(A1)).toMatchObject({ v: 42 })
264
+ })
265
+ })