@veltra/sheet 2.1.1 → 2.3.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 (212) hide show
  1. package/dist/components/report/binding-badge.js +100 -0
  2. package/dist/components/report/designer/cell-coords.js +46 -0
  3. package/dist/components/report/designer/col-widths.js +29 -0
  4. package/dist/components/report/designer/conditional-rules/helpers.js +139 -0
  5. package/dist/components/report/designer/drop-highlight-overlay.js +76 -0
  6. package/dist/components/report/designer/float-panel.js +422 -0
  7. package/dist/components/report/designer/role.js +17 -0
  8. package/dist/components/report/designer/rule-preview.js +33 -0
  9. package/dist/components/report/designer/rule-row.js +253 -0
  10. package/dist/components/report/designer/rules-dialog.js +118 -0
  11. package/dist/components/report/designer/topology-overlay.js +122 -0
  12. package/dist/components/report/designer/topology.js +64 -0
  13. package/dist/components/report/designer/use-grid-overlay.js +75 -0
  14. package/dist/components/report/designer-hub.js +177 -0
  15. package/dist/components/report/field-panel-helpers.js +33 -0
  16. package/dist/components/report/field-panel.js +132 -0
  17. package/dist/components/report/filter-bar.js +74 -0
  18. package/dist/components/report/hub-connection-form.js +145 -0
  19. package/dist/components/report/hub-dataset-editor.js +394 -0
  20. package/dist/components/report/preview-grid-size.js +32 -0
  21. package/dist/components/report/report-designer.js +393 -0
  22. package/dist/components/report/report-viewer.js +124 -0
  23. package/dist/components/report/style.d.ts +17 -0
  24. package/dist/components/report/style.js +18 -0
  25. package/dist/components/report/style2.css +874 -0
  26. package/dist/components/report/use-report-designer.js +382 -0
  27. package/dist/components/report/use-report-viewer.js +55 -0
  28. package/dist/{vue → components/sheet}/formula-bar.js +1 -1
  29. package/dist/{vue → components/sheet}/formula-suggest-list.js +1 -1
  30. package/dist/{vue → components/sheet}/import-file.js +1 -1
  31. package/dist/{vue → components/sheet}/insert-count-menu-item.js +1 -1
  32. package/dist/{vue → components/sheet}/insert-image.js +1 -1
  33. package/dist/{vue → components/sheet}/popup-helpers.js +1 -1
  34. package/dist/{vue → components/sheet}/popups/border-popup.js +31 -3
  35. package/dist/{vue → components/sheet}/popups/border-preset-glyph.js +1 -1
  36. package/dist/{vue → components/sheet}/popups/export-popup.js +2 -2
  37. package/dist/{vue → components/sheet}/popups/fill-color-popup.js +3 -3
  38. package/dist/{vue → components/sheet}/popups/find-popup.js +1 -1
  39. package/dist/{vue → components/sheet}/popups/font-color-popup.js +3 -3
  40. package/dist/{vue → components/sheet}/popups/font-size-popup.js +3 -3
  41. package/dist/{vue → components/sheet}/popups/import.worker.d.ts +1 -1
  42. package/dist/{vue → components/sheet}/popups/import.worker.js +1 -1
  43. package/dist/{vue → components/sheet}/popups/insert-image-popup.js +1 -1
  44. package/dist/{vue → components/sheet}/sheet-context-menu.js +7 -4
  45. package/dist/{vue → components/sheet}/sheet-tabs.js +1 -1
  46. package/dist/{vue → components/sheet}/sheet-toolbar.js +1 -1
  47. package/dist/{vue → components/sheet}/sheet.js +16 -1
  48. package/dist/{vue → components/sheet}/use-find-replace.js +1 -1
  49. package/dist/{vue → components/sheet}/use-formula-ref-select.js +1 -1
  50. package/dist/{vue → components/sheet}/use-sheet-grid.js +22 -4
  51. package/dist/{vue → components/sheet}/use-sheet-state.js +6 -5
  52. package/dist/{vue → components/sheet}/use-sheet-tabs-bar.js +1 -1
  53. package/dist/{vue → components/sheet}/use-tool-groups.js +2 -2
  54. package/dist/{vue → components/sheet}/use-tool-popup.js +2 -2
  55. package/dist/{vue → components/sheet}/use-toolbar-scroll.js +1 -1
  56. package/dist/index.d.ts +17 -3
  57. package/dist/index.js +13 -3
  58. package/dist/report/binding.d.ts +83 -0
  59. package/dist/report/binding.js +332 -0
  60. package/dist/report/connector.d.ts +66 -0
  61. package/dist/report/connector.js +159 -0
  62. package/dist/report/export-xlsx.d.ts +17 -0
  63. package/dist/report/export-xlsx.js +92 -0
  64. package/dist/report/filter-bar.d.ts +10 -0
  65. package/dist/report/filter-bar.js +31 -0
  66. package/dist/report/index.d.ts +12 -0
  67. package/dist/report/params.d.ts +13 -0
  68. package/dist/report/params.js +61 -0
  69. package/dist/report/render/aggregate.js +169 -0
  70. package/dist/report/render/builder.d.ts +12 -0
  71. package/dist/report/render/builder.js +139 -0
  72. package/dist/report/render/coordinate.d.ts +25 -0
  73. package/dist/report/render/coordinate.js +733 -0
  74. package/dist/report/render/index.d.ts +14 -0
  75. package/dist/report/render/index.js +21 -0
  76. package/dist/report/render/style-resolver.js +50 -0
  77. package/dist/report/render/template-index.d.ts +24 -0
  78. package/dist/report/render/template-index.js +137 -0
  79. package/dist/report/rules/evaluate.d.ts +25 -0
  80. package/dist/report/rules/evaluate.js +82 -0
  81. package/dist/report/rules/index.d.ts +2 -0
  82. package/dist/report/template.d.ts +63 -0
  83. package/dist/report/template.js +83 -0
  84. package/dist/report/types.d.ts +85 -0
  85. package/dist/tools/apply-style.js +29 -0
  86. package/dist/tools/builtin.js +17 -17
  87. package/dist/tools/context.d.ts +39 -6
  88. package/dist/tools/context.js +38 -1
  89. package/dist/types/index.d.ts +3 -2
  90. package/dist/types/report-designer.d.ts +38 -0
  91. package/dist/types/report-viewer.d.ts +38 -0
  92. package/dist/types/sheet.d.ts +21 -1
  93. package/package.json +6 -8
  94. package/src/components/report/__test__/conditional-rules-helpers.test.ts +88 -0
  95. package/src/components/report/__test__/field-panel-helpers.test.ts +65 -0
  96. package/src/components/report/__test__/float-panel-position.test.ts +45 -0
  97. package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
  98. package/src/components/report/__test__/report-designer.test.ts +464 -0
  99. package/src/components/report/__test__/report-viewer.test.ts +357 -0
  100. package/src/components/report/__test__/topology.test.ts +143 -0
  101. package/src/components/report/__test__/use-report-designer.test.ts +697 -0
  102. package/src/components/report/binding-badge.ts +92 -0
  103. package/src/components/report/designer/cell-coords.ts +94 -0
  104. package/src/components/report/designer/col-widths.ts +56 -0
  105. package/src/components/report/designer/conditional-rules/helpers.ts +189 -0
  106. package/src/components/report/designer/drop-highlight-overlay.vue +74 -0
  107. package/src/components/report/designer/float-panel.vue +431 -0
  108. package/src/components/report/designer/role.ts +32 -0
  109. package/src/components/report/designer/rule-preview.vue +40 -0
  110. package/src/components/report/designer/rule-row.vue +252 -0
  111. package/src/components/report/designer/rules-dialog.vue +110 -0
  112. package/src/components/report/designer/topology-overlay.vue +127 -0
  113. package/src/components/report/designer/topology.ts +124 -0
  114. package/src/components/report/designer/use-grid-overlay.ts +113 -0
  115. package/src/components/report/designer-hub.vue +198 -0
  116. package/src/components/report/field-panel-helpers.ts +44 -0
  117. package/src/components/report/field-panel.vue +136 -0
  118. package/src/components/report/filter-bar.vue +77 -0
  119. package/src/components/report/hub-connection-form.vue +124 -0
  120. package/src/components/report/hub-dataset-editor.vue +369 -0
  121. package/src/components/report/index.ts +2 -0
  122. package/src/components/report/preview-grid-size.ts +33 -0
  123. package/src/components/report/report-designer.vue +420 -0
  124. package/src/components/report/report-viewer.vue +136 -0
  125. package/src/components/report/style-designer.scss +587 -0
  126. package/src/components/report/style-hub.scss +292 -0
  127. package/src/components/report/style-viewer.scss +104 -0
  128. package/src/components/report/style.scss +3 -0
  129. package/src/components/report/style.ts +20 -0
  130. package/src/components/report/use-report-designer.ts +596 -0
  131. package/src/components/report/use-report-viewer.ts +88 -0
  132. package/src/{vue → components/sheet}/__test__/formula-bar.test.ts +37 -18
  133. package/src/{vue → components/sheet}/__test__/insert-image-popup.test.ts +1 -1
  134. package/src/{vue → components/sheet}/__test__/insert-image.test.ts +1 -1
  135. package/src/{vue → components/sheet}/__test__/sheet-component.test.ts +77 -3
  136. package/src/{vue → components/sheet}/__test__/sheet-context-menu.test.ts +4 -3
  137. package/src/{vue → components/sheet}/formula-bar.vue +1 -1
  138. package/src/{vue → components/sheet}/insert-image.ts +1 -1
  139. package/src/{vue → components/sheet}/popup-helpers.ts +1 -1
  140. package/src/{vue → components/sheet}/popups/border-popup.vue +32 -4
  141. package/src/{vue → components/sheet}/popups/export-popup.vue +2 -2
  142. package/src/{vue → components/sheet}/popups/fill-color-popup.vue +3 -3
  143. package/src/{vue → components/sheet}/popups/find-popup.vue +1 -1
  144. package/src/{vue → components/sheet}/popups/font-color-popup.vue +3 -3
  145. package/src/{vue → components/sheet}/popups/font-size-popup.vue +3 -3
  146. package/src/{vue → components/sheet}/popups/insert-image-popup.vue +1 -1
  147. package/src/{vue → components/sheet}/sheet-context-menu.ts +10 -4
  148. package/src/{vue → components/sheet}/sheet-toolbar.vue +2 -2
  149. package/src/{vue → components/sheet}/sheet.vue +6 -3
  150. package/src/{vue → components/sheet}/use-find-replace.ts +1 -1
  151. package/src/{vue → components/sheet}/use-sheet-grid.ts +23 -5
  152. package/src/{vue → components/sheet}/use-sheet-state.ts +11 -6
  153. package/src/{vue → components/sheet}/use-tool-groups.ts +2 -2
  154. package/src/{vue → components/sheet}/use-tool-popup.ts +2 -2
  155. package/src/index.ts +24 -188
  156. package/src/report/__test__/binding.test.ts +320 -0
  157. package/src/report/__test__/connector.test.ts +239 -0
  158. package/src/report/__test__/export-xlsx.test.ts +118 -0
  159. package/src/report/__test__/filter-bar.test.ts +58 -0
  160. package/src/report/__test__/params.test.ts +156 -0
  161. package/src/report/__test__/render.test.ts +830 -0
  162. package/src/report/__test__/rules.test.ts +131 -0
  163. package/src/report/__test__/template.test.ts +366 -0
  164. package/src/report/binding.ts +474 -0
  165. package/src/report/connector.ts +206 -0
  166. package/src/report/export-xlsx.ts +113 -0
  167. package/src/report/filter-bar.ts +36 -0
  168. package/src/report/index.ts +13 -0
  169. package/src/report/params.ts +96 -0
  170. package/src/report/render/__test__/aggregate.test.ts +16 -0
  171. package/src/report/render/__test__/coordinate.test.ts +405 -0
  172. package/src/report/render/aggregate.ts +261 -0
  173. package/src/report/render/builder.ts +211 -0
  174. package/src/report/render/coordinate.ts +1239 -0
  175. package/src/report/render/index.ts +30 -0
  176. package/src/report/render/style-resolver.ts +68 -0
  177. package/src/report/render/template-index.ts +200 -0
  178. package/src/report/rules/evaluate.ts +113 -0
  179. package/src/report/rules/index.ts +7 -0
  180. package/src/report/template.ts +145 -0
  181. package/src/report/types.ts +96 -0
  182. package/src/tools/__test__/apply-style.test.ts +129 -0
  183. package/src/tools/apply-style.ts +41 -0
  184. package/src/tools/builtin.ts +20 -20
  185. package/src/tools/context.ts +67 -4
  186. package/src/tools/index.ts +1 -0
  187. package/src/types/index.ts +2 -6
  188. package/src/types/report-designer.ts +38 -0
  189. package/src/types/report-viewer.ts +37 -0
  190. package/src/types/sheet.ts +26 -1
  191. /package/dist/{vue → components/sheet}/style.d.ts +0 -0
  192. /package/dist/{vue → components/sheet}/style.js +0 -0
  193. /package/dist/{vue → components/sheet}/style2.css +0 -0
  194. /package/dist/{vue → components/sheet}/use-formula-suggest.js +0 -0
  195. /package/src/{vue → components/sheet}/__test__/import-file.test.ts +0 -0
  196. /package/src/{vue → components/sheet}/__test__/insert-count-menu-item.test.ts +0 -0
  197. /package/src/{vue → components/sheet}/__test__/use-formula-ref-select.test.ts +0 -0
  198. /package/src/{vue → components/sheet}/__test__/use-formula-suggest.test.ts +0 -0
  199. /package/src/{vue → components/sheet}/formula-suggest-list.vue +0 -0
  200. /package/src/{vue → components/sheet}/import-file.ts +0 -0
  201. /package/src/{vue → components/sheet}/index.ts +0 -0
  202. /package/src/{vue → components/sheet}/insert-count-menu-item.vue +0 -0
  203. /package/src/{vue → components/sheet}/parsing.ts +0 -0
  204. /package/src/{vue → components/sheet}/popups/border-preset-glyph.vue +0 -0
  205. /package/src/{vue → components/sheet}/popups/import.worker.ts +0 -0
  206. /package/src/{vue → components/sheet}/sheet-tabs.vue +0 -0
  207. /package/src/{vue → components/sheet}/style.scss +0 -0
  208. /package/src/{vue → components/sheet}/style.ts +0 -0
  209. /package/src/{vue → components/sheet}/use-formula-ref-select.ts +0 -0
  210. /package/src/{vue → components/sheet}/use-formula-suggest.ts +0 -0
  211. /package/src/{vue → components/sheet}/use-sheet-tabs-bar.ts +0 -0
  212. /package/src/{vue → components/sheet}/use-toolbar-scroll.ts +0 -0
