@veltra/sheet 2.4.0 → 2.5.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 (142) hide show
  1. package/dist/index.d.ts +2 -19
  2. package/dist/index.js +2 -15
  3. package/dist/types/index.d.ts +1 -3
  4. package/package.json +2 -3
  5. package/src/index.ts +1 -27
  6. package/src/types/index.ts +0 -2
  7. package/dist/components/report/binding-badge.js +0 -118
  8. package/dist/components/report/designer/cell-coords.js +0 -46
  9. package/dist/components/report/designer/col-widths.js +0 -29
  10. package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
  11. package/dist/components/report/designer/drill-dialog.js +0 -258
  12. package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
  13. package/dist/components/report/designer/float-panel.js +0 -434
  14. package/dist/components/report/designer/role.js +0 -17
  15. package/dist/components/report/designer/rule-preview.js +0 -33
  16. package/dist/components/report/designer/rule-row.js +0 -253
  17. package/dist/components/report/designer/rules-dialog.js +0 -118
  18. package/dist/components/report/designer/topology-overlay.js +0 -122
  19. package/dist/components/report/designer/topology.js +0 -64
  20. package/dist/components/report/designer/use-grid-overlay.js +0 -75
  21. package/dist/components/report/designer-hub.js +0 -177
  22. package/dist/components/report/field-panel-helpers.js +0 -33
  23. package/dist/components/report/field-panel.js +0 -132
  24. package/dist/components/report/filter-bar.js +0 -74
  25. package/dist/components/report/hub-connection-form.js +0 -145
  26. package/dist/components/report/hub-dataset-editor.js +0 -409
  27. package/dist/components/report/preview-grid-size.js +0 -32
  28. package/dist/components/report/print.js +0 -39
  29. package/dist/components/report/report-designer.js +0 -428
  30. package/dist/components/report/report-viewer.js +0 -253
  31. package/dist/components/report/style.d.ts +0 -17
  32. package/dist/components/report/style.js +0 -18
  33. package/dist/components/report/style2.css +0 -965
  34. package/dist/components/report/use-report-designer.js +0 -387
  35. package/dist/components/report/use-report-viewer.js +0 -140
  36. package/dist/report/binding.d.ts +0 -83
  37. package/dist/report/binding.js +0 -334
  38. package/dist/report/connector.d.ts +0 -66
  39. package/dist/report/connector.js +0 -159
  40. package/dist/report/drill.d.ts +0 -48
  41. package/dist/report/drill.js +0 -78
  42. package/dist/report/export-xlsx.d.ts +0 -13
  43. package/dist/report/export-xlsx.js +0 -13
  44. package/dist/report/filter-bar.d.ts +0 -10
  45. package/dist/report/filter-bar.js +0 -31
  46. package/dist/report/index.d.ts +0 -14
  47. package/dist/report/params.d.ts +0 -13
  48. package/dist/report/params.js +0 -61
  49. package/dist/report/print.d.ts +0 -23
  50. package/dist/report/print.js +0 -197
  51. package/dist/report/render/aggregate.js +0 -193
  52. package/dist/report/render/builder.d.ts +0 -12
  53. package/dist/report/render/builder.js +0 -139
  54. package/dist/report/render/coordinate.d.ts +0 -25
  55. package/dist/report/render/coordinate.js +0 -733
  56. package/dist/report/render/index.d.ts +0 -14
  57. package/dist/report/render/index.js +0 -21
  58. package/dist/report/render/style-resolver.js +0 -50
  59. package/dist/report/render/template-index.d.ts +0 -24
  60. package/dist/report/render/template-index.js +0 -137
  61. package/dist/report/rules/evaluate.d.ts +0 -25
  62. package/dist/report/rules/evaluate.js +0 -82
  63. package/dist/report/rules/index.d.ts +0 -2
  64. package/dist/report/template.d.ts +0 -63
  65. package/dist/report/template.js +0 -83
  66. package/dist/report/types.d.ts +0 -103
  67. package/dist/types/report-designer.d.ts +0 -51
  68. package/dist/types/report-viewer.d.ts +0 -57
  69. package/src/components/report/__test__/binding-badge.test.ts +0 -92
  70. package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
  71. package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
  72. package/src/components/report/__test__/float-panel-position.test.ts +0 -45
  73. package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
  74. package/src/components/report/__test__/report-designer.test.ts +0 -559
  75. package/src/components/report/__test__/report-drill.test.ts +0 -692
  76. package/src/components/report/__test__/report-viewer.test.ts +0 -400
  77. package/src/components/report/__test__/topology.test.ts +0 -143
  78. package/src/components/report/__test__/use-report-designer.test.ts +0 -804
  79. package/src/components/report/binding-badge.ts +0 -122
  80. package/src/components/report/designer/cell-coords.ts +0 -95
  81. package/src/components/report/designer/col-widths.ts +0 -56
  82. package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
  83. package/src/components/report/designer/drill-dialog.vue +0 -245
  84. package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
  85. package/src/components/report/designer/float-panel.vue +0 -444
  86. package/src/components/report/designer/role.ts +0 -32
  87. package/src/components/report/designer/rule-preview.vue +0 -40
  88. package/src/components/report/designer/rule-row.vue +0 -252
  89. package/src/components/report/designer/rules-dialog.vue +0 -110
  90. package/src/components/report/designer/topology-overlay.vue +0 -128
  91. package/src/components/report/designer/topology.ts +0 -124
  92. package/src/components/report/designer/use-grid-overlay.ts +0 -114
  93. package/src/components/report/designer-hub.vue +0 -198
  94. package/src/components/report/field-panel-helpers.ts +0 -44
  95. package/src/components/report/field-panel.vue +0 -136
  96. package/src/components/report/filter-bar.vue +0 -77
  97. package/src/components/report/hub-connection-form.vue +0 -124
  98. package/src/components/report/hub-dataset-editor.vue +0 -387
  99. package/src/components/report/index.ts +0 -2
  100. package/src/components/report/preview-grid-size.ts +0 -33
  101. package/src/components/report/print.ts +0 -44
  102. package/src/components/report/report-designer.vue +0 -454
  103. package/src/components/report/report-viewer.vue +0 -289
  104. package/src/components/report/style-designer.scss +0 -653
  105. package/src/components/report/style-hub.scss +0 -292
  106. package/src/components/report/style-viewer.scss +0 -144
  107. package/src/components/report/style.scss +0 -3
  108. package/src/components/report/style.ts +0 -20
  109. package/src/components/report/use-report-designer.ts +0 -603
  110. package/src/components/report/use-report-viewer.ts +0 -227
  111. package/src/report/__test__/binding.test.ts +0 -320
  112. package/src/report/__test__/connector.test.ts +0 -239
  113. package/src/report/__test__/drill.test.ts +0 -194
  114. package/src/report/__test__/export-xlsx.test.ts +0 -96
  115. package/src/report/__test__/filter-bar.test.ts +0 -58
  116. package/src/report/__test__/params.test.ts +0 -156
  117. package/src/report/__test__/print.test.ts +0 -170
  118. package/src/report/__test__/render.test.ts +0 -856
  119. package/src/report/__test__/rules.test.ts +0 -131
  120. package/src/report/__test__/template.test.ts +0 -366
  121. package/src/report/binding.ts +0 -474
  122. package/src/report/connector.ts +0 -206
  123. package/src/report/drill.ts +0 -112
  124. package/src/report/export-xlsx.ts +0 -15
  125. package/src/report/filter-bar.ts +0 -36
  126. package/src/report/index.ts +0 -16
  127. package/src/report/params.ts +0 -96
  128. package/src/report/print.ts +0 -234
  129. package/src/report/render/__test__/aggregate.test.ts +0 -16
  130. package/src/report/render/__test__/coordinate.test.ts +0 -405
  131. package/src/report/render/aggregate.ts +0 -289
  132. package/src/report/render/builder.ts +0 -211
  133. package/src/report/render/coordinate.ts +0 -1239
  134. package/src/report/render/index.ts +0 -30
  135. package/src/report/render/style-resolver.ts +0 -68
  136. package/src/report/render/template-index.ts +0 -200
  137. package/src/report/rules/evaluate.ts +0 -113
  138. package/src/report/rules/index.ts +0 -7
  139. package/src/report/template.ts +0 -145
  140. package/src/report/types.ts +0 -116
  141. package/src/types/report-designer.ts +0 -55
  142. package/src/types/report-viewer.ts +0 -54
