@veltra/sheet 2.0.0 → 2.1.1
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/index.d.ts +4 -25
- package/dist/index.js +3 -22
- package/dist/tools/builtin.js +279 -8
- package/dist/tools/context.d.ts +54 -11
- package/dist/tools/context.js +28 -5
- package/dist/tools/download.js +68 -0
- package/dist/tools/export.worker.d.ts +16 -0
- package/dist/tools/export.worker.js +29 -0
- package/dist/tools/registry.d.ts +7 -4
- package/dist/tools/registry.js +1 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/sheet.d.ts +6 -5
- package/dist/vue/formula-bar.js +393 -0
- package/dist/vue/formula-suggest-list.js +66 -0
- package/dist/vue/import-file.js +140 -0
- package/dist/vue/insert-count-menu-item.js +68 -0
- package/dist/vue/insert-image.js +79 -0
- package/dist/vue/popup-helpers.js +73 -0
- package/dist/vue/popups/border-popup.js +75 -0
- package/dist/vue/popups/border-preset-glyph.js +106 -0
- package/dist/vue/popups/export-popup.js +51 -0
- package/dist/vue/popups/fill-color-popup.js +37 -0
- package/dist/vue/popups/find-popup.js +113 -0
- package/dist/vue/popups/font-color-popup.js +37 -0
- package/dist/vue/popups/font-size-popup.js +63 -0
- package/dist/vue/popups/import.worker.d.ts +21 -0
- package/dist/vue/popups/import.worker.js +41 -0
- package/dist/vue/popups/insert-image-popup.js +38 -0
- package/dist/vue/sheet-context-menu.js +200 -0
- package/dist/vue/sheet-tabs.js +162 -0
- package/dist/vue/sheet-toolbar.js +106 -0
- package/dist/vue/sheet.js +144 -138
- package/dist/vue/style.d.ts +11 -1
- package/dist/vue/style.js +11 -0
- package/dist/vue/style2.css +564 -3
- package/dist/vue/use-find-replace.js +161 -0
- package/dist/vue/use-formula-ref-select.js +37 -0
- package/dist/vue/use-formula-suggest.js +126 -0
- package/dist/vue/use-sheet-grid.js +180 -0
- package/dist/vue/use-sheet-state.js +122 -0
- package/dist/vue/use-sheet-tabs-bar.js +90 -0
- package/dist/vue/use-tool-groups.js +42 -0
- package/dist/vue/use-tool-popup.js +123 -0
- package/dist/vue/use-toolbar-scroll.js +72 -0
- package/package.json +12 -6
- package/src/env.d.ts +14 -0
- package/src/index.ts +203 -13
- package/src/tools/__test__/builtin.test.ts +208 -66
- package/src/tools/__test__/context.test.ts +62 -2
- package/src/tools/builtin.ts +350 -6
- package/src/tools/context.ts +89 -11
- package/src/tools/download.ts +82 -0
- package/src/tools/export.worker.ts +61 -0
- package/src/tools/registry.ts +16 -1
- package/src/types/index.ts +6 -0
- package/src/types/sheet.ts +5 -3
- package/src/vue/__test__/formula-bar.test.ts +455 -0
- package/src/vue/__test__/import-file.test.ts +183 -0
- package/src/vue/__test__/insert-count-menu-item.test.ts +123 -0
- package/src/vue/__test__/insert-image-popup.test.ts +89 -0
- package/src/vue/__test__/insert-image.test.ts +100 -0
- package/src/vue/__test__/sheet-component.test.ts +716 -27
- package/src/vue/__test__/sheet-context-menu.test.ts +206 -0
- package/src/vue/__test__/use-formula-ref-select.test.ts +35 -0
- package/src/vue/__test__/use-formula-suggest.test.ts +83 -0
- package/src/vue/formula-bar.vue +473 -0
- package/src/vue/formula-suggest-list.vue +64 -0
- package/src/vue/import-file.ts +173 -0
- package/src/vue/insert-count-menu-item.vue +69 -0
- package/src/vue/insert-image.ts +90 -0
- package/src/vue/parsing.ts +11 -0
- package/src/vue/popup-helpers.ts +50 -0
- package/src/vue/popups/border-popup.vue +93 -0
- package/src/vue/popups/border-preset-glyph.vue +111 -0
- package/src/vue/popups/export-popup.vue +52 -0
- package/src/vue/popups/fill-color-popup.vue +39 -0
- package/src/vue/popups/find-popup.vue +111 -0
- package/src/vue/popups/font-color-popup.vue +39 -0
- package/src/vue/popups/font-size-popup.vue +52 -0
- package/src/vue/popups/import.worker.ts +66 -0
- package/src/vue/popups/insert-image-popup.vue +39 -0
- package/src/vue/sheet-context-menu.ts +229 -0
- package/src/vue/sheet-tabs.vue +162 -0
- package/src/vue/sheet-toolbar.vue +91 -0
- package/src/vue/sheet.vue +163 -148
- package/src/vue/style.scss +646 -3
- package/src/vue/style.ts +12 -0
- package/src/vue/use-find-replace.ts +200 -0
- package/src/vue/use-formula-ref-select.ts +34 -0
- package/src/vue/use-formula-suggest.ts +150 -0
- package/src/vue/use-sheet-grid.ts +243 -0
- package/src/vue/use-sheet-state.ts +161 -0
- package/src/vue/use-sheet-tabs-bar.ts +115 -0
- package/src/vue/use-tool-groups.ts +61 -0
- package/src/vue/use-tool-popup.ts +150 -0
- package/src/vue/use-toolbar-scroll.ts +95 -0
- package/dist/core/address.d.ts +0 -60
- package/dist/core/address.js +0 -142
- package/dist/core/address.js.map +0 -1
- package/dist/core/cell-store.d.ts +0 -61
- package/dist/core/cell-store.js +0 -135
- package/dist/core/cell-store.js.map +0 -1
- package/dist/core/command/default-registry.d.ts +0 -10
- package/dist/core/command/default-registry.js +0 -18
- package/dist/core/command/default-registry.js.map +0 -1
- package/dist/core/command/history.d.ts +0 -64
- package/dist/core/command/history.js +0 -127
- package/dist/core/command/history.js.map +0 -1
- package/dist/core/command/merge-cells.d.ts +0 -21
- package/dist/core/command/merge-cells.js +0 -74
- package/dist/core/command/merge-cells.js.map +0 -1
- package/dist/core/command/registry.d.ts +0 -17
- package/dist/core/command/registry.js +0 -26
- package/dist/core/command/registry.js.map +0 -1
- package/dist/core/command/set-cell-formula.d.ts +0 -17
- package/dist/core/command/set-cell-formula.js +0 -29
- package/dist/core/command/set-cell-formula.js.map +0 -1
- package/dist/core/command/set-cell-value.d.ts +0 -22
- package/dist/core/command/set-cell-value.js +0 -35
- package/dist/core/command/set-cell-value.js.map +0 -1
- package/dist/core/command/types.d.ts +0 -68
- package/dist/core/events.js +0 -29
- package/dist/core/events.js.map +0 -1
- package/dist/core/formula/ast.d.ts +0 -58
- package/dist/core/formula/ast.js +0 -38
- package/dist/core/formula/ast.js.map +0 -1
- package/dist/core/formula/dependency-graph.d.ts +0 -70
- package/dist/core/formula/dependency-graph.js +0 -269
- package/dist/core/formula/dependency-graph.js.map +0 -1
- package/dist/core/formula/errors.d.ts +0 -25
- package/dist/core/formula/errors.js +0 -36
- package/dist/core/formula/errors.js.map +0 -1
- package/dist/core/formula/evaluator.d.ts +0 -41
- package/dist/core/formula/evaluator.js +0 -154
- package/dist/core/formula/evaluator.js.map +0 -1
- package/dist/core/formula/functions.d.ts +0 -30
- package/dist/core/formula/functions.js +0 -209
- package/dist/core/formula/functions.js.map +0 -1
- package/dist/core/formula/parser.d.ts +0 -7
- package/dist/core/formula/parser.js +0 -208
- package/dist/core/formula/parser.js.map +0 -1
- package/dist/core/formula/tokenizer.d.ts +0 -37
- package/dist/core/formula/tokenizer.js +0 -146
- package/dist/core/formula/tokenizer.js.map +0 -1
- package/dist/core/merge-manager.d.ts +0 -64
- package/dist/core/merge-manager.js +0 -111
- package/dist/core/merge-manager.js.map +0 -1
- package/dist/core/selection.d.ts +0 -31
- package/dist/core/selection.js +0 -62
- package/dist/core/selection.js.map +0 -1
- package/dist/core/sheet.d.ts +0 -105
- package/dist/core/sheet.js +0 -202
- package/dist/core/sheet.js.map +0 -1
- package/dist/core/workbook.d.ts +0 -43
- package/dist/core/workbook.js +0 -77
- package/dist/core/workbook.js.map +0 -1
- package/dist/grid/sheet-grid.d.ts +0 -59
- package/dist/grid/sheet-grid.js +0 -230
- package/dist/grid/sheet-grid.js.map +0 -1
- package/dist/tools/builtin.js.map +0 -1
- package/dist/tools/context.js.map +0 -1
- package/dist/tools/registry.js.map +0 -1
- package/dist/vue/index.d.ts +0 -2
- package/dist/vue/sheet.js.map +0 -1
- package/dist/vue/sheet.vue.d.ts +0 -24
- package/src/core/__test__/address.test.ts +0 -113
- package/src/core/__test__/cell-store.test.ts +0 -97
- package/src/core/__test__/command.test.ts +0 -377
- package/src/core/__test__/formula-adversarial.test.ts +0 -265
- package/src/core/__test__/formula-engine.test.ts +0 -470
- package/src/core/__test__/formula-functions.test.ts +0 -178
- package/src/core/__test__/formula-parser.test.ts +0 -160
- package/src/core/__test__/history.test.ts +0 -225
- package/src/core/__test__/merge-manager.test.ts +0 -106
- package/src/core/__test__/sheet.test.ts +0 -142
- package/src/core/address.ts +0 -166
- package/src/core/cell-store.ts +0 -173
- package/src/core/command/default-registry.ts +0 -15
- package/src/core/command/history.ts +0 -167
- package/src/core/command/index.ts +0 -7
- package/src/core/command/merge-cells.ts +0 -74
- package/src/core/command/registry.ts +0 -30
- package/src/core/command/set-cell-formula.ts +0 -29
- package/src/core/command/set-cell-value.ts +0 -38
- package/src/core/command/types.ts +0 -73
- package/src/core/events.ts +0 -37
- package/src/core/formula/ast.ts +0 -66
- package/src/core/formula/dependency-graph.ts +0 -329
- package/src/core/formula/errors.ts +0 -41
- package/src/core/formula/evaluator.ts +0 -219
- package/src/core/formula/functions.ts +0 -266
- package/src/core/formula/index.ts +0 -7
- package/src/core/formula/parser.ts +0 -198
- package/src/core/formula/tokenizer.ts +0 -160
- package/src/core/merge-manager.ts +0 -158
- package/src/core/selection.ts +0 -66
- package/src/core/sheet.ts +0 -257
- package/src/core/workbook.ts +0 -99
- package/src/grid/__test__/canvas-mock.ts +0 -49
- package/src/grid/__test__/setup.ts +0 -3
- package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
- package/src/grid/__test__/sheet-grid.test.ts +0 -177
- package/src/grid/sheet-grid.ts +0 -283
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRange } from '@veltra/sheet-core/core/address'
|
|
2
2
|
|
|
3
3
|
import '../builtin'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { Sheet } from '@veltra/sheet-core/core/sheet'
|
|
5
|
+
import { Workbook } from '@veltra/sheet-core/core/workbook'
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
7
|
+
|
|
6
8
|
import { createSheetContext } from '../context'
|
|
9
|
+
import { exportSheetCsvFile, exportWorkbookFile } from '../download'
|
|
7
10
|
import { defaultToolRegistry, type SheetTool } from '../registry'
|
|
8
11
|
|
|
9
12
|
function mustGet(id: string): SheetTool {
|
|
@@ -17,90 +20,167 @@ function isDisabled(tool: SheetTool, sheet: Sheet): boolean {
|
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
describe('内置工具(dogfood 扩展机制)', () => {
|
|
20
|
-
it('注册布局:history
|
|
23
|
+
it('注册布局:history / cell / text / edit / insert / file 六组;structure/freeze 已移除', () => {
|
|
21
24
|
const groups = defaultToolRegistry.getGroups()
|
|
22
|
-
expect(groups.map((group) => group.name)).toEqual([
|
|
25
|
+
expect(groups.map((group) => group.name)).toEqual([
|
|
26
|
+
'history',
|
|
27
|
+
'cell',
|
|
28
|
+
'text',
|
|
29
|
+
'edit',
|
|
30
|
+
'insert',
|
|
31
|
+
'file'
|
|
32
|
+
])
|
|
23
33
|
expect(groups[0]!.tools.map((tool) => tool.id)).toEqual(['undo', 'redo'])
|
|
24
|
-
expect(groups[1]!.tools.map((tool) => tool.id)).toEqual([
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
expect(groups[1]!.tools.map((tool) => tool.id)).toEqual([
|
|
35
|
+
'border',
|
|
36
|
+
'fill-color',
|
|
37
|
+
'merge',
|
|
38
|
+
'unmerge'
|
|
39
|
+
])
|
|
40
|
+
expect(groups[2]!.tools.map((tool) => tool.id)).toEqual([
|
|
41
|
+
'bold',
|
|
42
|
+
'italic',
|
|
43
|
+
'underline',
|
|
44
|
+
'strikethrough',
|
|
45
|
+
'font-color',
|
|
46
|
+
'font-size',
|
|
47
|
+
'align-left',
|
|
48
|
+
'align-center',
|
|
49
|
+
'align-right',
|
|
50
|
+
'valign-top',
|
|
51
|
+
'valign-middle',
|
|
52
|
+
'valign-bottom',
|
|
53
|
+
'wrap-text'
|
|
54
|
+
])
|
|
55
|
+
expect(groups[3]!.tools.map((tool) => tool.id)).toEqual(['find'])
|
|
56
|
+
expect(groups[4]!.tools.map((tool) => tool.id)).toEqual(['insert-image'])
|
|
57
|
+
expect(groups[5]!.tools.map((tool) => tool.id)).toEqual(['import', 'export'])
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
59
|
+
for (const id of [
|
|
60
|
+
'insert-rows',
|
|
61
|
+
'insert-cols',
|
|
62
|
+
'delete-rows',
|
|
63
|
+
'delete-cols',
|
|
64
|
+
'freeze',
|
|
65
|
+
'freeze-row',
|
|
66
|
+
'freeze-col',
|
|
67
|
+
'unfreeze',
|
|
68
|
+
'export-xlsx',
|
|
69
|
+
'export-csv'
|
|
70
|
+
]) {
|
|
71
|
+
expect(defaultToolRegistry.has(id)).toBe(false)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
45
74
|
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
it('全部保留工具带 icon', () => {
|
|
76
|
+
for (const group of defaultToolRegistry.getGroups()) {
|
|
77
|
+
for (const tool of group.tools) {
|
|
78
|
+
expect(tool.icon, `${tool.id} 缺少 icon`).toBeTruthy()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
48
81
|
})
|
|
49
82
|
|
|
50
|
-
it('
|
|
83
|
+
it('导入导出工具:import 直接选文件、export 为弹层型;导出下载辅助不依赖选区', async () => {
|
|
51
84
|
const sheet = new Sheet()
|
|
52
85
|
const ctx = createSheetContext(sheet)
|
|
53
|
-
const
|
|
86
|
+
const exportTool = mustGet('export')
|
|
87
|
+
const importTool = mustGet('import')
|
|
54
88
|
|
|
55
|
-
|
|
56
|
-
expect(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
expect(importTool.popup).toBeUndefined()
|
|
90
|
+
expect(exportTool.popup).toBe('export')
|
|
91
|
+
// 无 workbook 的上下文:导出辅助空操作(不抛错)
|
|
92
|
+
expect(() => exportWorkbookFile(ctx)).not.toThrow()
|
|
93
|
+
expect(() => exportSheetCsvFile(ctx)).not.toThrow()
|
|
94
|
+
})
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
it('导出 xlsx / csv:生成 Blob 下载(workbook 传入时)', async () => {
|
|
97
|
+
const workbook = new Workbook()
|
|
98
|
+
workbook.activeSheet.setCellValue({ row: 0, col: 0 }, 42)
|
|
99
|
+
const ctx = createSheetContext(workbook.activeSheet, workbook)
|
|
66
100
|
|
|
67
|
-
|
|
68
|
-
|
|
101
|
+
// stub 浏览器下载 API(happy-dom 无 createObjectURL)
|
|
102
|
+
const originalCreate = URL.createObjectURL
|
|
103
|
+
const originalRevoke = URL.revokeObjectURL
|
|
104
|
+
const createObjectURL = vi.fn(() => 'blob:mock')
|
|
105
|
+
const revokeObjectURL = vi.fn()
|
|
106
|
+
URL.createObjectURL = createObjectURL as typeof URL.createObjectURL
|
|
107
|
+
URL.revokeObjectURL = revokeObjectURL as typeof URL.revokeObjectURL
|
|
108
|
+
try {
|
|
109
|
+
exportWorkbookFile(ctx)
|
|
110
|
+
await vi.waitFor(() => expect(createObjectURL).toHaveBeenCalled(), { timeout: 2000 })
|
|
111
|
+
const blob = createObjectURL.mock.calls[0]![0] as Blob
|
|
112
|
+
expect(blob.type).toContain('spreadsheetml')
|
|
69
113
|
|
|
70
|
-
|
|
71
|
-
|
|
114
|
+
createObjectURL.mockClear()
|
|
115
|
+
exportSheetCsvFile(ctx)
|
|
116
|
+
await vi.waitFor(() => expect(createObjectURL).toHaveBeenCalled(), { timeout: 2000 })
|
|
117
|
+
const csvBlob = createObjectURL.mock.calls[0]![0] as Blob
|
|
118
|
+
expect(csvBlob.type).toContain('text/csv')
|
|
119
|
+
} finally {
|
|
120
|
+
URL.createObjectURL = originalCreate
|
|
121
|
+
URL.revokeObjectURL = originalRevoke
|
|
122
|
+
}
|
|
123
|
+
})
|
|
72
124
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
125
|
+
it('导出 xlsx:sheet 名含 Excel 非法字符时 reject(hucre 1.0 写入校验,错误传播给调用方)', async () => {
|
|
126
|
+
const workbook = new Workbook()
|
|
127
|
+
workbook.renameSheet('Sheet1', 'a:b')
|
|
128
|
+
const ctx = createSheetContext(workbook.activeSheet, workbook)
|
|
129
|
+
await expect(exportWorkbookFile(ctx)).rejects.toThrow(/Sheet name/)
|
|
76
130
|
})
|
|
77
131
|
|
|
78
|
-
it('
|
|
132
|
+
it('样式工具:弹层型声明 + 无选区禁用;填充/边框面板不直接写(onClick 为空)', () => {
|
|
79
133
|
const sheet = new Sheet()
|
|
80
134
|
const ctx = createSheetContext(sheet)
|
|
81
|
-
const
|
|
135
|
+
const fill = mustGet('fill-color')
|
|
136
|
+
const border = mustGet('border')
|
|
82
137
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
138
|
+
expect(fill.popup).toBe('fill-color')
|
|
139
|
+
expect(border.popup).toBe('border')
|
|
140
|
+
// 默认 A1 有选区 → 可用
|
|
141
|
+
expect(fill.disabled?.(ctx)).toBe(false)
|
|
142
|
+
sheet.selection.clear()
|
|
143
|
+
expect(fill.disabled?.(ctx)).toBe(true)
|
|
144
|
+
expect(border.disabled?.(ctx)).toBe(true)
|
|
145
|
+
})
|
|
86
146
|
|
|
87
|
-
|
|
88
|
-
|
|
147
|
+
it('合并:单格禁用;选区恰等于既有合并时禁用;可合并时执行', () => {
|
|
148
|
+
const sheet = new Sheet()
|
|
149
|
+
const merge = mustGet('merge')
|
|
150
|
+
const unmerge = mustGet('unmerge')
|
|
89
151
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
expect(unmerge
|
|
152
|
+
// 默认 A1 单格 → 合并禁用、取消合并禁用
|
|
153
|
+
expect(isDisabled(merge, sheet)).toBe(true)
|
|
154
|
+
expect(isDisabled(unmerge, sheet)).toBe(true)
|
|
93
155
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
expect(
|
|
156
|
+
sheet.selectRange(createRange({ row: 0, col: 0 }, { row: 1, col: 1 }))
|
|
157
|
+
expect(isDisabled(merge, sheet)).toBe(false)
|
|
158
|
+
merge.onClick(createSheetContext(sheet))
|
|
159
|
+
expect(sheet.getCellInfo({ row: 0, col: 0 }).mergeRange).toEqual(
|
|
160
|
+
createRange({ row: 0, col: 0 }, { row: 1, col: 1 })
|
|
161
|
+
)
|
|
162
|
+
// 选区恰等于合并 → 合并禁用;取消合并可用
|
|
163
|
+
expect(isDisabled(merge, sheet)).toBe(true)
|
|
164
|
+
expect(isDisabled(unmerge, sheet)).toBe(false)
|
|
165
|
+
unmerge.onClick(createSheetContext(sheet))
|
|
166
|
+
expect(sheet.getCellInfo({ row: 0, col: 0 }).mergeRange).toBeUndefined()
|
|
167
|
+
})
|
|
98
168
|
|
|
99
|
-
|
|
100
|
-
|
|
169
|
+
it('undo/redo:disabled 读 canUndo/canRedo', () => {
|
|
170
|
+
const sheet = new Sheet()
|
|
171
|
+
const ctx = createSheetContext(sheet)
|
|
172
|
+
const undo = mustGet('undo')
|
|
173
|
+
const redo = mustGet('redo')
|
|
101
174
|
|
|
102
|
-
ctx.
|
|
103
|
-
expect(
|
|
175
|
+
expect(undo.disabled?.(ctx)).toBe(true)
|
|
176
|
+
expect(redo.disabled?.(ctx)).toBe(true)
|
|
177
|
+
sheet.setCellValue({ row: 0, col: 0 }, 'x')
|
|
178
|
+
expect(undo.disabled?.(ctx)).toBe(false)
|
|
179
|
+
undo.onClick(ctx)
|
|
180
|
+
expect(sheet.getCellData({ row: 0, col: 0 })).toBeUndefined()
|
|
181
|
+
expect(redo.disabled?.(ctx)).toBe(false)
|
|
182
|
+
redo.onClick(ctx)
|
|
183
|
+
expect(sheet.getCellData({ row: 0, col: 0 })).toEqual({ v: 'x', t: 's' })
|
|
104
184
|
})
|
|
105
185
|
|
|
106
186
|
it('visible 随选区联动(自定义工具)', () => {
|
|
@@ -119,10 +199,72 @@ describe('内置工具(dogfood 扩展机制)', () => {
|
|
|
119
199
|
onClick: () => {}
|
|
120
200
|
}
|
|
121
201
|
|
|
122
|
-
|
|
123
|
-
ctx.selectCell({ row: 1, col: 1 })
|
|
202
|
+
// 默认 A1 落在合并内 → 可见;移出后不可见;再点覆盖格仍可见
|
|
124
203
|
expect(tool.visible?.(ctx)).toBe(true)
|
|
125
204
|
ctx.selectCell({ row: 3, col: 3 })
|
|
126
205
|
expect(tool.visible?.(ctx)).toBe(false)
|
|
206
|
+
ctx.selectCell({ row: 1, col: 1 })
|
|
207
|
+
expect(tool.visible?.(ctx)).toBe(true)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('查找工具:弹层型声明(popup: find),组 edit', () => {
|
|
211
|
+
const find = mustGet('find')
|
|
212
|
+
expect(find.popup).toBe('find')
|
|
213
|
+
expect(find.group).toBe('edit')
|
|
214
|
+
expect(find.disabled).toBeUndefined()
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('插入图片工具:弹层型声明(popup: insert-image),组 insert;无选区禁用', () => {
|
|
218
|
+
const sheet = new Sheet()
|
|
219
|
+
const ctx = createSheetContext(sheet)
|
|
220
|
+
const tool = mustGet('insert-image')
|
|
221
|
+
expect(tool.popup).toBe('insert-image')
|
|
222
|
+
expect(tool.group).toBe('insert')
|
|
223
|
+
expect(tool.disabled?.(ctx)).toBe(false)
|
|
224
|
+
sheet.selection.clear()
|
|
225
|
+
expect(tool.disabled?.(ctx)).toBe(true)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('文本工具:toggle 以活动格为基准统一翻转;active 高亮;对齐互斥取反', () => {
|
|
229
|
+
const sheet = new Sheet()
|
|
230
|
+
const ctx = createSheetContext(sheet)
|
|
231
|
+
const bold = mustGet('bold')
|
|
232
|
+
const alignCenter = mustGet('align-center')
|
|
233
|
+
const wrap = mustGet('wrap-text')
|
|
234
|
+
|
|
235
|
+
expect(mustGet('font-color').popup).toBe('font-color')
|
|
236
|
+
expect(mustGet('font-size').popup).toBe('font-size')
|
|
237
|
+
|
|
238
|
+
// 默认 A1:active 假;点 B → 加粗 + active
|
|
239
|
+
expect(bold.active?.(ctx)).toBe(false)
|
|
240
|
+
bold.onClick(ctx)
|
|
241
|
+
expect(sheet.getCellStyle({ row: 0, col: 0 })?.font?.bold).toBe(true)
|
|
242
|
+
expect(bold.active?.(ctx)).toBe(true)
|
|
243
|
+
// 再点 → 取消
|
|
244
|
+
bold.onClick(ctx)
|
|
245
|
+
expect(sheet.getCellStyle({ row: 0, col: 0 })).toBeUndefined()
|
|
246
|
+
expect(bold.active?.(ctx)).toBe(false)
|
|
247
|
+
|
|
248
|
+
// 混合选区:活动格未加粗 → 全加粗
|
|
249
|
+
sheet.setCellStyle(createRange({ row: 0, col: 0 }, { row: 0, col: 0 }), {
|
|
250
|
+
font: { bold: true }
|
|
251
|
+
})
|
|
252
|
+
sheet.setCellValue({ row: 0, col: 1 }, 'x')
|
|
253
|
+
ctx.selectRange(createRange({ row: 0, col: 0 }, { row: 0, col: 1 }), { row: 0, col: 1 })
|
|
254
|
+
expect(bold.active?.(ctx)).toBe(false) // 活动格 B1 未加粗
|
|
255
|
+
bold.onClick(ctx)
|
|
256
|
+
expect(sheet.getCellStyle({ row: 0, col: 0 })?.font?.bold).toBe(true)
|
|
257
|
+
expect(sheet.getCellStyle({ row: 0, col: 1 })?.font?.bold).toBe(true)
|
|
258
|
+
|
|
259
|
+
// 对齐:点居中 → 再点清除
|
|
260
|
+
alignCenter.onClick(ctx)
|
|
261
|
+
expect(sheet.getCellStyle({ row: 0, col: 1 })?.align?.horizontal).toBe('center')
|
|
262
|
+
expect(alignCenter.active?.(ctx)).toBe(true)
|
|
263
|
+
alignCenter.onClick(ctx)
|
|
264
|
+
expect(sheet.getCellStyle({ row: 0, col: 1 })?.align?.horizontal).toBeUndefined()
|
|
265
|
+
|
|
266
|
+
wrap.onClick(ctx)
|
|
267
|
+
expect(sheet.getCellStyle({ row: 0, col: 1 })?.align?.wrap).toBe(true)
|
|
268
|
+
expect(wrap.active?.(ctx)).toBe(true)
|
|
127
269
|
})
|
|
128
270
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { createRange } from '@veltra/sheet-core/core/address'
|
|
2
|
+
import { Sheet } from '@veltra/sheet-core/core/sheet'
|
|
1
3
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
4
|
|
|
3
|
-
import { createRange } from '../../core/address'
|
|
4
|
-
import { Sheet } from '../../core/sheet'
|
|
5
5
|
import { createSheetContext } from '../context'
|
|
6
6
|
import type { SheetTool } from '../registry'
|
|
7
7
|
|
|
@@ -91,6 +91,31 @@ describe('SheetContext', () => {
|
|
|
91
91
|
expect(sheet.getCellData({ row: 1, col: 1 })).toBeUndefined()
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
+
it('样式门面:setCellStyle / clearCellStyle / getCellStyle,经命令可 undo', () => {
|
|
95
|
+
const sheet = new Sheet()
|
|
96
|
+
const ctx = createSheetContext(sheet)
|
|
97
|
+
|
|
98
|
+
const range = createRange({ row: 0, col: 0 }, { row: 1, col: 1 })
|
|
99
|
+
ctx.setCellStyle(range, { fill: { color: '#FF0000' } })
|
|
100
|
+
expect(ctx.getCellStyle({ row: 0, col: 0 })).toEqual({ fill: { color: '#FF0000' } })
|
|
101
|
+
expect(sheet.stylePool.size).toBe(1)
|
|
102
|
+
|
|
103
|
+
// 部分合并:只设边框保留填充
|
|
104
|
+
ctx.setCellStyle(range, { border: { top: { style: 'thin', width: 1, color: '#000000' } } })
|
|
105
|
+
expect(ctx.getCellStyle({ row: 1, col: 1 })).toEqual({
|
|
106
|
+
fill: { color: '#FF0000' },
|
|
107
|
+
border: { top: { style: 'thin', width: 1, color: '#000000' } }
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
ctx.clearCellStyle(range)
|
|
111
|
+
expect(ctx.getCellStyle({ row: 0, col: 0 })).toBeUndefined()
|
|
112
|
+
|
|
113
|
+
// 一次 undo 回退到带边框填充状态
|
|
114
|
+
expect(ctx.undo()).toBe(true)
|
|
115
|
+
expect(ctx.getCellStyle({ row: 0, col: 0 })?.border).toBeDefined()
|
|
116
|
+
expect(ctx.getCellStyle({ row: 0, col: 0 })?.fill?.color).toBe('#FF0000')
|
|
117
|
+
})
|
|
118
|
+
|
|
94
119
|
it('动态解析器:tab 切换后同一上下文指向新的活动 sheet', () => {
|
|
95
120
|
const sheet1 = new Sheet('Sheet1')
|
|
96
121
|
const sheet2 = new Sheet('Sheet2')
|
|
@@ -154,4 +179,39 @@ describe('SheetContext', () => {
|
|
|
154
179
|
expect(ctx.undo()).toBe(true)
|
|
155
180
|
expect(sheet.getCellData({ row: 0, col: 0 })).toEqual({ v: 'keep', t: 's' })
|
|
156
181
|
})
|
|
182
|
+
|
|
183
|
+
it('图片门面:insertImage / removeImage / updateImage / getImages / onImageChange,经命令可 undo', () => {
|
|
184
|
+
const sheet = new Sheet()
|
|
185
|
+
const ctx = createSheetContext(sheet)
|
|
186
|
+
const onImage = vi.fn()
|
|
187
|
+
const off = ctx.onImageChange(onImage)
|
|
188
|
+
|
|
189
|
+
const data = new Uint8Array([1, 2, 3])
|
|
190
|
+
const id = ctx.insertImage({ data, type: 'png', anchor: { from: { row: 1, col: 2 } } })
|
|
191
|
+
expect(id).toBeTruthy()
|
|
192
|
+
expect(ctx.getImages()).toHaveLength(1)
|
|
193
|
+
expect(ctx.getImages()[0]?.anchor.from).toEqual({ row: 1, col: 2 })
|
|
194
|
+
expect(onImage).toHaveBeenCalledWith({ id })
|
|
195
|
+
|
|
196
|
+
ctx.updateImage(id, { anchor: { from: { row: 3, col: 4 } } })
|
|
197
|
+
expect(ctx.getImages()[0]?.anchor.from).toEqual({ row: 3, col: 4 })
|
|
198
|
+
expect(onImage).toHaveBeenCalledWith({ id })
|
|
199
|
+
|
|
200
|
+
ctx.removeImage(id)
|
|
201
|
+
expect(ctx.getImages()).toHaveLength(0)
|
|
202
|
+
expect(onImage).toHaveBeenCalledWith({ id })
|
|
203
|
+
|
|
204
|
+
expect(ctx.undo()).toBe(true)
|
|
205
|
+
expect(ctx.getImages()).toHaveLength(1)
|
|
206
|
+
expect(ctx.getImages()[0]?.anchor.from).toEqual({ row: 3, col: 4 })
|
|
207
|
+
|
|
208
|
+
expect(ctx.undo()).toBe(true)
|
|
209
|
+
expect(ctx.getImages()[0]?.anchor.from).toEqual({ row: 1, col: 2 })
|
|
210
|
+
|
|
211
|
+
off()
|
|
212
|
+
onImage.mockClear()
|
|
213
|
+
ctx.removeImage(id)
|
|
214
|
+
expect(ctx.getImages()).toHaveLength(0)
|
|
215
|
+
expect(onImage).not.toHaveBeenCalled()
|
|
216
|
+
})
|
|
157
217
|
})
|