package/src/index.ts CHANGED
@@ -3,192 +3,10 @@
3
3
  // 无头场景不涉及
4
4
  import './tools/builtin'
5
5
 
6
- // 数据模型 / 渲染内核(原 src/core、src/grid)已迁至 @veltra/sheet-core,
7
- // 下方 core/grid 来源的导出全部为其主入口白名单的 re-export(公开 API 不变)。
8
- // 聚合导出为**公开 API 白名单**:仅测试引用的内部符号(如 rangeContainsRange、
9
- // mergeCellStyle、shiftFormulaRefs、io 转换函数、grid 样式映射、normalizeStyle、
10
- // ToolRegistry 类等,见 #31)不在此导出,测试一律深导入 @veltra/sheet-core 子路径。
11
- // 类成员方法(Sheet.setCell / setCellStyles、CellStore.setCellValue 等)随类整体
12
- // 导出,属内部实现细节,见 packages/sheet/AGENTS.md「核心语义」注。
13
-
14
- export {
15
- cellKey,
16
- colIndexToName,
17
- colNameToIndex,
18
- parseAddress,
19
- formatAddress,
20
- createRange,
21
- parseRange,
22
- formatRange,
23
- rangesEqual,
24
- rangesIntersect,
25
- rangeContainsAddress,
26
- boundingBox,
27
- iterateRange,
28
- type CellAddress,
29
- type CellRange
30
- } from '@veltra/sheet-core'
31
-
32
- export {
33
- NUMERIC_TEXT_RE,
34
- normalizeInputValue,
35
- inferCellType,
36
- isEmptyCellData,
37
- cellDataEqual,
38
- CellStore,
39
- type CellType,
40
- type CellValue,
41
- type CellData,
42
- type CellSnapshotItem
43
- } from '@veltra/sheet-core'
44
-
45
- export {
46
- computeFillTargetRange,
47
- generateFill,
48
- type FillDirection,
49
- type GenerateFillOptions
50
- } from '@veltra/sheet-core'
51
-
52
- export {
53
- findAll,
54
- findNext,
55
- findNextFrom,
56
- findPrev,
57
- findPrevFrom,
58
- type FindOptions,
59
- type FindMatch
60
- } from '@veltra/sheet-core'
61
-
62
- export {
63
- MergeManager,
64
- type MergedCellKind,
65
- type CellInfo,
66
- type MergeResult
67
- } from '@veltra/sheet-core'
68
-
69
- export { SelectionModel, type SelectionState } from '@veltra/sheet-core'
70
-
71
- export { Sheet, type FrozenState, type SheetSnapshot, type SheetEvents } from '@veltra/sheet-core'
72
-
73
- export {
74
- createImageId,
75
- cloneImageAnchor,
76
- cloneSheetImage,
77
- type SheetImageType,
78
- type SheetImageAnchor,
79
- type SheetImage,
80
- type ImageInput
81
- } from '@veltra/sheet-core'
82
-
83
- export {
84
- BORDER_SIDES,
85
- BORDER_STYLE_WIDTH,
86
- BORDER_EDGE_DEFAULTS,
87
- FONT_STYLE_KEYS,
88
- ALIGN_STYLE_KEYS,
89
- type BorderLineStyle,
90
- type BorderSide,
91
- type BorderEdge,
92
- type HorizontalAlign,
93
- type VerticalAlign,
94
- type CellFont,
95
- type CellAlign,
96
- type CellStyle,
97
- type StyleId,
98
- type CellStylePatch
99
- } from '@veltra/sheet-core'
100
-
101
- export { StylePool } from '@veltra/sheet-core'
102
-
103
- export {
104
- buildBorderPresetItems,
105
- type BorderPreset,
106
- type BorderPresetItem
107
- } from '@veltra/sheet-core'
108
-
109
- export { Workbook, type WorkbookEvents } from '@veltra/sheet-core'
110
-
111
- export {
112
- HistoryManager,
113
- type HistoryState,
114
- type PatchDirection,
115
- type CellPatch,
116
- type MergePatch,
117
- type StructureChange,
118
- type StructurePatch,
119
- type SnapshotPatch,
120
- type ImagePatch,
121
- type Patch,
122
- type Mutation,
123
- type CommandResult,
124
- type CommandContext,
125
- type Command,
126
- type SetCellValueItem,
127
- type SetCellValueParams,
128
- SetCellValueCommand,
129
- type SetCellFormulaParams,
130
- SetCellFormulaCommand,
131
- type SetCellStyleItem,
132
- type SetCellStyleParams,
133
- SetCellStyleCommand,
134
- type InsertCellsParams,
135
- InsertCellsCommand,
136
- type MergeCellsParams,
137
- MergeCellsCommand,
138
- type UnmergeCellsParams,
139
- UnmergeCellsCommand,
140
- type InsertImageParams,
141
- InsertImageCommand,
142
- type RemoveImageParams,
143
- RemoveImageCommand,
144
- type ImageUpdateFields,
145
- type UpdateImageParams,
146
- UpdateImageCommand,
147
- defaultCommandRegistry
148
- } from '@veltra/sheet-core'
149
-
150
- export {
151
- FORMULA_ERROR_CODES,
152
- formulaError,
153
- isFormulaError,
154
- isFormulaErrorCode,
155
- type FormulaErrorCode,
156
- type FormulaError,
157
- type BinaryOperator,
158
- type AstNode,
159
- type AstReference,
160
- collectReferences,
161
- FormulaParseError,
162
- tokenizeFormula,
163
- type FormulaOperator,
164
- type FormulaToken,
165
- parseFormula,
166
- type ScalarValue,
167
- type EvalValue,
168
- type FormulaEvalContext,
169
- coerceToNumber,
170
- coerceToText,
171
- coerceToBoolean,
172
- evaluateAst,
173
- registerFormulaFunction,
174
- listFormulaFunctions,
175
- invokeFormulaFunction,
176
- type FormulaFunctionMeta,
177
- type FormulaFunction,
178
- type FormulaDependency,
179
- type FormulaNode,
180
- DependencyGraph
181
- } from '@veltra/sheet-core'
182
-
183
- export { exportWorkbookXlsx, exportSheetCsv } from '@veltra/sheet-core'
184
- export { importXlsx, importCsv, replaceWorkbook } from '@veltra/sheet-core'
185
-
186
- export {
187
- SheetGrid,
188
- type SheetGridOptions,
189
- type SheetGridContextMenuKind,
190
- type SheetGridContextMenuInfo
191
- } from '@veltra/sheet-core'
6
+ // 数据模型 / 渲染内核在 @veltra/sheet-core(独立发包)。本包**不做** re-export:
7
+ // core API(Workbook / Sheet / SheetGrid / 公式 / IO 等)一律
8
+ // `from '@veltra/sheet-core'` 直导,白名单外符号深导入其子路径。
9
+ // 本入口只导出 sheet 自有能力:组件、tools、组件类型。
192
10
 