@@ -1,227 +0,0 @@
1
- import type { CellAddress, SheetSnapshot } from '@veltra/sheet-core'
2
- import { cellKey } from '@veltra/sheet-core'
3
- import { computed, ref, shallowRef, watch, type ComputedRef, type Ref, type ShallowRef } from 'vue'
4
-
5
- import type { ConnectorError } from '../../report/connector'
6
- import {
7
- buildDrillHitMap,
8
- createDrillStack,
9
- currentDrillLayer,
10
- popDrillLayer,
11
- pushDrillLayer,
12
- resolveDrillParams,
13
- type DrillHit,
14
- type DrillStack
15
- } from '../../report/drill'
16
- import type { ReportColWidthEntry } from '../../report/export-xlsx'
17
- import { renderReport, resolveFilledColWidths } from '../../report/render'
18
- import {
19
- fetchTemplateRecords,
20
- resolveParamDefaults,
21
- resolveTemplateParams,
22
- type ReportTemplate
23
- } from '../../report/template'
24
- import type { ParamValues, QueryParamDef, ReportDrillConfig } from '../../report/types'
25
- import type { ReportViewerProps } from '../../types'
26
-
27
- export interface UseReportViewerReturn {
28
- /** Filter Bar 参数(当前层模板实际绑定的数据集的查询参数并集) */
29
- params: ComputedRef<QueryParamDef[]>
30
- /** 当前运行时参数值(初始为参数默认值;下钻时写入映射值) */
31
- values: Ref<ParamValues>
32
- /** 取数中 */
33
- loading: Ref<boolean>
34
- /** 最近一次取数的业务错误或下钻解析错误(可读提示;成功或重新取数时清空) */
35
- error: ShallowRef<ConnectorError | null>
36
- /** 最近一次成功展开渲染的 Filled Report 快照(组件壳据此替换网格内容) */
37
- filledSnapshot: ShallowRef<SheetSnapshot | null>
38
- /** 展开后物理列宽(由模板 colWidths 映射;无模板列宽时为 undefined) */
39
- filledColWidths: ShallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>
40
- /** 当前展示模板:下钻栈顶;未下钻时为 props.template */
41
- currentTemplate: ComputedRef<ReportTemplate>
42
- /** 下钻栈深(0 = 根层,未下钻) */
43
- drillDepth: ComputedRef<number>
44
- /** 可回退(栈深 > 0) */
45
- canDrillBack: ComputedRef<boolean>
46
- /** 物理格下钻命中查询(无命中返回 null);供组件壳点击 / 悬停判定 */
47
- resolveDrillHit: (addr: CellAddress) => DrillHit | null
48
- /**
49
- * 解析下钻目标:宿主契约取模板 + 默认值叠加映射参数。
50
- * 失败设可读错误、返回 null(栈不变)。成功时清除先前的下钻解析错误。
51
- */
52
- resolveDrillTarget: (
53
- config: ReportDrillConfig,
54
- record: Record<string, unknown>
55
- ) => Promise<{ template: ReportTemplate; params: ParamValues } | null>
56
- /**
57
- * 下钻进入(openMode 'switch'):解析目标模板 → 映射参数带入 Filter Bar → 压栈切换并取数。
58
- * 解析失败设可读错误提示、停留当前报、栈不变。
59
- */
60
- drillInto: (config: ReportDrillConfig, record: Record<string, unknown>) => Promise<void>
61
- /** 回退上一层:恢复该层模板与当时 Filter Bar 参数并重新取数渲染 */
62
- drillBack: () => Promise<void>
63
- /** 重新取数并展开渲染 */
64
- refresh: () => Promise<void>
65
- /** Filter Bar 改值:写入新参数值并重新取数 */
66
- setValues: (next: ParamValues) => void
67
- }
68
-
69
- /**
70
- * UReportViewer 的 headless 编排:参数提取 → 取数 → renderReport 展开。
71
- * 不持有 DOM / 网格引用;填充快照经 `filledSnapshot` 交给组件壳应用。
72
- * 下钻:openMode 'switch' 栈式切换当前层;'dialog' 由组件壳解析目标后嵌套独立查看器。
73
- * 导出 / 打印始终作用于当前可见层。
74
- */
75
- export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn {
76
- const loading = ref(false)
77
- const error = shallowRef<ConnectorError | null>(null)
78
- const filledSnapshot = shallowRef<SheetSnapshot | null>(null)
79
- const filledColWidths = shallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>(undefined)
80
-
81
- // 下钻栈:null = 未下钻(当前层即 props.template);命中索引随每次成功取数重建
82
- const drillStack = shallowRef<DrillStack | null>(null)
83
- const drillHits = shallowRef<Map<number, DrillHit>>(new Map())
84
-
85
- const currentTemplate = computed(() => {
86
- const stack = drillStack.value
87
- return stack ? currentDrillLayer(stack).template : props.template
88
- })
89
- const drillDepth = computed(() => (drillStack.value?.length ?? 1) - 1)
90
- const canDrillBack = computed(() => drillDepth.value > 0)
91
-
92
- const params = computed(() => resolveTemplateParams(currentTemplate.value))
93
- // 初始值由下方 immediate watch 播种(参数默认值)
94
- const values = shallowRef<ParamValues>({})
95
-
96
- // 并发守卫:只应用最后一次取数结果(Filter Bar 连续改值时前序结果作废)
97
- let fetchSeq = 0
98
-
99
- async function refresh(): Promise<void> {
100
- const current = ++fetchSeq
101
- const template = currentTemplate.value
102
- loading.value = true
103
- error.value = null
104
- const result = await fetchTemplateRecords(props.connector, template, values.value)
105
- if (current !== fetchSeq) return
106
- loading.value = false
107
- if (!result.ok) {
108
- error.value = result.error
109
- filledColWidths.value = undefined
110
- return
111
- }
112
- filledSnapshot.value = renderReport(template, result.data)
113
- filledColWidths.value = template.colWidths?.length
114
- ? resolveFilledColWidths(template, result.data, template.colWidths)
115
- : undefined
116
- drillHits.value = props.resolveTemplate ? buildDrillHitMap(template, result.data) : new Map()
117
- }
118
-
119
- /** 切层共有收尾:清填充结果与命中索引,写入新层参数并重新取数 */
120
- function transitionTo(layerParams: ParamValues): Promise<void> {
121
- filledSnapshot.value = null
122
- filledColWidths.value = undefined
123
- drillHits.value = new Map()
124
- values.value = layerParams
125
- return refresh()
126
- }
127
-
128
- async function resolveDrillTarget(
129
- config: ReportDrillConfig,
130
- record: Record<string, unknown>
131
- ): Promise<{ template: ReportTemplate; params: ParamValues } | null> {
132
- const resolve = props.resolveTemplate
133
- if (!resolve) return null
134
-
135
- let target: ReportTemplate | null | undefined
136
- try {
137
- target = await resolve(config.target)
138
- } catch (err) {
139
- error.value = {
140
- code: 'DRILL_RESOLVE_FAILED',
141
- message: err instanceof Error ? err.message : String(err)
142
- }
143
- return null
144
- }
145
- if (!target) {
146
- error.value = {
147
- code: 'DRILL_RESOLVE_FAILED',
148
- message: `下钻目标模板解析失败:${config.target}`
149
- }
150
- return null
151
- }
152
-
153
- error.value = null
154
- return {
155
- template: target,
156
- params: {
157
- ...resolveParamDefaults(resolveTemplateParams(target)),
158
- ...resolveDrillParams(config, record)
159
- }
160
- }
161
- }
162
-
163
- async function drillInto(
164
- config: ReportDrillConfig,
165
- record: Record<string, unknown>
166
- ): Promise<void> {
167
- const next = await resolveDrillTarget(config, record)
168
- if (!next) return
169
- const base =
170
- drillStack.value ?? createDrillStack({ template: props.template, params: values.value })
171
- drillStack.value = pushDrillLayer(
172
- base,
173
- { template: next.template, params: next.params },
174
- values.value
175
- )
176
- await transitionTo(next.params)
177
- }
178
-
179
- async function drillBack(): Promise<void> {
180
- const stack = drillStack.value
181
- if (!stack || stack.length <= 1) return
182
- const next = popDrillLayer(stack)
183
- drillStack.value = next
184
- await transitionTo({ ...currentDrillLayer(next).params })
185
- }
186
-
187
- function resolveDrillHit(addr: CellAddress): DrillHit | null {
188
- return drillHits.value.get(cellKey(addr)) ?? null
189
- }
190
-
191
- function setValues(next: ParamValues): void {
192
- values.value = next
193
- void refresh()
194
- }
195
-
196
- // 宿主更换根模板:下钻栈作废;Filter Bar 播种为默认值叠加 initialValues
197
- watch(
198
- () => props.template,
199
- () => {
200
- drillStack.value = null
201
- drillHits.value = new Map()
202
- filledSnapshot.value = null
203
- filledColWidths.value = undefined
204
- values.value = { ...resolveParamDefaults(params.value), ...props.initialValues }
205
- void refresh()
206
- },
207
- { immediate: true }
208
- )
209
-
210
- return {
211
- params,
212
- values,
213
- loading,
214
- error,
215
- filledSnapshot,
216
- filledColWidths,
217
- currentTemplate,
218
- drillDepth,
219
- canDrillBack,
220
- resolveDrillHit,
221
- resolveDrillTarget,
222
- drillInto,
223
- drillBack,
224
- refresh,
225
- setValues
226
- }
227
- }
@@ -1,320 +0,0 @@
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
- })