@veltra/sheet 2.3.4 → 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.
- package/dist/index.d.ts +2 -17
- package/dist/index.js +2 -13
- package/dist/types/index.d.ts +1 -3
- package/package.json +5 -6
- package/src/index.ts +1 -27
- package/src/types/index.ts +0 -2
- package/dist/components/report/binding-badge.js +0 -112
- package/dist/components/report/designer/cell-coords.js +0 -46
- package/dist/components/report/designer/col-widths.js +0 -29
- package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
- package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
- package/dist/components/report/designer/float-panel.js +0 -422
- package/dist/components/report/designer/role.js +0 -17
- package/dist/components/report/designer/rule-preview.js +0 -33
- package/dist/components/report/designer/rule-row.js +0 -253
- package/dist/components/report/designer/rules-dialog.js +0 -118
- package/dist/components/report/designer/topology-overlay.js +0 -122
- package/dist/components/report/designer/topology.js +0 -64
- package/dist/components/report/designer/use-grid-overlay.js +0 -75
- package/dist/components/report/designer-hub.js +0 -177
- package/dist/components/report/field-panel-helpers.js +0 -33
- package/dist/components/report/field-panel.js +0 -132
- package/dist/components/report/filter-bar.js +0 -74
- package/dist/components/report/hub-connection-form.js +0 -145
- package/dist/components/report/hub-dataset-editor.js +0 -409
- package/dist/components/report/preview-grid-size.js +0 -32
- package/dist/components/report/report-designer.js +0 -393
- package/dist/components/report/report-viewer.js +0 -124
- package/dist/components/report/style.d.ts +0 -17
- package/dist/components/report/style.js +0 -18
- package/dist/components/report/style2.css +0 -874
- package/dist/components/report/use-report-designer.js +0 -386
- package/dist/components/report/use-report-viewer.js +0 -55
- package/dist/report/binding.d.ts +0 -83
- package/dist/report/binding.js +0 -334
- package/dist/report/connector.d.ts +0 -66
- package/dist/report/connector.js +0 -159
- package/dist/report/export-xlsx.d.ts +0 -13
- package/dist/report/export-xlsx.js +0 -13
- package/dist/report/filter-bar.d.ts +0 -10
- package/dist/report/filter-bar.js +0 -31
- package/dist/report/index.d.ts +0 -12
- package/dist/report/params.d.ts +0 -13
- package/dist/report/params.js +0 -61
- package/dist/report/render/aggregate.js +0 -169
- package/dist/report/render/builder.d.ts +0 -12
- package/dist/report/render/builder.js +0 -139
- package/dist/report/render/coordinate.d.ts +0 -25
- package/dist/report/render/coordinate.js +0 -733
- package/dist/report/render/index.d.ts +0 -14
- package/dist/report/render/index.js +0 -21
- package/dist/report/render/style-resolver.js +0 -50
- package/dist/report/render/template-index.d.ts +0 -24
- package/dist/report/render/template-index.js +0 -137
- package/dist/report/rules/evaluate.d.ts +0 -25
- package/dist/report/rules/evaluate.js +0 -82
- package/dist/report/rules/index.d.ts +0 -2
- package/dist/report/template.d.ts +0 -63
- package/dist/report/template.js +0 -83
- package/dist/report/types.d.ts +0 -85
- package/dist/types/report-designer.d.ts +0 -38
- package/dist/types/report-viewer.d.ts +0 -38
- package/src/components/report/__test__/binding-badge.test.ts +0 -92
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
- package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
- package/src/components/report/__test__/float-panel-position.test.ts +0 -45
- package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
- package/src/components/report/__test__/report-designer.test.ts +0 -464
- package/src/components/report/__test__/report-viewer.test.ts +0 -357
- package/src/components/report/__test__/topology.test.ts +0 -143
- package/src/components/report/__test__/use-report-designer.test.ts +0 -697
- package/src/components/report/binding-badge.ts +0 -117
- package/src/components/report/designer/cell-coords.ts +0 -95
- package/src/components/report/designer/col-widths.ts +0 -56
- package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
- package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
- package/src/components/report/designer/float-panel.vue +0 -432
- package/src/components/report/designer/role.ts +0 -32
- package/src/components/report/designer/rule-preview.vue +0 -40
- package/src/components/report/designer/rule-row.vue +0 -252
- package/src/components/report/designer/rules-dialog.vue +0 -110
- package/src/components/report/designer/topology-overlay.vue +0 -128
- package/src/components/report/designer/topology.ts +0 -124
- package/src/components/report/designer/use-grid-overlay.ts +0 -114
- package/src/components/report/designer-hub.vue +0 -198
- package/src/components/report/field-panel-helpers.ts +0 -44
- package/src/components/report/field-panel.vue +0 -136
- package/src/components/report/filter-bar.vue +0 -77
- package/src/components/report/hub-connection-form.vue +0 -124
- package/src/components/report/hub-dataset-editor.vue +0 -387
- package/src/components/report/index.ts +0 -2
- package/src/components/report/preview-grid-size.ts +0 -33
- package/src/components/report/report-designer.vue +0 -421
- package/src/components/report/report-viewer.vue +0 -136
- package/src/components/report/style-designer.scss +0 -587
- package/src/components/report/style-hub.scss +0 -292
- package/src/components/report/style-viewer.scss +0 -104
- package/src/components/report/style.scss +0 -3
- package/src/components/report/style.ts +0 -20
- package/src/components/report/use-report-designer.ts +0 -602
- package/src/components/report/use-report-viewer.ts +0 -88
- package/src/report/__test__/binding.test.ts +0 -320
- package/src/report/__test__/connector.test.ts +0 -239
- package/src/report/__test__/export-xlsx.test.ts +0 -96
- package/src/report/__test__/filter-bar.test.ts +0 -58
- package/src/report/__test__/params.test.ts +0 -156
- package/src/report/__test__/render.test.ts +0 -856
- package/src/report/__test__/rules.test.ts +0 -131
- package/src/report/__test__/template.test.ts +0 -366
- package/src/report/binding.ts +0 -474
- package/src/report/connector.ts +0 -206
- package/src/report/export-xlsx.ts +0 -15
- package/src/report/filter-bar.ts +0 -36
- package/src/report/index.ts +0 -13
- package/src/report/params.ts +0 -96
- package/src/report/render/__test__/aggregate.test.ts +0 -16
- package/src/report/render/__test__/coordinate.test.ts +0 -405
- package/src/report/render/aggregate.ts +0 -261
- package/src/report/render/builder.ts +0 -211
- package/src/report/render/coordinate.ts +0 -1239
- package/src/report/render/index.ts +0 -30
- package/src/report/render/style-resolver.ts +0 -68
- package/src/report/render/template-index.ts +0 -200
- package/src/report/rules/evaluate.ts +0 -113
- package/src/report/rules/index.ts +0 -7
- package/src/report/template.ts +0 -145
- package/src/report/types.ts +0 -96
- package/src/types/report-designer.ts +0 -38
- package/src/types/report-viewer.ts +0 -37
|
@@ -1,697 +0,0 @@
|
|
|
1
|
-
import { Workbook } from '@veltra/sheet-core'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import { effectScope, ref, type EffectScope, type Ref } from 'vue'
|
|
4
|
-
|
|
5
|
-
import { REPORT_META_NAMESPACE } from '../../../report/binding'
|
|
6
|
-
import type { DataConnection, DataConnector } from '../../../report/connector'
|
|
7
|
-
import { IncompatibleTemplateVersionError, type ReportTemplate } from '../../../report/template'
|
|
8
|
-
import type { DatasetField, ParamValues, ReportBinding } from '../../../report/types'
|
|
9
|
-
import { roleBindingDefaults } from '../designer/role'
|
|
10
|
-
import { useReportDesigner, type UseReportDesignerReturn } from '../use-report-designer'
|
|
11
|
-
|
|
12
|
-
// ---- 内联 fixtures:stub connector(实现 DataConnector 接口的内存测试夹具)----
|
|
13
|
-
|
|
14
|
-
const MYSQL: DataConnection = {
|
|
15
|
-
id: 'c1',
|
|
16
|
-
label: 'stub MySQL',
|
|
17
|
-
type: 'mysql',
|
|
18
|
-
host: '127.0.0.1',
|
|
19
|
-
port: 3306,
|
|
20
|
-
database: 'demo',
|
|
21
|
-
username: 'root',
|
|
22
|
-
password: ''
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const PG: DataConnection = {
|
|
26
|
-
id: 'c2',
|
|
27
|
-
label: 'stub PG',
|
|
28
|
-
type: 'postgresql',
|
|
29
|
-
host: '127.0.0.1',
|
|
30
|
-
port: 5432,
|
|
31
|
-
database: 'demo',
|
|
32
|
-
username: 'postgres',
|
|
33
|
-
password: ''
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const ORDER_FIELDS: DatasetField[] = [
|
|
37
|
-
{ name: 'customer', label: '客户', type: 'string' },
|
|
38
|
-
{ name: 'orderNo', label: '订单号', type: 'string' },
|
|
39
|
-
{ name: 'amount', label: '金额', type: 'number' }
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
interface StubCalls {
|
|
43
|
-
test: DataConnection[]
|
|
44
|
-
describe: Array<{ connection: DataConnection; sql: string }>
|
|
45
|
-
query: Array<{ connection: DataConnection; sql: string; values: ParamValues }>
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function createStubConnector() {
|
|
49
|
-
const calls: StubCalls = { test: [], describe: [], query: [] }
|
|
50
|
-
const connector: DataConnector = {
|
|
51
|
-
test: (connection) => {
|
|
52
|
-
calls.test.push(connection)
|
|
53
|
-
return Promise.resolve({ ok: true, data: undefined })
|
|
54
|
-
},
|
|
55
|
-
describe: (connection, sql) => {
|
|
56
|
-
calls.describe.push({ connection, sql })
|
|
57
|
-
if (sql.includes('BAD')) {
|
|
58
|
-
return Promise.resolve({
|
|
59
|
-
ok: false,
|
|
60
|
-
error: { code: 'SQL_ERROR', message: 'syntax error near BAD' }
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
return Promise.resolve({ ok: true, data: ORDER_FIELDS })
|
|
64
|
-
},
|
|
65
|
-
query: (connection, sql, values) => {
|
|
66
|
-
calls.query.push({ connection, sql, values })
|
|
67
|
-
return Promise.resolve({
|
|
68
|
-
ok: true,
|
|
69
|
-
data: { fields: ORDER_FIELDS, rows: [{ customer: '甲公司', amount: 100 }] }
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { connector, calls }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface DesignerFixture {
|
|
77
|
-
scope: EffectScope
|
|
78
|
-
designer: UseReportDesignerReturn
|
|
79
|
-
connections: Ref<DataConnection[]>
|
|
80
|
-
workbook: Workbook
|
|
81
|
-
calls: StubCalls
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function createDesigner(
|
|
85
|
-
initial: DataConnection[] = [MYSQL],
|
|
86
|
-
options?: { template?: ReportTemplate }
|
|
87
|
-
): DesignerFixture {
|
|
88
|
-
const { connector, calls } = createStubConnector()
|
|
89
|
-
const connections = ref<DataConnection[]>(initial)
|
|
90
|
-
const workbook = new Workbook()
|
|
91
|
-
const scope = effectScope()
|
|
92
|
-
const designer = scope.run(() =>
|
|
93
|
-
useReportDesigner({ props: { connector, workbook, template: options?.template }, connections })
|
|
94
|
-
)!
|
|
95
|
-
return { scope, designer, connections, workbook, calls }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function bindGroupHeader(
|
|
99
|
-
designer: UseReportDesignerReturn,
|
|
100
|
-
workbook: Workbook,
|
|
101
|
-
datasetId: string,
|
|
102
|
-
addr: { row: number; col: number },
|
|
103
|
-
field = 'customer'
|
|
104
|
-
): void {
|
|
105
|
-
designer.bindField(datasetId, field, addr)
|
|
106
|
-
workbook.activeSheet.selectCell(addr)
|
|
107
|
-
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
describe('useReportDesigner:连接 / 数据集 CRUD 与连接器调用', () => {
|
|
111
|
-
it('连接 CRUD 全走 v-model 代理;删除连接级联删除其数据集', () => {
|
|
112
|
-
const { designer, connections } = createDesigner()
|
|
113
|
-
|
|
114
|
-
designer.addConnection(PG)
|
|
115
|
-
expect(connections.value).toEqual([MYSQL, PG])
|
|
116
|
-
|
|
117
|
-
designer.updateConnection({ ...PG, label: 'renamed' })
|
|
118
|
-
expect(connections.value[1]).toEqual({ ...PG, label: 'renamed' })
|
|
119
|
-
|
|
120
|
-
const dataset = designer.addDataset('c2')
|
|
121
|
-
designer.removeConnection('c2')
|
|
122
|
-
expect(connections.value).toEqual([MYSQL])
|
|
123
|
-
expect(designer.datasets.value.find((item) => item.id === dataset.id)).toBeUndefined()
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('测试连接透传 connector.test(新建草稿同样可测)', async () => {
|
|
127
|
-
const { designer, calls } = createDesigner()
|
|
128
|
-
const draft: DataConnection = { ...MYSQL, id: 'draft', host: '10.0.0.1' }
|
|
129
|
-
const result = await designer.testConnection(draft)
|
|
130
|
-
expect(result).toEqual({ ok: true, data: undefined })
|
|
131
|
-
expect(calls.test).toEqual([draft])
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('describeDataset 经连接器解析字段并写入数据集字段缓存;fieldOverrides 在 catalog 层应用', async () => {
|
|
135
|
-
const { designer } = createDesigner()
|
|
136
|
-
const dataset = designer.addDataset('c1')
|
|
137
|
-
designer.updateDataset(dataset.id, {
|
|
138
|
-
sql: 'SELECT customer, amount FROM orders',
|
|
139
|
-
fieldOverrides: { customer: { label: '客户名称' } }
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
const result = await designer.describeDataset(dataset.id)
|
|
143
|
-
expect(result).toEqual({ ok: true, data: ORDER_FIELDS })
|
|
144
|
-
expect(designer.datasets.value[0]!.fields).toEqual(ORDER_FIELDS)
|
|
145
|
-
|
|
146
|
-
const catalog = designer.catalog.value
|
|
147
|
-
expect(catalog).toHaveLength(1)
|
|
148
|
-
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '订单号', '金额'])
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('describeDataset 业务错误整体透传且不写字段缓存', async () => {
|
|
152
|
-
const { designer } = createDesigner()
|
|
153
|
-
const dataset = designer.addDataset('c1')
|
|
154
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT BAD' })
|
|
155
|
-
|
|
156
|
-
const result = await designer.describeDataset(dataset.id)
|
|
157
|
-
expect(result).toEqual({
|
|
158
|
-
ok: false,
|
|
159
|
-
error: { code: 'SQL_ERROR', message: 'syntax error near BAD' }
|
|
160
|
-
})
|
|
161
|
-
expect(designer.datasets.value[0]!.fields).toBeUndefined()
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('previewDataset 按参数默认值(含元数据覆盖)经连接器取数', async () => {
|
|
165
|
-
const { designer, calls } = createDesigner()
|
|
166
|
-
const dataset = designer.addDataset('c1')
|
|
167
|
-
designer.updateDataset(dataset.id, {
|
|
168
|
-
sql: 'SELECT customer FROM orders WHERE customer LIKE ${keyword} AND amount >= ${minAmount}',
|
|
169
|
-
paramOverrides: { minAmount: { defaultValue: 50 } }
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
await designer.previewDataset(dataset.id)
|
|
173
|
-
expect(calls.query).toHaveLength(1)
|
|
174
|
-
expect(calls.query[0]!.connection).toEqual(MYSQL)
|
|
175
|
-
expect(calls.query[0]!.values).toEqual({ keyword: '', minAmount: 50 })
|
|
176
|
-
})
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
describe('useReportDesigner:拖拽落格写 Cell Meta', () => {
|
|
180
|
-
async function seedDataset(designer: UseReportDesignerReturn, connectionId = 'c1') {
|
|
181
|
-
const dataset = designer.addDataset(connectionId)
|
|
182
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
183
|
-
await designer.describeDataset(dataset.id)
|
|
184
|
-
return dataset
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
it('落格写入 createReportBinding 默认绑定(detail/list/down)', async () => {
|
|
188
|
-
const { designer, workbook } = createDesigner()
|
|
189
|
-
const dataset = await seedDataset(designer)
|
|
190
|
-
|
|
191
|
-
designer.bindField(dataset.id, 'customer', { row: 0, col: 0 })
|
|
192
|
-
|
|
193
|
-
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
194
|
-
{ row: 0, col: 0 },
|
|
195
|
-
REPORT_META_NAMESPACE
|
|
196
|
-
)
|
|
197
|
-
expect(binding).toEqual({
|
|
198
|
-
dataset: dataset.id,
|
|
199
|
-
field: 'customer',
|
|
200
|
-
aggregate: 'list',
|
|
201
|
-
expand: 'down',
|
|
202
|
-
preset: 'detail',
|
|
203
|
-
sort: 'none',
|
|
204
|
-
conditionalRules: []
|
|
205
|
-
})
|
|
206
|
-
expect(designer.boundKeys.value.has(`${dataset.id}:customer`)).toBe(true)
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
it('同列向上推断 rowParent 候选', async () => {
|
|
210
|
-
const { designer, workbook } = createDesigner()
|
|
211
|
-
const dataset = await seedDataset(designer)
|
|
212
|
-
|
|
213
|
-
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
214
|
-
designer.bindField(dataset.id, 'orderNo', { row: 2, col: 0 })
|
|
215
|
-
|
|
216
|
-
const detail = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
217
|
-
{ row: 2, col: 0 },
|
|
218
|
-
REPORT_META_NAMESPACE
|
|
219
|
-
)
|
|
220
|
-
expect(detail).toMatchObject({
|
|
221
|
-
dataset: dataset.id,
|
|
222
|
-
field: 'orderNo',
|
|
223
|
-
preset: 'detail',
|
|
224
|
-
aggregate: 'list',
|
|
225
|
-
expand: 'down',
|
|
226
|
-
rowParent: { row: 1, col: 0 }
|
|
227
|
-
})
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
it('跨数据集拖拽保留字段自己的 dataset,不继承分组数据集', async () => {
|
|
231
|
-
const { designer, workbook } = createDesigner([MYSQL, PG])
|
|
232
|
-
const orders = await seedDataset(designer, 'c1')
|
|
233
|
-
const inventory = await seedDataset(designer, 'c2')
|
|
234
|
-
|
|
235
|
-
designer.bindField(orders.id, 'customer', { row: 1, col: 0 })
|
|
236
|
-
designer.bindField(inventory.id, 'orderNo', { row: 2, col: 0 })
|
|
237
|
-
|
|
238
|
-
const detail = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
239
|
-
{ row: 2, col: 0 },
|
|
240
|
-
REPORT_META_NAMESPACE
|
|
241
|
-
)
|
|
242
|
-
expect(detail).toMatchObject({
|
|
243
|
-
dataset: inventory.id,
|
|
244
|
-
field: 'orderNo',
|
|
245
|
-
rowParent: { row: 1, col: 0 }
|
|
246
|
-
})
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('数值字段落至展开带下方相邻行时推断为小计预设', async () => {
|
|
250
|
-
const { designer, workbook } = createDesigner()
|
|
251
|
-
const dataset = await seedDataset(designer)
|
|
252
|
-
|
|
253
|
-
bindGroupHeader(designer, workbook, dataset.id, { row: 1, col: 0 })
|
|
254
|
-
designer.bindField(dataset.id, 'amount', { row: 2, col: 0 })
|
|
255
|
-
|
|
256
|
-
const subtotal = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
257
|
-
{ row: 2, col: 0 },
|
|
258
|
-
REPORT_META_NAMESPACE
|
|
259
|
-
)
|
|
260
|
-
expect(subtotal).toMatchObject({
|
|
261
|
-
preset: 'subtotal',
|
|
262
|
-
aggregate: 'sum',
|
|
263
|
-
expand: 'none',
|
|
264
|
-
rowParent: { row: 1, col: 0 }
|
|
265
|
-
})
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
it('同行明细落格把 rowParent 推到左侧分组头', async () => {
|
|
269
|
-
const { designer, workbook } = createDesigner()
|
|
270
|
-
const dataset = await seedDataset(designer)
|
|
271
|
-
|
|
272
|
-
bindGroupHeader(designer, workbook, dataset.id, { row: 1, col: 0 })
|
|
273
|
-
designer.bindField(dataset.id, 'orderNo', { row: 1, col: 2 })
|
|
274
|
-
|
|
275
|
-
const detail = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
276
|
-
{ row: 1, col: 2 },
|
|
277
|
-
REPORT_META_NAMESPACE
|
|
278
|
-
)
|
|
279
|
-
expect(detail).toMatchObject({
|
|
280
|
-
field: 'orderNo',
|
|
281
|
-
preset: 'detail',
|
|
282
|
-
rowParent: { row: 1, col: 0 }
|
|
283
|
-
})
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
it('下方跨列小计落格推到行分组头而不是同列 list', async () => {
|
|
287
|
-
const { designer, workbook } = createDesigner()
|
|
288
|
-
const dataset = await seedDataset(designer)
|
|
289
|
-
|
|
290
|
-
bindGroupHeader(designer, workbook, dataset.id, { row: 1, col: 0 })
|
|
291
|
-
designer.bindField(dataset.id, 'orderNo', { row: 1, col: 2 })
|
|
292
|
-
designer.bindField(dataset.id, 'amount', { row: 2, col: 2 })
|
|
293
|
-
|
|
294
|
-
const subtotal = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
295
|
-
{ row: 2, col: 2 },
|
|
296
|
-
REPORT_META_NAMESPACE
|
|
297
|
-
)
|
|
298
|
-
expect(subtotal).toMatchObject({
|
|
299
|
-
preset: 'subtotal',
|
|
300
|
-
aggregate: 'sum',
|
|
301
|
-
expand: 'none',
|
|
302
|
-
rowParent: { row: 1, col: 0 }
|
|
303
|
-
})
|
|
304
|
-
expect(subtotal?.colParent).toBeUndefined()
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
it('右侧列小计落格推到列分组头', async () => {
|
|
308
|
-
const { designer, workbook } = createDesigner()
|
|
309
|
-
const dataset = await seedDataset(designer)
|
|
310
|
-
|
|
311
|
-
designer.bindField(dataset.id, 'customer', { row: 0, col: 1 })
|
|
312
|
-
workbook.activeSheet.selectCell({ row: 0, col: 1 })
|
|
313
|
-
designer.patchActiveBinding({ preset: 'groupHeader', aggregate: 'group', expand: 'right' })
|
|
314
|
-
designer.bindField(dataset.id, 'amount', { row: 0, col: 2 })
|
|
315
|
-
|
|
316
|
-
const subtotal = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
317
|
-
{ row: 0, col: 2 },
|
|
318
|
-
REPORT_META_NAMESPACE
|
|
319
|
-
)
|
|
320
|
-
expect(subtotal).toMatchObject({
|
|
321
|
-
preset: 'subtotal',
|
|
322
|
-
aggregate: 'sum',
|
|
323
|
-
expand: 'none',
|
|
324
|
-
colParent: { row: 0, col: 1 }
|
|
325
|
-
})
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
it('未给落点地址时回退到当前选区', async () => {
|
|
329
|
-
const { designer, workbook } = createDesigner()
|
|
330
|
-
const dataset = await seedDataset(designer)
|
|
331
|
-
workbook.activeSheet.selectCell({ row: 3, col: 2 })
|
|
332
|
-
|
|
333
|
-
designer.bindField(dataset.id, 'amount')
|
|
334
|
-
expect(workbook.activeSheet.getCellMeta({ row: 3, col: 2 }, REPORT_META_NAMESPACE)).toBeTruthy()
|
|
335
|
-
})
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
describe('useReportDesigner:角色徽章渲染 hook(ADR-0004 首个消费者)', () => {
|
|
339
|
-
it('绑定格返回徽章布局(renderDefault: false),未绑定格回落默认渲染', async () => {
|
|
340
|
-
const { designer, workbook } = createDesigner()
|
|
341
|
-
const dataset = designer.addDataset('c1')
|
|
342
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT customer FROM orders' })
|
|
343
|
-
await designer.describeDataset(dataset.id)
|
|
344
|
-
designer.bindField(dataset.id, 'customer', { row: 0, col: 0 })
|
|
345
|
-
|
|
346
|
-
expect(designer.resolveCellRenderer({ row: 0, col: 1 }, undefined)).toBeUndefined()
|
|
347
|
-
|
|
348
|
-
const layout = designer.resolveCellRenderer({ row: 0, col: 0 }, undefined)
|
|
349
|
-
expect(layout).toBeTruthy()
|
|
350
|
-
expect(layout!.renderDefault).toBe(false)
|
|
351
|
-
expect(layout!.rootContainer).toBeTruthy()
|
|
352
|
-
// 根容器不得挂 percentCalc 宽高(VTable 会与 cellSize Math.max 出 NaN)
|
|
353
|
-
expect(layout!.rootContainer.attribute.width).toBeUndefined()
|
|
354
|
-
expect(layout!.rootContainer.attribute.height).toBeUndefined()
|
|
355
|
-
expect(layout!.rootContainer.attribute.fill).toBeUndefined()
|
|
356
|
-
|
|
357
|
-
const style = designer.resolveCellStyle({ row: 0, col: 0 }, undefined)
|
|
358
|
-
expect(style?.fill).toEqual({ color: '#effaf3' })
|
|
359
|
-
|
|
360
|
-
// 模型与快照无 renderer 残留
|
|
361
|
-
expect(workbook.activeSheet.getCellData({ row: 0, col: 0 })).toBeUndefined()
|
|
362
|
-
})
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
describe('useReportDesigner:getTemplate', () => {
|
|
366
|
-
it('返回含 meta 绑定与内嵌数据集定义的 Report Template', async () => {
|
|
367
|
-
const { designer } = createDesigner([MYSQL, PG])
|
|
368
|
-
const orders = designer.addDataset('c1')
|
|
369
|
-
designer.updateDataset(orders.id, {
|
|
370
|
-
label: '销售明细',
|
|
371
|
-
sql: 'SELECT customer FROM orders WHERE customer LIKE ${keyword}',
|
|
372
|
-
paramOverrides: { keyword: { label: '客户关键词' } },
|
|
373
|
-
fieldOverrides: { customer: { label: '客户名称' } }
|
|
374
|
-
})
|
|
375
|
-
await designer.describeDataset(orders.id)
|
|
376
|
-
designer.bindField(orders.id, 'customer', { row: 1, col: 0 })
|
|
377
|
-
|
|
378
|
-
const template = designer.getTemplate()
|
|
379
|
-
|
|
380
|
-
expect(template.version).toBe(1)
|
|
381
|
-
|
|
382
|
-
// meta 绑定随快照吐出
|
|
383
|
-
const meta = template.meta ?? []
|
|
384
|
-
expect(meta).toHaveLength(1)
|
|
385
|
-
expect(meta[0]).toMatchObject({ row: 1, col: 0, namespace: 'report' })
|
|
386
|
-
expect((meta[0]!.payload as ReportBinding).field).toBe('customer')
|
|
387
|
-
|
|
388
|
-
// 数据集内嵌连接对象(无 connectionId / fields 缓存泄漏)
|
|
389
|
-
expect(template.datasets).toHaveLength(1)
|
|
390
|
-
const dataset = template.datasets![0]!
|
|
391
|
-
expect(dataset).toEqual({
|
|
392
|
-
id: orders.id,
|
|
393
|
-
label: '销售明细',
|
|
394
|
-
connection: MYSQL,
|
|
395
|
-
sql: 'SELECT customer FROM orders WHERE customer LIKE ${keyword}',
|
|
396
|
-
paramOverrides: { keyword: { label: '客户关键词' } },
|
|
397
|
-
fieldOverrides: { customer: { label: '客户名称' } }
|
|
398
|
-
})
|
|
399
|
-
expect(dataset.connection).not.toBe(MYSQL)
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
it('无匹配连接的数据集不吐出(凭据不悬空)', () => {
|
|
403
|
-
const { designer } = createDesigner([MYSQL])
|
|
404
|
-
const orphan = designer.addDataset('c-missing')
|
|
405
|
-
designer.updateDataset(orphan.id, { sql: 'SELECT 1' })
|
|
406
|
-
|
|
407
|
-
expect(designer.getTemplate().datasets).toEqual([])
|
|
408
|
-
})
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
describe('useReportDesigner:Action Pill 就地编辑(角色 / 聚合 / 条件规则 / 清除绑定)', () => {
|
|
412
|
-
async function seedAndBind(
|
|
413
|
-
designer: UseReportDesignerReturn,
|
|
414
|
-
addr: { row: number; col: number }
|
|
415
|
-
) {
|
|
416
|
-
const dataset = designer.addDataset('c1')
|
|
417
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
418
|
-
await designer.describeDataset(dataset.id)
|
|
419
|
-
designer.bindField(dataset.id, 'amount', addr)
|
|
420
|
-
return dataset
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
it('预设切换写入 presetBindingDefaults 组合值;同族聚合切换保留预设', async () => {
|
|
424
|
-
const { designer, workbook } = createDesigner()
|
|
425
|
-
await seedAndBind(designer, { row: 0, col: 0 })
|
|
426
|
-
workbook.activeSheet.selectCell({ row: 0, col: 0 })
|
|
427
|
-
|
|
428
|
-
designer.patchActiveBinding(roleBindingDefaults('subtotal'))
|
|
429
|
-
let binding = designer.activeBinding.value
|
|
430
|
-
expect(binding).toMatchObject({ preset: 'subtotal', aggregate: 'sum', expand: 'none' })
|
|
431
|
-
|
|
432
|
-
designer.patchActiveBinding({ aggregate: 'avg', expand: 'none' })
|
|
433
|
-
binding = designer.activeBinding.value
|
|
434
|
-
expect(binding).toMatchObject({ preset: 'subtotal', aggregate: 'avg', expand: 'none' })
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
it('聚合/展开偏离预设族时清除 preset', async () => {
|
|
438
|
-
const { designer, workbook } = createDesigner()
|
|
439
|
-
await seedAndBind(designer, { row: 0, col: 0 })
|
|
440
|
-
workbook.activeSheet.selectCell({ row: 0, col: 0 })
|
|
441
|
-
|
|
442
|
-
designer.patchActiveBinding(roleBindingDefaults('subtotal'))
|
|
443
|
-
designer.patchActiveBinding({ aggregate: 'list', expand: 'down' })
|
|
444
|
-
expect(designer.activeBinding.value?.preset).toBeUndefined()
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
it('条件规则与排序经 patch 写入绑定;拓扑条目跟随 meta 变更', async () => {
|
|
448
|
-
const { designer, workbook } = createDesigner()
|
|
449
|
-
const dataset = await seedAndBind(designer, { row: 0, col: 0 })
|
|
450
|
-
workbook.activeSheet.selectCell({ row: 0, col: 0 })
|
|
451
|
-
|
|
452
|
-
designer.patchActiveBinding({
|
|
453
|
-
sort: 'desc',
|
|
454
|
-
conditionalRules: [{ operator: 'gt', value: 100, style: { font: { color: '#DC2626' } } }]
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
458
|
-
{ row: 0, col: 0 },
|
|
459
|
-
REPORT_META_NAMESPACE
|
|
460
|
-
)
|
|
461
|
-
expect(binding?.sort).toBe('desc')
|
|
462
|
-
expect(binding?.conditionalRules).toHaveLength(1)
|
|
463
|
-
|
|
464
|
-
const entries = designer.bindingEntries.value
|
|
465
|
-
expect(entries).toHaveLength(1)
|
|
466
|
-
expect(entries[0]).toMatchObject({ addr: { row: 0, col: 0 }, binding: { dataset: dataset.id } })
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
it('点选式父格编辑写入模板存储的 rowParent', async () => {
|
|
470
|
-
const { designer, workbook } = createDesigner()
|
|
471
|
-
const dataset = designer.addDataset('c1')
|
|
472
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
473
|
-
await designer.describeDataset(dataset.id)
|
|
474
|
-
|
|
475
|
-
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
476
|
-
designer.bindField(dataset.id, 'amount', { row: 2, col: 1 })
|
|
477
|
-
|
|
478
|
-
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
479
|
-
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
480
|
-
workbook.activeSheet.selectCell({ row: 2, col: 1 })
|
|
481
|
-
designer.startParentPick('row')
|
|
482
|
-
designer.pickParentAt({ row: 1, col: 0 })
|
|
483
|
-
|
|
484
|
-
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
485
|
-
{ row: 2, col: 1 },
|
|
486
|
-
REPORT_META_NAMESPACE
|
|
487
|
-
)
|
|
488
|
-
expect(binding?.rowParent).toEqual({ row: 1, col: 0 })
|
|
489
|
-
expect(designer.parentPick.value).toBeNull()
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
it('切换小计行时纠正指向 list 的父格', async () => {
|
|
493
|
-
const { designer, workbook } = createDesigner()
|
|
494
|
-
const dataset = designer.addDataset('c1')
|
|
495
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
496
|
-
await designer.describeDataset(dataset.id)
|
|
497
|
-
|
|
498
|
-
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
499
|
-
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
500
|
-
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
501
|
-
designer.bindField(dataset.id, 'orderNo', { row: 1, col: 2 })
|
|
502
|
-
designer.bindField(dataset.id, 'amount', { row: 2, col: 2 })
|
|
503
|
-
|
|
504
|
-
workbook.activeSheet.selectCell({ row: 2, col: 2 })
|
|
505
|
-
designer.patchActiveBinding({ rowParent: { row: 1, col: 2 } })
|
|
506
|
-
expect(designer.activeBinding.value?.rowParent).toEqual({ row: 1, col: 2 })
|
|
507
|
-
|
|
508
|
-
designer.patchActiveBinding(roleBindingDefaults('subtotal'))
|
|
509
|
-
expect(designer.activeBinding.value).toMatchObject({
|
|
510
|
-
preset: 'subtotal',
|
|
511
|
-
rowParent: { row: 1, col: 0 }
|
|
512
|
-
})
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
it('非法点选父格时选区弹回源格且保持拾取态', async () => {
|
|
516
|
-
const { designer, workbook } = createDesigner()
|
|
517
|
-
const dataset = designer.addDataset('c1')
|
|
518
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
519
|
-
await designer.describeDataset(dataset.id)
|
|
520
|
-
|
|
521
|
-
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
522
|
-
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
523
|
-
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
524
|
-
designer.bindField(dataset.id, 'amount', { row: 2, col: 2 })
|
|
525
|
-
|
|
526
|
-
workbook.activeSheet.selectCell({ row: 2, col: 2 })
|
|
527
|
-
designer.startParentPick('row')
|
|
528
|
-
designer.pickParentAt({ row: 0, col: 3 })
|
|
529
|
-
|
|
530
|
-
expect(designer.parentPick.value).toEqual({ mode: 'row', source: { row: 2, col: 2 } })
|
|
531
|
-
expect(workbook.activeSheet.getSelection().activeCell).toEqual({ row: 2, col: 2 })
|
|
532
|
-
expect(
|
|
533
|
-
workbook.activeSheet.getCellMeta<ReportBinding>({ row: 2, col: 2 }, REPORT_META_NAMESPACE)
|
|
534
|
-
?.rowParent
|
|
535
|
-
).toEqual({ row: 1, col: 0 })
|
|
536
|
-
})
|
|
537
|
-
|
|
538
|
-
it('清除绑定移除 Cell Meta;行 / 列方向父格标签读取绑定存储值', async () => {
|
|
539
|
-
const { designer, workbook } = createDesigner()
|
|
540
|
-
const dataset = designer.addDataset('c1')
|
|
541
|
-
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
542
|
-
await designer.describeDataset(dataset.id)
|
|
543
|
-
|
|
544
|
-
// 同列上方分组头 + 下方小计(rowParent 推断到分组头)
|
|
545
|
-
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
546
|
-
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
547
|
-
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
548
|
-
designer.bindField(dataset.id, 'amount', { row: 2, col: 0 })
|
|
549
|
-
|
|
550
|
-
workbook.activeSheet.selectCell({ row: 2, col: 0 })
|
|
551
|
-
expect(designer.resolvedRowParentLabel.value).toBe('A2')
|
|
552
|
-
|
|
553
|
-
designer.removeActiveBinding()
|
|
554
|
-
expect(
|
|
555
|
-
workbook.activeSheet.getCellMeta({ row: 2, col: 0 }, REPORT_META_NAMESPACE)
|
|
556
|
-
).toBeUndefined()
|
|
557
|
-
expect(designer.activeBinding.value).toBeUndefined()
|
|
558
|
-
expect(designer.boundKeys.value.has(`${dataset.id}:amount`)).toBe(false)
|
|
559
|
-
})
|
|
560
|
-
})
|
|
561
|
-
|
|
562
|
-
describe('useReportDesigner:template 版本校验', () => {
|
|
563
|
-
it('载入缺少 version 的模板时抛可读错误', () => {
|
|
564
|
-
const { connector } = createStubConnector()
|
|
565
|
-
const connections = ref<DataConnection[]>([MYSQL])
|
|
566
|
-
expect(() =>
|
|
567
|
-
effectScope().run(() =>
|
|
568
|
-
useReportDesigner({
|
|
569
|
-
props: {
|
|
570
|
-
connector,
|
|
571
|
-
template: {
|
|
572
|
-
cells: [],
|
|
573
|
-
styles: [],
|
|
574
|
-
merges: [],
|
|
575
|
-
frozen: { rows: 0, cols: 0 },
|
|
576
|
-
rows: 24,
|
|
577
|
-
cols: 10
|
|
578
|
-
}
|
|
579
|
-
},
|
|
580
|
-
connections
|
|
581
|
-
})
|
|
582
|
-
)
|
|
583
|
-
).toThrow(IncompatibleTemplateVersionError)
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
it('载入高于当前支持 version 的模板时抛可读错误', () => {
|
|
587
|
-
const { connector } = createStubConnector()
|
|
588
|
-
const connections = ref<DataConnection[]>([MYSQL])
|
|
589
|
-
expect(() =>
|
|
590
|
-
effectScope().run(() =>
|
|
591
|
-
useReportDesigner({
|
|
592
|
-
props: {
|
|
593
|
-
connector,
|
|
594
|
-
template: {
|
|
595
|
-
version: 99,
|
|
596
|
-
cells: [],
|
|
597
|
-
styles: [],
|
|
598
|
-
merges: [],
|
|
599
|
-
frozen: { rows: 0, cols: 0 },
|
|
600
|
-
rows: 24,
|
|
601
|
-
cols: 10
|
|
602
|
-
}
|
|
603
|
-
},
|
|
604
|
-
connections
|
|
605
|
-
})
|
|
606
|
-
)
|
|
607
|
-
).toThrow(/高于当前支持/)
|
|
608
|
-
})
|
|
609
|
-
})
|
|
610
|
-
|
|
611
|
-
describe('useReportDesigner:template prop 载入既有模板', () => {
|
|
612
|
-
function buildTemplate(): ReportTemplate {
|
|
613
|
-
const group = createReportBindingFixture()
|
|
614
|
-
return {
|
|
615
|
-
version: 1,
|
|
616
|
-
cells: [{ row: 0, col: 0, v: '客户' }],
|
|
617
|
-
styles: [],
|
|
618
|
-
merges: [],
|
|
619
|
-
frozen: { rows: 0, cols: 0 },
|
|
620
|
-
rows: 24,
|
|
621
|
-
cols: 10,
|
|
622
|
-
meta: [{ row: 1, col: 0, namespace: REPORT_META_NAMESPACE, payload: group }],
|
|
623
|
-
datasets: [
|
|
624
|
-
{
|
|
625
|
-
id: 'ds-template',
|
|
626
|
-
label: '模板数据集',
|
|
627
|
-
connection: { ...PG, id: 'conn-template' },
|
|
628
|
-
sql: 'SELECT customer, amount FROM orders',
|
|
629
|
-
paramOverrides: { keyword: { label: '客户关键词' } },
|
|
630
|
-
fieldOverrides: { customer: { label: '客户名称' } }
|
|
631
|
-
}
|
|
632
|
-
]
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function createReportBindingFixture(): ReportBinding {
|
|
637
|
-
return {
|
|
638
|
-
dataset: 'ds-template',
|
|
639
|
-
field: 'customer',
|
|
640
|
-
preset: 'groupHeader',
|
|
641
|
-
aggregate: 'group',
|
|
642
|
-
expand: 'down',
|
|
643
|
-
sort: 'none',
|
|
644
|
-
conditionalRules: [{ operator: 'eq', value: '甲公司', style: { fill: { color: '#FFCCCC' } } }]
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
it('恢复网格绑定与设计态数据集;内嵌连接按 id 合并进 v-model 列表;describe 恢复字段缓存', async () => {
|
|
649
|
-
const { designer, connections, workbook, calls } = createDesigner([MYSQL], {
|
|
650
|
-
template: buildTemplate()
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
// 绑定随快照恢复(含条件规则)
|
|
654
|
-
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
655
|
-
{ row: 1, col: 0 },
|
|
656
|
-
REPORT_META_NAMESPACE
|
|
657
|
-
)
|
|
658
|
-
expect(binding).toEqual(createReportBindingFixture())
|
|
659
|
-
|
|
660
|
-
// 数据集还原为设计态(connectionId 引用;fields 缓存经 describe 恢复)
|
|
661
|
-
expect(designer.datasets.value).toHaveLength(1)
|
|
662
|
-
expect(designer.datasets.value[0]).toMatchObject({
|
|
663
|
-
id: 'ds-template',
|
|
664
|
-
label: '模板数据集',
|
|
665
|
-
connectionId: 'conn-template',
|
|
666
|
-
sql: 'SELECT customer, amount FROM orders'
|
|
667
|
-
})
|
|
668
|
-
await Promise.resolve()
|
|
669
|
-
expect(calls.describe).toHaveLength(1)
|
|
670
|
-
expect(calls.describe[0]!.connection.id).toBe('conn-template')
|
|
671
|
-
|
|
672
|
-
// 内嵌连接按 id 合并(已有连接不重复追加)
|
|
673
|
-
expect(connections.value.map((item) => item.id)).toEqual(['c1', 'conn-template'])
|
|
674
|
-
|
|
675
|
-
// catalog 应用 fieldOverrides(字段面板与徽章 label 数据源)
|
|
676
|
-
const catalog = designer.catalog.value
|
|
677
|
-
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '订单号', '金额'])
|
|
678
|
-
})
|
|
679
|
-
|
|
680
|
-
it('载入后 getTemplate 往返保持绑定与数据集定义;宿主已有同 id 连接时不覆盖', async () => {
|
|
681
|
-
const template = buildTemplate()
|
|
682
|
-
const { designer } = createDesigner(
|
|
683
|
-
[MYSQL, { ...PG, id: 'conn-template', label: '宿主连接' }],
|
|
684
|
-
{ template }
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
// 宿主同 id 连接优先(单一事实源),模板内嵌连接不覆盖
|
|
688
|
-
const roundTrip = designer.getTemplate()
|
|
689
|
-
expect(roundTrip.datasets).toHaveLength(1)
|
|
690
|
-
expect(roundTrip.datasets![0]!.connection.label).toBe('宿主连接')
|
|
691
|
-
|
|
692
|
-
// 绑定完整往返
|
|
693
|
-
const meta = roundTrip.meta ?? []
|
|
694
|
-
expect(meta).toHaveLength(1)
|
|
695
|
-
expect(meta[0]!.payload).toEqual(createReportBindingFixture())
|
|
696
|
-
})
|
|
697
|
-
})
|