waveform-analysis 0.1.62 → 0.1.64

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 (83) hide show
  1. package/README.md +9 -2
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +953 -946
  4. package/dist/style.css +1 -1
  5. package/dist/types/types/chart.d.ts +2 -1
  6. package/package.json +1 -1
  7. package/src/components/WaveformChart.vue +1 -1
  8. package/src/components/core/xDomain.ts +26 -2
  9. package/src/components/rendering/WaveformLegend.vue +1 -1
  10. package/src/components/rendering/WaveformSeriesLayer.vue +1 -1
  11. package/src/demo/DemoChartHost.vue +1 -0
  12. package/src/types/chart.ts +2 -1
  13. package/src/App.test.ts +0 -400
  14. package/src/components/annotation/components.test.ts +0 -372
  15. package/src/components/annotation/markup.test.ts +0 -371
  16. package/src/components/annotation/serialization.test.ts +0 -114
  17. package/src/components/core/constants.test.ts +0 -29
  18. package/src/components/core/fixedYDomainLayout.test.ts +0 -204
  19. package/src/components/core/grid.test.ts +0 -197
  20. package/src/components/core/latestTaskScheduler.test.ts +0 -51
  21. package/src/components/core/layout.test.ts +0 -373
  22. package/src/components/core/title.test.ts +0 -76
  23. package/src/components/core/useWaveformData.test.ts +0 -95
  24. package/src/components/core/useWaveformRenderSampling.test.ts +0 -17
  25. package/src/components/core/xDomain.test.ts +0 -28
  26. package/src/components/core/yAxisNice.test.ts +0 -174
  27. package/src/components/core/yAxisSlotLayout.test.ts +0 -165
  28. package/src/components/core/yDomain.test.ts +0 -56
  29. package/src/components/interaction/WaveformTooltip.test.ts +0 -276
  30. package/src/components/interaction/viewportInteractionState.test.ts +0 -101
  31. package/src/components/utils/useAnimationFrameThrottle.test.ts +0 -53
  32. package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +0 -257
  33. package/src/components/waveformChartCases/compactAndSharedHover.test.ts +0 -358
  34. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +0 -358
  35. package/src/components/waveformChartCases/dataLifecycle.test.ts +0 -310
  36. package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +0 -389
  37. package/src/components/waveformChartCases/gridFill.test.ts +0 -95
  38. package/src/components/waveformChartCases/incompleteDataHover.test.ts +0 -155
  39. package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +0 -328
  40. package/src/components/waveformChartCases/layoutAndDomains.test.ts +0 -371
  41. package/src/components/waveformChartCases/legendVisibility.test.ts +0 -350
  42. package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +0 -400
  43. package/src/components/waveformChartCases/multiAxisLabelAlignment.test.ts +0 -61
  44. package/src/components/waveformChartCases/presentationAndAxes.test.ts +0 -345
  45. package/src/components/waveformChartCases/presentationMode.test.ts +0 -327
  46. package/src/components/waveformChartCases/renderingStyles.test.ts +0 -283
  47. package/src/components/waveformChartCases/sizingAndTitles.test.ts +0 -370
  48. package/src/components/waveformChartCases/trackOrder.test.ts +0 -323
  49. package/src/components/waveformChartCases/viewportBoxAndPan.test.ts +0 -365
  50. package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +0 -311
  51. package/src/components/waveformChartCases/viewportLifecycle.test.ts +0 -132
  52. package/src/components/waveformChartCases/viewportReset.test.ts +0 -251
  53. package/src/components/waveformChartCases/wheelZoomOut.test.ts +0 -114
  54. package/src/components/waveformChartCases/workerSamplingIntegration.test.ts +0 -117
  55. package/src/components/waveformChartCases/xDomainStrategy.test.ts +0 -168
  56. package/src/components/waveformChartCases/yAxisLayout.test.ts +0 -103
  57. package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +0 -213
  58. package/src/components/waveformChartCases/zoomPointConstraints.test.ts +0 -308
  59. package/src/components/waveformLegendGeometry.test.ts +0 -21
  60. package/src/components/waveformNormalization.test.ts +0 -170
  61. package/src/core/data.test.ts +0 -241
  62. package/src/core/rendering.test.ts +0 -267
  63. package/src/core/renderingStrategies.test.ts +0 -90
  64. package/src/core/samplingReference.test.ts +0 -124
  65. package/src/core/wasmSampling.test.ts +0 -205
  66. package/src/core/workerSampling/multiresolution.test.ts +0 -69
  67. package/src/core/workerSampling/protocol.test.ts +0 -34
  68. package/src/core/workerSampling/repository.test.ts +0 -400
  69. package/src/core/workerSampling/repositoryCache.test.ts +0 -163
  70. package/src/core/workerSampling/repositorySampling.test.ts +0 -106
  71. package/src/core/workerSampling/repositoryTypedDataset.test.ts +0 -89
  72. package/src/core/workerSampling/repositoryWasmDataset.test.ts +0 -70
  73. package/src/data/simulatedWaveforms.test.ts +0 -60
  74. package/src/demo/plotMarginControls.test.ts +0 -29
  75. package/src/demo/xAxisLabelControls.test.ts +0 -86
  76. package/src/test/setup.ts +0 -82
  77. package/src/test/waveformChart.ts +0 -75
  78. package/src/utils/domain.test.ts +0 -28
  79. package/src/utils/formatters.test.ts +0 -131
  80. package/src/utils/sampling.test.ts +0 -226
  81. package/src/views/FixedYDomainDemo.test.ts +0 -44
  82. package/src/views/WasmSamplingDemo.test.ts +0 -13
  83. package/src/views/wasmSamplingDemoSampling.test.ts +0 -27