193
11
  export { createSheetContext, type SheetContext } from './tools/context'
194
12
 
@@ -201,6 +19,24 @@ export {
201
19
  type SheetToolGroup
202
20
  } from './tools/registry'
203
21
 
204
- export { type SheetProps, type SheetEmits, type _SheetExposed, type SheetExposed } from './types'
22
+ export {
23
+ type SheetProps,
24
+ type SheetEmits,
25
+ type _SheetExposed,
26
+ type SheetExposed,
27
+ type ReportViewerProps,
28
+ type _ReportViewerExposed,
29
+ type ReportViewerExposed,
30
+ type ReportDesignerProps,
31
+ type ReportDesignerEmits,
32
+ type _ReportDesignerExposed,
33
+ type ReportDesignerExposed
34
+ } from './types'
35
+
36
+ export { USheet } from './components/sheet'
37
+
38
+ export { UReportViewer, UReportDesigner } from './components/report'
205
39
 
206
- export { default as USheet } from './vue/sheet.vue'
40
+ // 报表纯 TS 内核:renderReport / binding / rules / params / DataConnector / Report Template /
41
+ // Filter Bar 值规范化(结构见 packages/sheet/AGENTS.md)
42
+ export * from './report'
@@ -0,0 +1,320 @@
1
+ import type { CellAddress } from '@veltra/sheet-core'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import {
5
+ applyReportPreset,
6
+ createReportBinding,
7
+ formatBindingPlaceholder,
8
+ formatCellAddress,
9
+ inferColParentCandidate,
10
+ inferDropPreset,
11
+ inferParentsForPreset,
12
+ inferReportPreset,
13
+ inferRowParentCandidate,
14
+ isExpansionBandRow,
15
+ parseCellAddress,
16
+ presetBindingPatch,
17
+ presetStillApplies,
18
+ setBindingCatalog
19
+ } from '../binding'
20
+ import type { DatasetCatalogItem, ReportBinding } from '../types'
21
+
22
+ /** 内联 catalog fixture(单一事实源在测试自身,不依赖 playground mock) */
23
+ const ORDERS_DATASET: DatasetCatalogItem = {
24
+ id: 'orders',
25
+ label: '销售明细',
26
+ fields: [
27
+ { name: 'customer', label: '客户', type: 'string' },
28
+ { name: 'orderNo', label: '订单号', type: 'string' },
29
+ { name: 'region', label: '地区', type: 'string' },
30
+ { name: 'amount', label: '金额', type: 'number' },
31
+ { name: 'orderDate', label: '下单日期', type: 'date' }
32
+ ]
33
+ }
34
+
35
+ setBindingCatalog([ORDERS_DATASET])
36
+
37
+ describe('report binding', () => {
38
+ it('createReportBinding 默认 list + 纵向扩展 + 明细预设', () => {
39
+ const binding = createReportBinding(ORDERS_DATASET, 'amount')
40
+ expect(binding).toEqual({
41
+ dataset: 'orders',
42
+ field: 'amount',
43
+ aggregate: 'list',
44
+ expand: 'down',
45
+ preset: 'detail',
46
+ sort: 'none',
47
+ conditionalRules: []
48
+ })
49
+ })
50
+
51
+ it('formatBindingPlaceholder 中文标签(含 list / max / min)', () => {
52
+ expect(formatBindingPlaceholder(createReportBinding(ORDERS_DATASET, 'amount'))).toBe(
53
+ '明细 · 金额'
54
+ )
55
+
56
+ const group = createReportBinding(ORDERS_DATASET, 'customer')
57
+ group.aggregate = 'group'
58
+ expect(formatBindingPlaceholder(group)).toBe('分组 · 客户')
59
+
60
+ const sum = createReportBinding(ORDERS_DATASET, 'amount')
61
+ sum.aggregate = 'sum'
62
+ sum.expand = 'none'
63
+ expect(formatBindingPlaceholder(sum)).toBe('求和 · 金额')
64
+
65
+ const max = createReportBinding(ORDERS_DATASET, 'amount')
66
+ max.aggregate = 'max'
67
+ max.expand = 'none'
68
+ expect(formatBindingPlaceholder(max)).toBe('最大 · 金额')
69
+
70
+ const min = createReportBinding(ORDERS_DATASET, 'amount')
71
+ min.aggregate = 'min'
72
+ min.expand = 'none'
73
+ expect(formatBindingPlaceholder(min)).toBe('最小 · 金额')
74
+ })
75
+
76
+ it('presetBindingPatch / applyReportPreset 映射表', () => {
77
+ const rowParent = { row: 1, col: 0 }
78
+ const colParent = { row: 0, col: 1 }
79
+ const base: ReportBinding = {
80
+ dataset: 'orders',
81
+ field: 'amount',
82
+ expand: 'down',
83
+ aggregate: 'list',
84
+ rowParent,
85
+ colParent
86
+ }
87
+
88
+ expect(presetBindingPatch('groupHeader')).toEqual({
89
+ preset: 'groupHeader',
90
+ expand: 'down',
91
+ aggregate: 'group'
92
+ })
93
+ expect(presetBindingPatch('groupHeader', { transpose: true })).toMatchObject({
94
+ expand: 'right',
95
+ aggregate: 'group'
96
+ })
97
+
98
+ expect(presetBindingPatch('detail')).toEqual({
99
+ preset: 'detail',
100
+ expand: 'down',
101
+ aggregate: 'list'
102
+ })
103
+ expect(presetBindingPatch('detail', { transpose: true })).toMatchObject({
104
+ expand: 'right',
105
+ aggregate: 'list'
106
+ })
107
+
108
+ expect(presetBindingPatch('subtotal')).toEqual({
109
+ preset: 'subtotal',
110
+ expand: 'none',
111
+ aggregate: 'sum'
112
+ })
113
+ expect(applyReportPreset(base, 'subtotal').rowParent).toEqual(rowParent)
114
+
115
+ expect(presetBindingPatch('grandTotal')).toEqual({
116
+ preset: 'grandTotal',
117
+ expand: 'none',
118
+ aggregate: 'sum',
119
+ rowParent: undefined,
120
+ colParent: undefined
121
+ })
122
+ expect(applyReportPreset(base, 'grandTotal').rowParent).toBeUndefined()
123
+ expect(applyReportPreset(base, 'grandTotal').colParent).toBeUndefined()
124
+
125
+ expect(presetBindingPatch('cross')).toEqual({
126
+ preset: 'cross',
127
+ expand: 'none',
128
+ aggregate: 'sum'
129
+ })
130
+ expect(applyReportPreset(base, 'cross').rowParent).toEqual(rowParent)
131
+ expect(applyReportPreset(base, 'cross').colParent).toEqual(colParent)
132
+ })
133
+
134
+ it('inferReportPreset 从字段组合推断预设', () => {
135
+ expect(
136
+ inferReportPreset({
137
+ dataset: 'orders',
138
+ field: 'customer',
139
+ aggregate: 'group',
140
+ expand: 'down'
141
+ })
142
+ ).toBe('groupHeader')
143
+
144
+ expect(
145
+ inferReportPreset({
146
+ dataset: 'orders',
147
+ field: 'orderNo',
148
+ aggregate: 'list',
149
+ expand: 'down',
150
+ rowParent: { row: 1, col: 0 }
151
+ })
152
+ ).toBe('detail')
153
+
154
+ expect(
155
+ inferReportPreset({
156
+ dataset: 'orders',
157
+ field: 'amount',
158
+ aggregate: 'sum',
159
+ expand: 'none',
160
+ rowParent: { row: 1, col: 0 }
161
+ })
162
+ ).toBe('subtotal')
163
+
164
+ expect(
165
+ inferReportPreset({ dataset: 'orders', field: 'amount', aggregate: 'sum', expand: 'none' })
166
+ ).toBe('grandTotal')
167
+
168
+ expect(
169
+ inferReportPreset({
170
+ dataset: 'orders',
171
+ field: 'amount',
172
+ aggregate: 'sum',
173
+ expand: 'none',
174
+ rowParent: { row: 1, col: 0 },
175
+ colParent: { row: 0, col: 1 }
176
+ })
177
+ ).toBe('cross')
178
+
179
+ expect(
180
+ inferReportPreset({
181
+ dataset: 'orders',
182
+ field: 'amount',
183
+ aggregate: 'avg',
184
+ expand: 'none',
185
+ rowParent: { row: 1, col: 0 }
186
+ })
187
+ ).toBe('subtotal')
188
+
189
+ expect(
190
+ inferReportPreset({
191
+ dataset: 'orders',
192
+ field: 'amount',
193
+ aggregate: 'sum',
194
+ expand: 'none',
195
+ colParent: { row: 0, col: 1 }
196
+ })
197
+ ).toBe('subtotal')
198
+ })
199
+
200
+ it('presetStillApplies 同族内改聚合仍成立', () => {
201
+ const subtotal: ReportBinding = {
202
+ dataset: 'orders',
203
+ field: 'amount',
204
+ preset: 'subtotal',
205
+ aggregate: 'avg',
206
+ expand: 'none',
207
+ rowParent: { row: 1, col: 0 }
208
+ }
209
+ expect(presetStillApplies(subtotal)).toBe(true)
210
+
211
+ const broken = { ...subtotal, aggregate: 'list' as const, expand: 'down' as const }
212
+ expect(presetStillApplies(broken)).toBe(false)
213
+ })
214
+
215
+ it('inferDropPreset / isExpansionBandRow 落格推断', () => {
216
+ const groupAddr = { row: 1, col: 0 }
217
+ const group = createReportBinding(ORDERS_DATASET, 'customer')
218
+ group.aggregate = 'group'
219
+ group.expand = 'down'
220
+
221
+ const getBindingAt = (addr: CellAddress) => {
222
+ if (addr.row === groupAddr.row && addr.col === groupAddr.col) return group
223
+ return undefined
224
+ }
225
+
226
+ expect(isExpansionBandRow(1, getBindingAt)).toBe(true)
227
+ expect(isExpansionBandRow(0, getBindingAt)).toBe(false)
228
+ expect(inferDropPreset({ row: 2, col: 0 }, 'number', getBindingAt)).toBe('subtotal')
229
+ expect(inferDropPreset({ row: 2, col: 0 }, 'string', getBindingAt)).toBe('detail')
230
+ })
231
+
232
+ it('inferDropPreset:数值字段落在横向展开带正右相邻列也推断为小计', () => {
233
+ const colGroup = createReportBinding(ORDERS_DATASET, 'region')
234
+ colGroup.aggregate = 'group'
235
+ colGroup.expand = 'right'
236
+
237
+ const getBindingAt = (addr: CellAddress) => {
238
+ if (addr.row === 0 && addr.col === 1) return colGroup
239
+ return undefined
240
+ }
241
+
242
+ expect(inferDropPreset({ row: 0, col: 2 }, 'number', getBindingAt)).toBe('subtotal')
243
+ expect(inferDropPreset({ row: 0, col: 2 }, 'string', getBindingAt)).toBe('detail')
244
+ })
245
+
246
+ it('inferRowParentCandidate:同行向左优先分组头', () => {
247
+ const group = createReportBinding(ORDERS_DATASET, 'customer')
248
+ group.aggregate = 'group'
249
+ group.expand = 'down'
250
+ const detail = createReportBinding(ORDERS_DATASET, 'orderNo')
251
+
252
+ const getBindingAt = (addr: CellAddress) => {
253
+ if (addr.row === 1 && addr.col === 0) return group
254
+ if (addr.row === 1 && addr.col === 1) return detail
255
+ return undefined
256
+ }
257
+
258
+ expect(inferRowParentCandidate({ row: 1, col: 2 }, getBindingAt)).toEqual({ row: 1, col: 0 })
259
+ })
260
+
261
+ it('inferRowParentCandidate:下方跨列小计推到行分组头而不是同列 list', () => {
262
+ const group = createReportBinding(ORDERS_DATASET, 'customer')
263
+ group.aggregate = 'group'
264
+ group.expand = 'down'
265
+ const detail = createReportBinding(ORDERS_DATASET, 'amount')
266
+
267
+ const getBindingAt = (addr: CellAddress) => {
268
+ if (addr.row === 1 && addr.col === 0) return group
269
+ if (addr.row === 1 && addr.col === 2) return detail
270
+ return undefined
271
+ }
272
+
273
+ expect(
274
+ inferRowParentCandidate({ row: 2, col: 2 }, getBindingAt, { preferGroup: true })
275
+ ).toEqual({ row: 1, col: 0 })
276
+ expect(inferParentsForPreset({ row: 2, col: 2 }, 'subtotal', getBindingAt)).toEqual({
277
+ rowParent: { row: 1, col: 0 }
278
+ })
279
+ })
280
+
281
+ it('inferColParentCandidate:右侧列小计同列向上推到列分组头', () => {
282
+ const colGroup = createReportBinding(ORDERS_DATASET, 'region')
283
+ colGroup.aggregate = 'group'
284
+ colGroup.expand = 'right'
285
+ const cross = createReportBinding(ORDERS_DATASET, 'amount')
286
+ cross.preset = 'cross'
287
+ cross.aggregate = 'sum'
288
+ cross.expand = 'none'
289
+ cross.colParent = { row: 0, col: 1 }
290
+
291
+ const getBindingAt = (addr: CellAddress) => {
292
+ if (addr.row === 0 && addr.col === 1) return colGroup
293
+ if (addr.row === 1 && addr.col === 1) return cross
294
+ return undefined
295
+ }
296
+
297
+ expect(
298
+ inferColParentCandidate({ row: 2, col: 1 }, getBindingAt, { preferGroup: true })
299
+ ).toEqual({ row: 0, col: 1 })
300
+ expect(inferParentsForPreset({ row: 2, col: 1 }, 'subtotal', getBindingAt)).toEqual({
301
+ colParent: { row: 0, col: 1 }
302
+ })
303
+ })
304
+
305
+ it('formatCellAddress / parseCellAddress 支持多字母列互逆', () => {
306
+ const single = { row: 2, col: 1 }
307
+ expect(formatCellAddress(single)).toBe('B3')
308
+ expect(parseCellAddress('B3')).toEqual(single)
309
+
310
+ const wide = { row: 0, col: 26 }
311
+ expect(formatCellAddress(wide)).toBe('AA1')
312
+ expect(parseCellAddress('AA1')).toEqual(wide)
313
+
314
+ const wider = { row: 99, col: 701 }
315
+ expect(formatCellAddress(wider)).toBe('ZZ100')
316
+ expect(parseCellAddress('ZZ100')).toEqual(wider)
317
+
318
+ expect(parseCellAddress('bad')).toBeNull()
319
+ })
320
+ })