@@ -1,389 +0,0 @@
1
- import { flushPromises } from '@vue/test-utils'
2
- import { describe, expect, it, vi } from 'vitest'
3
-
4
- import { flushAnimationFrames } from '../../test/setup'
5
- import type { WaveformXAxisLabelFormatter } from '../../types'
6
- import { type WaveformData } from '../waveform'
7
-
8
- import { gridSeries, mountSizedChart } from '../../test/waveformChart'
9
-
10
- describe('WaveformChart', () => {
11
- it('renders reference grid styling and an optional frame watermark', async () => {
12
- const wrapper = await mountSizedChart(
13
- {
14
- kind: 'points',
15
- points: [
16
- { x: 0, y: 0 },
17
- { x: 1, y: 1 },
18
- ],
19
- },
20
- { frameNumber: 12 },
21
- )
22
-
23
- expect(wrapper.findAll('.waveform-chart__grid--major line').length).toBeGreaterThan(0)
24
- expect(wrapper.findAll('.waveform-chart__grid--minor line').length).toBeGreaterThan(0)
25
- expect(
26
- wrapper.findAll('.waveform-chart__grid--major [data-grid-direction="horizontal"]'),
27
- ).toHaveLength(5)
28
- expect(
29
- wrapper.findAll('.waveform-chart__grid--minor [data-grid-direction="horizontal"]'),
30
- ).toHaveLength(4)
31
- expect(
32
- wrapper.findAll('.waveform-chart__grid--minor [data-grid-direction="vertical"]'),
33
- ).not.toHaveLength(0)
34
- expect(getComputedStyle(wrapper.element).borderTopWidth).toBe('1.2px')
35
- expect(wrapper.find('.waveform-chart__plot-frame').exists()).toBe(true)
36
- expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({
37
- fill: 'none',
38
- stroke: '#1f2937',
39
- 'stroke-width': '1',
40
- })
41
- expect(
42
- wrapper.get('.waveform-chart__plot-frame').attributes('stroke-dasharray'),
43
- ).toBeUndefined()
44
- expect(wrapper.get('.waveform-chart__plot-background').attributes('fill')).toBe('transparent')
45
- expect(wrapper.get('.waveform-chart__watermark').text()).toBe('12')
46
- expect(getComputedStyle(wrapper.get('.waveform-chart__watermark').element).userSelect).toBe(
47
- 'none',
48
- )
49
- expect(wrapper.get('.waveform-chart__line').attributes('stroke')).toBe('#0960bd')
50
- })
51
-
52
- it('controls horizontal and vertical grid lines independently by track ID', async () => {
53
- const data = gridSeries(2)
54
- if (data.kind === 'series') {
55
- data.series[0].trackId = 'frame-a'
56
- data.series[1].trackId = 'frame-b'
57
- }
58
- const wrapper = await mountSizedChart(data, {
59
- grid: {
60
- rowCount: 1,
61
- columnCount: 2,
62
- trackLines: {
63
- 'frame-a': { horizontal: false },
64
- 'frame-b': { vertical: false },
65
- },
66
- },
67
- })
68
-
69
- const tracks = wrapper.findAll('.waveform-chart__track')
70
- expect(tracks).toHaveLength(2)
71
- expect(tracks[0].findAll('[data-grid-direction="horizontal"]')).toHaveLength(0)
72
- expect(tracks[0].findAll('[data-grid-direction="vertical"]').length).not.toBe(0)
73
- expect(tracks[1].findAll('[data-grid-direction="vertical"]')).toHaveLength(0)
74
- expect(tracks[1].findAll('[data-grid-direction="horizontal"]').length).not.toBe(0)
75
- })
76
-
77
- it('keeps per-track grid line visibility attached across pagination', async () => {
78
- const wrapper = await mountSizedChart(gridSeries(3), {
79
- grid: {
80
- rowCount: 1,
81
- columnCount: 1,
82
- trackLines: {
83
- 'channel-1': { horizontal: false, vertical: false },
84
- },
85
- },
86
- })
87
-
88
- expect(wrapper.findAll('[data-grid-direction]')).not.toHaveLength(0)
89
- await wrapper.get('.ant-pagination-next button').trigger('click')
90
- expect(wrapper.findAll('[data-grid-direction]')).toHaveLength(0)
91
- await wrapper.get('.ant-pagination-next button').trigger('click')
92
- expect(wrapper.findAll('[data-grid-direction]')).not.toHaveLength(0)
93
- })
94
-
95
- it('applies one custom frame style to every non-empty track', async () => {
96
- const wrapper = await mountSizedChart(gridSeries(2), {
97
- grid: { rowCount: 2, columnCount: 1 },
98
- frameStyle: {
99
- borderColor: 'rgba(255, 0, 0, 0.7)',
100
- borderWidth: 2.5,
101
- borderStyle: 'dashed',
102
- backgroundColor: 'rgba(16, 185, 129, 0.2)',
103
- },
104
- })
105
-
106
- const tracks = wrapper.findAll('.waveform-chart__track')
107
- const frames = wrapper.findAll('.waveform-chart__plot-frame')
108
- const backgrounds = wrapper.findAll('.waveform-chart__plot-background')
109
-
110
- expect(frames).toHaveLength(2)
111
- expect(backgrounds).toHaveLength(2)
112
- frames.forEach((frame) => {
113
- expect(frame.attributes()).toMatchObject({
114
- stroke: 'rgba(255, 0, 0, 0.7)',
115
- 'stroke-width': '2.5',
116
- 'stroke-dasharray': '6 4',
117
- })
118
- })
119
- backgrounds.forEach((background) => {
120
- expect(background.attributes('fill')).toBe('rgba(16, 185, 129, 0.2)')
121
- })
122
-
123
- tracks.forEach((track) => {
124
- const renderingLayers = track.findAll(
125
- '.waveform-chart__plot-background, .waveform-chart__grid',
126
- )
127
- expect(renderingLayers[0].classes()).toContain('waveform-chart__plot-background')
128
- })
129
- })
130
-
131
- it('falls back to the default width for invalid frame widths', async () => {
132
- const wrapper = await mountSizedChart(gridSeries(1), {
133
- frameStyle: { borderWidth: -1 },
134
- })
135
-
136
- expect(wrapper.get('.waveform-chart__plot-frame').attributes('stroke-width')).toBe('1')
137
-
138
- await wrapper.setProps({ frameStyle: { borderWidth: Number.NaN } })
139
- expect(wrapper.get('.waveform-chart__plot-frame').attributes('stroke-width')).toBe('1')
140
- })
141
-
142
- it('renders a dotted frame with rounded dots', async () => {
143
- const wrapper = await mountSizedChart(gridSeries(1), {
144
- frameStyle: { borderStyle: 'dotted' },
145
- })
146
-
147
- expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({
148
- 'stroke-dasharray': '1 3',
149
- 'stroke-linecap': 'round',
150
- })
151
- })
152
-
153
- it('continues minor x-grid lines beyond the final major tick to the exact endpoint', async () => {
154
- const wrapper = await mountSizedChart({
155
- kind: 'points',
156
- points: [
157
- { x: -8, y: 0 },
158
- { x: 4.9903, y: 1 },
159
- ],
160
- })
161
- const majorGridPositions = wrapper
162
- .findAll('.waveform-chart__grid--major line')
163
- .map((line) => Number(line.attributes('x1')))
164
- .filter(Number.isFinite)
165
- const minorGridPositions = wrapper
166
- .findAll('.waveform-chart__grid--minor line')
167
- .map((line) => Number(line.attributes('x1')))
168
- .filter(Number.isFinite)
169
- const trackWidth = Number(wrapper.get('.waveform-chart__track').attributes('data-track-width'))
170
-
171
- expect(Math.max(...minorGridPositions)).toBeGreaterThan(Math.max(...majorGridPositions))
172
- expect(Math.max(...minorGridPositions)).toBeLessThan(trackWidth)
173
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').attributes('x')).toBe(
174
- String(trackWidth),
175
- )
176
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('4990.3')
177
- expect(wrapper.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
178
- })
179
-
180
- it('uses one shared scientific exponent only for large and tiny Y-axis domains', async () => {
181
- const cases = [
182
- { values: [0, 50], yDomain: [0, 50] as [number, number], exponent: null },
183
- { values: [1000, 3000], yDomain: [1000, 3000] as [number, number], exponent: 'E+03' },
184
- {
185
- values: [0.0001, 0.0003],
186
- yDomain: [0.0001, 0.0003] as [number, number],
187
- exponent: 'E-04',
188
- },
189
- ]
190
-
191
- for (const { values, yDomain, exponent } of cases) {
192
- const data = {
193
- kind: 'points' as const,
194
- points: values.map((y, x) => ({ x, y })),
195
- }
196
- const originalValues = data.points.map((point) => point.y)
197
- const wrapper = await mountSizedChart(data, { yDomain })
198
- const labels = wrapper
199
- .get('.waveform-chart__axis--y')
200
- .findAll('.tick text')
201
- .map((tick) => tick.text())
202
- const exponentLabels = labels.filter((label) => label.startsWith('E'))
203
-
204
- if (exponent === null) {
205
- expect(exponentLabels).toHaveLength(0)
206
- } else {
207
- expect(exponentLabels).toHaveLength(1)
208
- expect(exponentLabels[0]).toMatch(new RegExp(`^${exponent.replace('+', '\\+')} `))
209
- }
210
- expect(wrapper.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
211
- expect(data.points.map((point) => point.y)).toEqual(originalValues)
212
-
213
- wrapper.unmount()
214
- }
215
- })
216
-
217
- it('uses milliseconds by default and supports seconds with a custom label', async () => {
218
- const data: WaveformData = {
219
- kind: 'points',
220
- points: [
221
- { x: 0, y: 0 },
222
- { x: 1, y: 1 },
223
- ],
224
- }
225
- const millisecondsChart = await mountSizedChart(data)
226
- const millisecondTicks = millisecondsChart
227
- .get('.waveform-chart__axis--x')
228
- .findAll('.tick text')
229
- .map((tick) => tick.text())
230
-
231
- expect(millisecondsChart.text()).toContain('时间(ms)')
232
- expect(millisecondTicks.length).toBeGreaterThan(0)
233
- expect(millisecondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
234
- expect(millisecondsChart.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
235
- expect(millisecondsChart.find('.waveform-chart__watermark').exists()).toBe(false)
236
-
237
- const secondsChart = await mountSizedChart(data, { timeUnit: 's', xLabel: 'Elapsed time' })
238
- expect(secondsChart.text()).toContain('Elapsed time')
239
- expect(secondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1')
240
- expect(secondsChart.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
241
- })
242
-
243
- it('pins the exact visible range values to both x-axis endpoints', async () => {
244
- const wrapper = await mountSizedChart({
245
- kind: 'samples',
246
- values: Array.from({ length: 2000 }, (_, index) => Math.sin(index / 10)),
247
- sampleRate: 1000,
248
- })
249
- const start = wrapper.get('.waveform-chart__axis-endpoint--start')
250
- const end = wrapper.get('.waveform-chart__axis-endpoint--end')
251
- const middleTickLabels = wrapper
252
- .get('.waveform-chart__axis--x')
253
- .findAll('.tick text')
254
- .map((tick) => tick.text())
255
-
256
- expect(start.attributes('x')).toBe('0')
257
- expect(start.attributes('text-anchor')).toBe('start')
258
- expect(start.text()).toBe('0')
259
- expect(end.attributes('x')).toBe(
260
- wrapper.get('.waveform-chart__track').attributes('data-track-width'),
261
- )
262
- expect(end.attributes('text-anchor')).toBe('end')
263
- expect(end.text()).toBe('1999')
264
- expect(middleTickLabels.length).toBeGreaterThan(0)
265
- expect(middleTickLabels).not.toContain('0')
266
- expect(wrapper.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
267
- expect(wrapper.findAll('.waveform-chart__grid--major line').length).toBeGreaterThan(
268
- middleTickLabels.length,
269
- )
270
- const middleTick = wrapper.get('.waveform-chart__axis--x .tick text')
271
- const endpointGroup = wrapper.get('.waveform-chart__axis-endpoints')
272
- const xAxis = wrapper.get('.waveform-chart__axis--x')
273
- expect(start.attributes('y')).toBe(middleTick.attributes('y'))
274
- expect(start.attributes('dy')).toBe(middleTick.attributes('dy'))
275
- expect(end.attributes('y')).toBe(middleTick.attributes('y'))
276
- expect(end.attributes('dy')).toBe(middleTick.attributes('dy'))
277
- expect(endpointGroup.attributes('font-family')).toBe(xAxis.attributes('font-family'))
278
- expect(endpointGroup.attributes('font-size')).toBe(xAxis.attributes('font-size'))
279
- })
280
-
281
- it('formats X-axis ticks and endpoints with display-unit values and source context', async () => {
282
- const data: WaveformData = {
283
- kind: 'points',
284
- points: [
285
- { x: 0.125, y: 0 },
286
- { x: 1.875, y: 1 },
287
- ],
288
- }
289
- const originalPoints = data.points.map((point) => ({ ...point }))
290
- const labelFormatter: WaveformXAxisLabelFormatter = (value, context) =>
291
- `${context.kind}:${value / 10}`
292
- const formatter = vi.fn(labelFormatter)
293
- const wrapper = await mountSizedChart(data, {
294
- timeUnit: 'ms',
295
- axes: { x: { labelFormatter: formatter } },
296
- })
297
-
298
- expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('start:12.5')
299
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('end:187.5')
300
- expect(
301
- wrapper
302
- .get('.waveform-chart__axis--x')
303
- .findAll('.tick text')
304
- .every((tick) => tick.text().startsWith('tick:')),
305
- ).toBe(true)
306
-
307
- const startCall = formatter.mock.calls.find(([, context]) => context.kind === 'start')
308
- const tickCall = formatter.mock.calls.find(([, context]) => context.kind === 'tick')
309
- const endCall = formatter.mock.calls.find(([, context]) => context.kind === 'end')
310
- expect(startCall).toEqual([
311
- 125,
312
- {
313
- kind: 'start',
314
- rawValue: 0.125,
315
- timeUnit: 'ms',
316
- domain: [0.125, 1.875],
317
- displayDomain: [125, 1875],
318
- },
319
- ])
320
- expect(tickCall?.[0]).toBeTypeOf('number')
321
- expect(tickCall?.[1]).toMatchObject({
322
- kind: 'tick',
323
- timeUnit: 'ms',
324
- domain: [0.125, 1.875],
325
- displayDomain: [125, 1875],
326
- })
327
- expect(endCall?.[1]).toMatchObject({ kind: 'end', rawValue: 1.875 })
328
- expect(data.points).toEqual(originalPoints)
329
-
330
- await wrapper.setProps({
331
- axes: { x: { labelFormatter: (value: number) => `updated:${value}` } },
332
- })
333
- await flushPromises()
334
- expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('updated:125')
335
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('updated:1875')
336
- expect(
337
- wrapper
338
- .get('.waveform-chart__axis--x')
339
- .findAll('.tick text')
340
- .every((tick) => tick.text().startsWith('updated:')),
341
- ).toBe(true)
342
- expect(data.points).toEqual(originalPoints)
343
- })
344
-
345
- it('keeps zoom-change domains in source seconds', async () => {
346
- const wrapper = await mountSizedChart({
347
- kind: 'points',
348
- points: [
349
- { x: 0, y: 0 },
350
- { x: 2, y: 1 },
351
- ],
352
- })
353
- const overlay = wrapper.get('.waveform-chart__overlay')
354
- const overlayWidth = Number(overlay.attributes('width'))
355
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
356
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
357
- })
358
-
359
- const initialStart = wrapper.get('.waveform-chart__axis-endpoint--start').text()
360
- const initialEnd = wrapper.get('.waveform-chart__axis-endpoint--end').text()
361
-
362
- overlay.element.dispatchEvent(
363
- new WheelEvent('wheel', {
364
- deltaY: -4000,
365
- clientX: overlayWidth / 2,
366
- clientY: 145,
367
- bubbles: true,
368
- cancelable: true,
369
- }),
370
- )
371
- flushAnimationFrames()
372
- await flushPromises()
373
-
374
- const emittedDomain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as
375
- [number, number] | undefined
376
- expect(emittedDomain).toBeDefined()
377
- expect(emittedDomain?.[0]).toBeGreaterThanOrEqual(0)
378
- expect(emittedDomain?.[1]).toBeLessThanOrEqual(2)
379
- expect(wrapper.get('.waveform-chart__axis-endpoint--start').attributes('x')).toBe('0')
380
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').attributes('x')).toBe(
381
- String(overlayWidth),
382
- )
383
- expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe(initialStart)
384
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).not.toBe(initialEnd)
385
- expect(
386
- Number.isFinite(Number(wrapper.get('.waveform-chart__axis-endpoint--start').text())),
387
- ).toBe(true)
388
- })
389
- })
@@ -1,95 +0,0 @@
1
- import { flushPromises } from '@vue/test-utils'
2
- import { describe, expect, it } from 'vitest'
3
-
4
- import { gridSeries, mountSizedChart } from '../../test/waveformChart'
5
-
6
- function geometry(track: { attributes: (name: string) => string | undefined }) {
7
- return {
8
- left: Number(track.attributes('data-track-left')),
9
- top: Number(track.attributes('data-track-top')),
10
- width: Number(track.attributes('data-track-width')),
11
- height: Number(track.attributes('data-track-height')),
12
- }
13
- }
14
-
15
- describe('WaveformChart incomplete grid rows', () => {
16
- it.each(['independent', 'separated', 'compact'] as const)(
17
- 'fills an incomplete two by two row in %s mode',
18
- async (displayMode) => {
19
- const wrapper = await mountSizedChart(gridSeries(3), {
20
- displayMode,
21
- grid: { rowCount: 2, columnCount: 2, fillIncompleteLastRow: true },
22
- })
23
- const tracks = wrapper.findAll('.waveform-chart__track')
24
- const first = geometry(tracks[0]!)
25
- const last = geometry(tracks[2]!)
26
-
27
- expect(tracks).toHaveLength(3)
28
- expect(wrapper.findAll('.waveform-chart__track--empty')).toHaveLength(0)
29
- expect(wrapper.findAll('.waveform-chart__grid-slot-placeholder')).toHaveLength(0)
30
- expect(last.left).toBe(0)
31
- expect(last.width).toBeGreaterThan(first.width * 1.8)
32
- expect(last.top).toBeGreaterThan(first.top)
33
- if (displayMode === 'independent') {
34
- const overlays = wrapper.findAll('.waveform-chart__overlay--independent')
35
- expect(overlays).toHaveLength(3)
36
- expect(Number(overlays[2]?.attributes('width'))).toBe(last.width)
37
- } else {
38
- expect(wrapper.findAll('.waveform-chart__overlay--shared')).toHaveLength(1)
39
- }
40
- },
41
- )
42
-
43
- it('removes unused one-column rows and redistributes chart height', async () => {
44
- const base = await mountSizedChart(gridSeries(3), {
45
- displayMode: 'independent',
46
- grid: { rowCount: 4, columnCount: 1 },
47
- })
48
- const filled = await mountSizedChart(gridSeries(3), {
49
- displayMode: 'independent',
50
- grid: { rowCount: 4, columnCount: 1, fillIncompleteLastRow: true },
51
- })
52
- const baseFirst = geometry(base.findAll('.waveform-chart__track')[0]!)
53
- const filledTracks = filled.findAll('.waveform-chart__track')
54
- const filledFirst = geometry(filledTracks[0]!)
55
- const filledLast = geometry(filledTracks[2]!)
56
-
57
- expect(filledTracks).toHaveLength(3)
58
- expect(filledFirst.height).toBeGreaterThan(baseFirst.height)
59
- expect(filledLast.top + filledLast.height).toBeGreaterThan(baseFirst.height * 3)
60
- })
61
-
62
- it('keeps the final two tracks evenly distributed in a two by three grid', async () => {
63
- const wrapper = await mountSizedChart(gridSeries(5), {
64
- displayMode: 'separated',
65
- grid: { rowCount: 2, columnCount: 3, fillIncompleteLastRow: true },
66
- })
67
- const tracks = wrapper.findAll('.waveform-chart__track').map(geometry)
68
-
69
- expect(tracks).toHaveLength(5)
70
- expect(tracks[3]?.left).toBe(0)
71
- expect(tracks[3]?.width).toBeCloseTo(tracks[4]?.width ?? 0)
72
- expect(tracks[4]?.left).toBeGreaterThan((tracks[3]?.width ?? 0) + 1)
73
- })
74
-
75
- it('keeps full pages unchanged and fills a partial second page', async () => {
76
- const data = gridSeries(5)
77
- const standard = await mountSizedChart(data, {
78
- displayMode: 'separated',
79
- grid: { rowCount: 2, columnCount: 2 },
80
- })
81
- const filled = await mountSizedChart(data, {
82
- displayMode: 'separated',
83
- grid: { rowCount: 2, columnCount: 2, fillIncompleteLastRow: true },
84
- })
85
- const standardFirstPage = standard.findAll('.waveform-chart__track').map(geometry)
86
- expect(filled.findAll('.waveform-chart__track').map(geometry)).toEqual(standardFirstPage)
87
-
88
- await filled.get('.ant-pagination-next button').trigger('click')
89
- await flushPromises()
90
- const secondPage = filled.findAll('.waveform-chart__track').map(geometry)
91
- expect(secondPage).toHaveLength(1)
92
- expect(secondPage[0]?.left).toBe(0)
93
- expect(secondPage[0]?.width).toBeGreaterThan(standardFirstPage[0]?.width ?? 0)
94
- })
95
- })
@@ -1,155 +0,0 @@
1
- import { flushPromises } from '@vue/test-utils'
2
- import { describe, expect, it } from 'vitest'
3
-
4
- import { flushAnimationFrames } from '../../test/setup'
5
- import { mountSizedChart } from '../../test/waveformChart'
6
-
7
- describe('WaveformChart incomplete data hover', () => {
8
- it.each(['separated', 'compact'] as const)(
9
- 'clamps each track crosshair and tooltip values to data endpoints in %s mode',
10
- async (displayMode) => {
11
- const wrapper = await mountSizedChart(
12
- {
13
- kind: 'series',
14
- series: [
15
- {
16
- name: '完整数据',
17
- data: {
18
- kind: 'points',
19
- points: [
20
- { x: -1, y: 1 },
21
- { x: -0.5, y: 2 },
22
- { x: 1, y: 3 },
23
- ],
24
- },
25
- },
26
- {
27
- name: '从零开始',
28
- data: {
29
- kind: 'points',
30
- points: [
31
- { x: 0, y: 10 },
32
- { x: 1, y: 20 },
33
- ],
34
- },
35
- },
36
- ],
37
- },
38
- { displayMode },
39
- )
40
- const overlay = wrapper.get('.waveform-chart__overlay--shared')
41
- const overlayWidth = Number(overlay.attributes('width'))
42
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
43
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
44
- })
45
-
46
- overlay.element.dispatchEvent(
47
- new MouseEvent('pointermove', {
48
- clientX: overlayWidth / 4,
49
- clientY: 120,
50
- bubbles: true,
51
- }),
52
- )
53
- flushAnimationFrames()
54
- await flushPromises()
55
-
56
- const crosshairPositions = wrapper
57
- .findAll('.waveform-chart__crosshair line')
58
- .map((line) => Number(line.attributes('x1')))
59
- expect(crosshairPositions).toHaveLength(2)
60
- expect(crosshairPositions[0]).toBeCloseTo(overlayWidth / 4)
61
- expect(crosshairPositions[1]).toBeCloseTo(overlayWidth / 2)
62
- const tooltipRows = wrapper.findAll('.waveform-chart__tooltip-series')
63
- expect(tooltipRows).toHaveLength(2)
64
- expect(tooltipRows[0].text()).toContain('(x:-500 y:2)')
65
- expect(tooltipRows[1].text()).toContain('(x:0 y:10)')
66
- expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([{ x: -0.5, y: 2 }])
67
- },
68
- )
69
-
70
- it.each(['separated', 'compact'] as const)(
71
- 'uses the longest series for a shared track crosshair while clamping each tooltip row in %s mode',
72
- async (displayMode) => {
73
- const wrapper = await mountSizedChart(
74
- {
75
- kind: 'series',
76
- series: [
77
- {
78
- name: '范围参考',
79
- data: {
80
- kind: 'points',
81
- points: [
82
- { x: -2, y: 1 },
83
- { x: 2, y: 2 },
84
- ],
85
- },
86
- },
87
- {
88
- name: '最长曲线',
89
- trackId: 'same-track',
90
- data: {
91
- kind: 'points',
92
- points: [
93
- { x: 0, y: 10 },
94
- { x: 1, y: 20 },
95
- ],
96
- },
97
- },
98
- {
99
- name: '较短曲线',
100
- trackId: 'same-track',
101
- data: {
102
- kind: 'points',
103
- points: [
104
- { x: 0.25, y: 30 },
105
- { x: 0.5, y: 40 },
106
- ],
107
- },
108
- },
109
- ],
110
- },
111
- { displayMode },
112
- )
113
- const overlay = wrapper.get('.waveform-chart__overlay--shared')
114
- const overlayWidth = Number(overlay.attributes('width'))
115
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
116
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
117
- })
118
-
119
- overlay.element.dispatchEvent(
120
- new MouseEvent('pointermove', {
121
- clientX: overlayWidth / 8,
122
- clientY: 120,
123
- bubbles: true,
124
- }),
125
- )
126
- flushAnimationFrames()
127
- await flushPromises()
128
-
129
- let crosshairPositions = wrapper
130
- .findAll('.waveform-chart__crosshair line')
131
- .map((line) => Number(line.attributes('x1')))
132
- expect(crosshairPositions).toHaveLength(2)
133
- expect(crosshairPositions[1]).toBeCloseTo(overlayWidth / 2)
134
- expect(wrapper.text()).toContain('最长曲线(x:0 y:10)')
135
- expect(wrapper.text()).toContain('较短曲线(x:250 y:30)')
136
-
137
- overlay.element.dispatchEvent(
138
- new MouseEvent('pointermove', {
139
- clientX: (overlayWidth * 7) / 8,
140
- clientY: 120,
141
- bubbles: true,
142
- }),
143
- )
144
- flushAnimationFrames()
145
- await flushPromises()
146
-
147
- crosshairPositions = wrapper
148
- .findAll('.waveform-chart__crosshair line')
149
- .map((line) => Number(line.attributes('x1')))
150
- expect(crosshairPositions[1]).toBeCloseTo((overlayWidth * 3) / 4)
151
- expect(wrapper.text()).toContain('最长曲线(x:1,000 y:20)')
152
- expect(wrapper.text()).toContain('较短曲线(x:500 y:40)')
153
- },
154
- )
155
- })