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,358 +0,0 @@
1
- import { flushPromises, mount } from '@vue/test-utils'
2
- import { describe, expect, it, vi } from 'vitest'
3
-
4
- import { flushAnimationFrames, pendingAnimationFrameCount } from '../../test/setup'
5
- import WaveformChart from '../WaveformChart.vue'
6
-
7
- import { gridSeries, mountSizedChart } from '../../test/waveformChart'
8
-
9
- describe('WaveformChart', () => {
10
- it('suppresses native context menus across the waveform component', async () => {
11
- const wrapper = await mountSizedChart(gridSeries(2), {
12
- title: { text: '波形标题' },
13
- grid: { rowCount: 1, columnCount: 1, showPagination: true },
14
- })
15
-
16
- for (const selector of [
17
- '.waveform-chart__title-area',
18
- '.waveform-chart__grid',
19
- '.waveform-chart__overlay',
20
- '.waveform-chart__pagination',
21
- ]) {
22
- const event = new MouseEvent('contextmenu', { bubbles: true, cancelable: true })
23
- const dispatched = wrapper.get(selector).element.dispatchEvent(event)
24
-
25
- expect(dispatched).toBe(false)
26
- expect(event.defaultPrevented).toBe(true)
27
- }
28
-
29
- const sharedWrapper = await mountSizedChart(
30
- {
31
- kind: 'points',
32
- points: [
33
- { x: 0, y: 0 },
34
- { x: 1, y: 1 },
35
- ],
36
- },
37
- { displayMode: 'separated' },
38
- )
39
- const sharedEvent = new MouseEvent('contextmenu', { bubbles: true, cancelable: true })
40
- const sharedDispatched = sharedWrapper
41
- .get('.waveform-chart__overlay--shared')
42
- .element.dispatchEvent(sharedEvent)
43
-
44
- expect(sharedDispatched).toBe(false)
45
- expect(sharedEvent.defaultPrevented).toBe(true)
46
- })
47
-
48
- it('preserves native context menus for editable controls', async () => {
49
- const wrapper = await mountSizedChart({ kind: 'samples', values: [0, 1], sampleRate: 1 })
50
- const editableElements = [
51
- document.createElement('input'),
52
- document.createElement('textarea'),
53
- document.createElement('div'),
54
- ]
55
- editableElements[2]?.setAttribute('contenteditable', 'true')
56
-
57
- for (const element of editableElements) {
58
- wrapper.element.appendChild(element)
59
- const event = new MouseEvent('contextmenu', { bubbles: true, cancelable: true })
60
- const dispatched = element.dispatchEvent(event)
61
-
62
- expect(dispatched).toBe(true)
63
- expect(event.defaultPrevented).toBe(false)
64
- }
65
- })
66
-
67
- it('applies size fallbacks for minimum, negative, and non-finite values', async () => {
68
- const minimumWrapper = mount(WaveformChart, {
69
- props: {
70
- data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
71
- width: -20,
72
- height: -20,
73
- },
74
- })
75
-
76
- expect(minimumWrapper.attributes('style')).toContain('width: 0px')
77
- expect(minimumWrapper.attributes('style')).toContain('height: 180px')
78
- expect(minimumWrapper.get('.waveform-chart__svg').attributes('height')).toBe('180')
79
-
80
- const adaptiveWrapper = mount(WaveformChart, {
81
- props: {
82
- data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
83
- width: Number.POSITIVE_INFINITY,
84
- height: Number.NaN,
85
- },
86
- })
87
-
88
- expect(adaptiveWrapper.attributes('style')).toContain('width: 100%')
89
- expect(adaptiveWrapper.attributes('style')).toContain('height: 100%')
90
- })
91
-
92
- it('keeps a 100k-point SVG path bounded by the plot width', async () => {
93
- const sourcePoints = Array.from({ length: 100_000 }, (_, index) => ({
94
- x: index / 1_000,
95
- y: index === 50_001 ? 100 : Math.sin(index / 50),
96
- }))
97
- const wrapper = await mountSizedChart(
98
- { kind: 'points', points: sourcePoints },
99
- { rendering: { downsampleThreshold: 1_000, maxPointsPerPixel: 4 } },
100
- )
101
- const overlayWidth = Number(wrapper.get('.waveform-chart__overlay').attributes('width'))
102
- await vi.waitFor(
103
- () => {
104
- expect(wrapper.emitted('sampling-complete')?.length).toBeGreaterThan(0)
105
- const path = wrapper.get('.waveform-chart__line').attributes('d') ?? ''
106
- const renderedPointCount = path.match(/[ML]/g)?.length ?? 0
107
- expect(renderedPointCount).toBeGreaterThan(0)
108
- expect(renderedPointCount).toBeLessThanOrEqual(Math.floor(overlayWidth * 4) + 2)
109
- expect(path).toContain(',0')
110
- },
111
- { timeout: 5_000 },
112
- )
113
- })
114
-
115
- it('bounds dense decorations by pixel spacing while keeping one SVG path per series', async () => {
116
- const sourcePoints = Array.from({ length: 1_000 }, (_, index) => ({
117
- x: index,
118
- y: Math.sin(index / 20),
119
- error: 0.1,
120
- }))
121
- const wrapper = await mountSizedChart(
122
- {
123
- kind: 'series',
124
- series: [
125
- {
126
- id: 'dense-decorations',
127
- name: '密集标记',
128
- pointType: 'triangle',
129
- errorBar: { visible: true },
130
- data: { kind: 'points', points: sourcePoints },
131
- },
132
- ],
133
- },
134
- { rendering: { pointMinSpacing: 10, errorBarMinSpacing: 12 } },
135
- )
136
- const overlayWidth = Number(wrapper.get('.waveform-chart__overlay').attributes('width'))
137
- const pointPaths = wrapper.findAll('.waveform-chart__point')
138
- const errorBarPaths = wrapper.findAll('.waveform-chart__error-bar')
139
- const pointCount = pointPaths[0]?.attributes('d')?.match(/M/g)?.length ?? 0
140
- const errorBarCount = (errorBarPaths[0]?.attributes('d')?.match(/M/g)?.length ?? 0) / 3
141
-
142
- expect(pointPaths).toHaveLength(1)
143
- expect(errorBarPaths).toHaveLength(1)
144
- expect(pointCount).toBeLessThanOrEqual(Math.ceil(overlayWidth / 10) + 2)
145
- expect(errorBarCount).toBeLessThanOrEqual(Math.ceil(overlayWidth / 12) + 2)
146
- })
147
-
148
- it('renders explicit points and supports a single point', async () => {
149
- const wrapper = await mountSizedChart({ kind: 'points', points: [{ x: 3, y: 8 }] })
150
-
151
- expect(wrapper.get('.waveform-chart__line').attributes('d')).toContain('M')
152
- expect(wrapper.find('.waveform-chart__empty').exists()).toBe(false)
153
- })
154
-
155
- it('shows an empty state for empty and invalid data', async () => {
156
- const wrapper = await mountSizedChart({ kind: 'samples', values: [1], sampleRate: -1 })
157
-
158
- expect(wrapper.get('.waveform-chart__empty').text()).toContain('暂无有效波形数据')
159
- expect(wrapper.find('.waveform-chart__line').exists()).toBe(false)
160
- })
161
-
162
- it('emits the nearest point on hover and clears it on leave', async () => {
163
- const wrapper = await mountSizedChart(
164
- {
165
- kind: 'points',
166
- points: [
167
- { x: 0, y: 0 },
168
- { x: 1, y: 5 },
169
- ],
170
- },
171
- { grid: { rowCount: 1, columnCount: 1 } },
172
- )
173
- const overlay = wrapper.get('.waveform-chart__overlay')
174
- const overlayWidth = Number(overlay.attributes('width'))
175
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
176
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
177
- })
178
-
179
- overlay.element.dispatchEvent(
180
- new MouseEvent('pointermove', { clientX: 700, clientY: 120, bubbles: true }),
181
- )
182
- flushAnimationFrames()
183
- await flushPromises()
184
- expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([{ x: 1, y: 5 }])
185
- expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(true)
186
- expect(wrapper.get('.waveform-tooltip__series-label').text()).toContain('未配置炮号:')
187
- const crosshairLines = wrapper.findAll('.waveform-chart__crosshair line')
188
- expect(crosshairLines).toHaveLength(1)
189
- expect(crosshairLines[0].attributes('x1')).toBe(crosshairLines[0].attributes('x2'))
190
- expect(crosshairLines[0].attributes('y1')).toBe('0')
191
- expect(wrapper.find('.waveform-chart__crosshair circle').exists()).toBe(false)
192
-
193
- await overlay.trigger('pointerleave')
194
- expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([null])
195
- })
196
-
197
- it('clears hover when the pointer leaves the chart and restores it only after a new plot move', async () => {
198
- const wrapper = await mountSizedChart(
199
- {
200
- kind: 'points',
201
- points: [
202
- { x: 0, y: 0 },
203
- { x: 1, y: 5 },
204
- ],
205
- },
206
- { grid: { rowCount: 1, columnCount: 1 } },
207
- )
208
- const overlay = wrapper.get('.waveform-chart__overlay')
209
- const overlayWidth = Number(overlay.attributes('width'))
210
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
211
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
212
- })
213
-
214
- overlay.element.dispatchEvent(
215
- new MouseEvent('pointermove', { clientX: overlayWidth, clientY: 120, bubbles: true }),
216
- )
217
- flushAnimationFrames()
218
- await flushPromises()
219
- expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(true)
220
-
221
- await wrapper.trigger('pointerleave')
222
- expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(false)
223
- expect(wrapper.find('.waveform-chart__crosshair').exists()).toBe(false)
224
- expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([null])
225
-
226
- await wrapper.trigger('pointerenter')
227
- expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(false)
228
-
229
- overlay.element.dispatchEvent(
230
- new MouseEvent('pointermove', { clientX: 0, clientY: 120, bubbles: true }),
231
- )
232
- flushAnimationFrames()
233
- await flushPromises()
234
- expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(true)
235
- })
236
-
237
- it('hides the numeric tooltip and crosshair when showTooltip is disabled', async () => {
238
- const wrapper = await mountSizedChart(
239
- {
240
- kind: 'points',
241
- points: [
242
- { x: 0, y: 0 },
243
- { x: 1, y: 5 },
244
- ],
245
- },
246
- { grid: { rowCount: 1, columnCount: 1 }, showTooltip: false },
247
- )
248
- const overlay = wrapper.get('.waveform-chart__overlay')
249
- const overlayWidth = Number(overlay.attributes('width'))
250
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
251
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
252
- })
253
-
254
- overlay.element.dispatchEvent(
255
- new MouseEvent('pointermove', { clientX: 700, clientY: 120, bubbles: true }),
256
- )
257
- flushAnimationFrames()
258
- await flushPromises()
259
-
260
- expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(false)
261
- expect(wrapper.find('.waveform-chart__crosshair').exists()).toBe(false)
262
-
263
- await wrapper.setProps({ showTooltip: true })
264
- await flushPromises()
265
- expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(true)
266
- expect(wrapper.find('.waveform-chart__crosshair').exists()).toBe(true)
267
- })
268
-
269
- it('coalesces pointer moves per frame and cancels pending hover work', async () => {
270
- const wrapper = await mountSizedChart(
271
- {
272
- kind: 'points',
273
- points: [
274
- { x: 0, y: 0 },
275
- { x: 1, y: 5 },
276
- ],
277
- },
278
- { grid: { rowCount: 1, columnCount: 1 } },
279
- )
280
- const overlay = wrapper.get('.waveform-chart__overlay')
281
- const overlayWidth = Number(overlay.attributes('width'))
282
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
283
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
284
- })
285
- const emittedBeforeMove = wrapper.emitted('point-hover')?.length ?? 0
286
-
287
- overlay.element.dispatchEvent(
288
- new MouseEvent('pointermove', { clientX: 0, clientY: 100, bubbles: true }),
289
- )
290
- overlay.element.dispatchEvent(
291
- new MouseEvent('pointermove', { clientX: overlayWidth, clientY: 120, bubbles: true }),
292
- )
293
-
294
- expect(pendingAnimationFrameCount()).toBe(1)
295
- expect(wrapper.emitted('point-hover')?.length ?? 0).toBe(emittedBeforeMove)
296
- flushAnimationFrames()
297
- await flushPromises()
298
- expect(wrapper.emitted('point-hover')).toHaveLength(emittedBeforeMove + 1)
299
- expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([{ x: 1, y: 5 }])
300
-
301
- overlay.element.dispatchEvent(
302
- new MouseEvent('pointermove', { clientX: 0, clientY: 100, bubbles: true }),
303
- )
304
- expect(pendingAnimationFrameCount()).toBe(1)
305
- await overlay.trigger('pointerleave')
306
- const emittedAfterLeave = wrapper.emitted('point-hover')?.length ?? 0
307
- expect(pendingAnimationFrameCount()).toBe(0)
308
- flushAnimationFrames()
309
- await flushPromises()
310
- expect(wrapper.emitted('point-hover')).toHaveLength(emittedAfterLeave)
311
- expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([null])
312
-
313
- overlay.element.dispatchEvent(
314
- new MouseEvent('pointermove', { clientX: overlayWidth, clientY: 100, bubbles: true }),
315
- )
316
- expect(pendingAnimationFrameCount()).toBe(1)
317
- wrapper.unmount()
318
- expect(pendingAnimationFrameCount()).toBe(0)
319
- })
320
-
321
- it('isolates hover rendering from the chart, track, and waveform path subtrees', async () => {
322
- const wrapper = await mountSizedChart(
323
- {
324
- kind: 'points',
325
- points: [
326
- { x: 0, y: 0 },
327
- { x: 1, y: 5 },
328
- ],
329
- },
330
- { grid: { rowCount: 1, columnCount: 1 } },
331
- )
332
- const overlay = wrapper.get('.waveform-chart__overlay')
333
- const overlayWidth = Number(overlay.attributes('width'))
334
- Object.defineProperty(overlay.element, 'getBoundingClientRect', {
335
- value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
336
- })
337
-
338
- const track = wrapper.getComponent({ name: 'WaveformTrack' })
339
- const seriesLayer = wrapper.getComponent({ name: 'WaveformSeriesLayer' })
340
- const chartUpdate = vi.spyOn(wrapper.vm.$, 'update')
341
- const trackUpdate = vi.spyOn(track.vm.$, 'update')
342
- const seriesLayerUpdate = vi.spyOn(seriesLayer.vm.$, 'update')
343
- const pathBeforeHover = wrapper.get('.waveform-chart__line').element
344
-
345
- overlay.element.dispatchEvent(
346
- new MouseEvent('pointermove', { clientX: overlayWidth, clientY: 120, bubbles: true }),
347
- )
348
- flushAnimationFrames()
349
- await flushPromises()
350
-
351
- expect(wrapper.get('.waveform-tooltip__series-label').text()).toContain('未配置炮号:')
352
- expect(wrapper.findAll('.waveform-chart__crosshair line')).toHaveLength(1)
353
- expect(wrapper.get('.waveform-chart__line').element).toBe(pathBeforeHover)
354
- expect(chartUpdate).not.toHaveBeenCalled()
355
- expect(trackUpdate).not.toHaveBeenCalled()
356
- expect(seriesLayerUpdate).not.toHaveBeenCalled()
357
- })
358
- })
@@ -1,310 +0,0 @@
1
- import { flushPromises } from '@vue/test-utils'
2
- import { describe, expect, it } from 'vitest'
3
-
4
- import { type WaveformData } from '../waveform'
5
-
6
- import { gridSeries, mountSizedChart } from '../../test/waveformChart'
7
-
8
- describe('WaveformChart', () => {
9
- it('keeps other independent tracks unchanged when one data window is replaced', async () => {
10
- const wrapper = await mountSizedChart(gridSeries(2), {
11
- displayMode: 'independent',
12
- grid: { rowCount: 1, columnCount: 2 },
13
- })
14
- const originalEndpoints = wrapper
15
- .findAll('.waveform-chart__axis-endpoint--end')
16
- .map((endpoint) => endpoint.text())
17
-
18
- await wrapper.setProps({
19
- data: {
20
- kind: 'series',
21
- series: [
22
- {
23
- id: 'channel-0',
24
- name: '通道 1',
25
- data: {
26
- kind: 'points',
27
- points: [
28
- { x: 0.25, y: 0 },
29
- { x: 0.75, y: 1 },
30
- ],
31
- },
32
- },
33
- {
34
- id: 'channel-1',
35
- name: '通道 2',
36
- data: {
37
- kind: 'points',
38
- points: [
39
- { x: 0, y: 1 },
40
- { x: 1, y: 2 },
41
- ],
42
- },
43
- },
44
- ],
45
- },
46
- })
47
- await flushPromises()
48
-
49
- const nextEndpoints = wrapper
50
- .findAll('.waveform-chart__axis-endpoint--end')
51
- .map((endpoint) => endpoint.text())
52
- expect(nextEndpoints[0]).not.toBe(originalEndpoints[0])
53
- expect(nextEndpoints[1]).toBe(originalEndpoints[1])
54
- })
55
-
56
- it('rebuilds cached domains only when the data reference changes', async () => {
57
- const firstData: WaveformData = {
58
- kind: 'points',
59
- points: [
60
- { x: 0, y: 0 },
61
- { x: 1, y: 1 },
62
- ],
63
- }
64
- const wrapper = await mountSizedChart(firstData)
65
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
66
-
67
- firstData.points.push({ x: 2, y: 2 })
68
- await flushPromises()
69
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
70
-
71
- await wrapper.setProps({ data: { ...firstData, points: [...firstData.points] } })
72
- await flushPromises()
73
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
74
- })
75
-
76
- it('keeps controlled annotations when replacing the loaded data window', async () => {
77
- const annotations = [
78
- { id: 'window-note', seriesId: 'series-0', x: 0.5, y: 0.5, text: '窗口标注' },
79
- ]
80
- const wrapper = await mountSizedChart(
81
- {
82
- kind: 'points',
83
- points: [
84
- { x: 0, y: 0 },
85
- { x: 1, y: 1 },
86
- ],
87
- },
88
- { annotations },
89
- )
90
- expect(wrapper.find('[data-annotation-id="window-note"]').exists()).toBe(true)
91
-
92
- await wrapper.setProps({
93
- data: {
94
- kind: 'points',
95
- points: [
96
- { x: 2, y: 0 },
97
- { x: 3, y: 1 },
98
- ],
99
- },
100
- })
101
- await flushPromises()
102
-
103
- expect(wrapper.find('[data-annotation-id="window-note"]').exists()).toBe(false)
104
- expect(annotations).toEqual([
105
- { id: 'window-note', seriesId: 'series-0', x: 0.5, y: 0.5, text: '窗口标注' },
106
- ])
107
-
108
- await wrapper.setProps({
109
- data: {
110
- kind: 'points',
111
- points: [
112
- { x: 0, y: 0 },
113
- { x: 1, y: 1 },
114
- ],
115
- },
116
- })
117
- await flushPromises()
118
- expect(wrapper.find('[data-annotation-id="window-note"]').exists()).toBe(true)
119
- })
120
-
121
- it('renders named multi-channel paths as independent tracks by default', async () => {
122
- const wrapper = await mountSizedChart({
123
- kind: 'series',
124
- series: [
125
- {
126
- name: 'BT2_2M',
127
- unit: 'T',
128
- data: {
129
- kind: 'points',
130
- points: [
131
- { x: 0, y: 0 },
132
- { x: 1, y: 2 },
133
- ],
134
- },
135
- },
136
- {
137
- name: 'BT1_2M',
138
- unit: 'T',
139
- data: {
140
- kind: 'points',
141
- points: [
142
- { x: 0, y: -1 },
143
- { x: 2, y: 3 },
144
- ],
145
- },
146
- },
147
- ],
148
- })
149
- const paths = wrapper.findAll('.waveform-chart__line')
150
-
151
- expect(paths).toHaveLength(2)
152
- expect(wrapper.findAll('.waveform-chart__svg')).toHaveLength(1)
153
- expect(wrapper.findAll('.waveform-chart__track')).toHaveLength(2)
154
- expect(wrapper.findAll('.waveform-chart__axis--x')).toHaveLength(2)
155
- expect(wrapper.findAll('.waveform-chart__overlay--independent')).toHaveLength(2)
156
- expect(paths[0].attributes('stroke')).toBe('#0960bd')
157
- expect(paths[1].attributes('stroke')).toBe('#ff7f0e')
158
- expect(paths[0].attributes('data-series-name')).toBe('BT2_2M')
159
- const yAxisLabels = wrapper.findAll('.waveform-chart__y-axis-label')
160
- expect(yAxisLabels.map((label) => label.text())).toEqual(['BT2_2M', 'BT1_2M'])
161
- expect(
162
- wrapper
163
- .findAll('.waveform-chart__axis--y .tick text')
164
- .map((label) => label.text())
165
- .some((label) => label.includes('(T)')),
166
- ).toBe(true)
167
- expect(yAxisLabels.map((label) => label.attributes('fill'))).toEqual(['#0960bd', '#ff7f0e'])
168
- const labelX = Number(
169
- yAxisLabels[0].attributes('transform')?.match(/^translate\(([-\d.]+),/)?.[1],
170
- )
171
- expect(labelX).toBeLessThan(-46)
172
- expect(Number(wrapper.get('.waveform-chart__y-axis-label-bg').attributes('x'))).toBe(labelX - 6)
173
- expect(yAxisLabels.every((label) => !label.text().includes('(T)'))).toBe(true)
174
- expect(wrapper.findAll('.waveform-chart__track-label')).toHaveLength(0)
175
- expect(
176
- wrapper.findAll('.waveform-chart__axis-endpoint--end').map((item) => item.text()),
177
- ).toEqual(['1000', '2000'])
178
- })
179
-
180
- it('keeps the zero Y-axis label on upper compact tracks', async () => {
181
- const wrapper = await mountSizedChart(
182
- {
183
- kind: 'series',
184
- series: [
185
- {
186
- name: 'first',
187
- data: {
188
- kind: 'points',
189
- points: [
190
- { x: 0, y: 0 },
191
- { x: 1, y: 1 },
192
- ],
193
- },
194
- },
195
- {
196
- name: 'second',
197
- data: {
198
- kind: 'points',
199
- points: [
200
- { x: 0, y: -1 },
201
- { x: 1, y: 2 },
202
- ],
203
- },
204
- },
205
- ],
206
- },
207
- { displayMode: 'compact' },
208
- )
209
-
210
- const tracks = wrapper.findAll('.waveform-chart__track')
211
- const firstAxisTicks = tracks[0].findAll('.waveform-chart__axis--y .tick')
212
- const zeroTicks = firstAxisTicks.filter((tick) => Number(tick.text()) === 0)
213
- const firstTrackHeight = Number(tracks[0].attributes('data-track-height'))
214
- const zeroTickY = Number(
215
- zeroTicks[0].attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)?.[1],
216
- )
217
-
218
- expect(zeroTicks).toHaveLength(1)
219
- expect(Math.abs(zeroTickY - firstTrackHeight)).toBeLessThanOrEqual(1)
220
- })
221
-
222
- it.each(['independent', 'separated', 'compact'] as const)(
223
- 'shows a non-zero Y-axis start value once in %s mode',
224
- async (displayMode) => {
225
- const wrapper = await mountSizedChart(
226
- {
227
- kind: 'series',
228
- series: [
229
- {
230
- name: 'first',
231
- data: {
232
- kind: 'points',
233
- points: [
234
- { x: 0, y: 0.11 },
235
- { x: 1, y: 0.89 },
236
- ],
237
- },
238
- },
239
- {
240
- name: 'second',
241
- data: {
242
- kind: 'points',
243
- points: [
244
- { x: 0, y: 4 },
245
- { x: 1, y: 5 },
246
- ],
247
- },
248
- },
249
- ],
250
- },
251
- { displayMode },
252
- )
253
-
254
- const firstTrack = wrapper.findAll('.waveform-chart__track')[0]
255
- const firstTrackHeight = Number(firstTrack.attributes('data-track-height'))
256
- const startTicks = firstTrack.findAll('.waveform-chart__axis--y .tick').filter((tick) => {
257
- const match = tick.attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)
258
- return match ? Math.abs(Number(match[1]) - firstTrackHeight) <= 1 : false
259
- })
260
-
261
- expect(startTicks).toHaveLength(1)
262
- expect(Number(startTicks[0].text())).toBe(0)
263
- },
264
- )
265
-
266
- it.each(['independent', 'separated'] as const)(
267
- 'shows the Y-axis end value once on every track in %s mode',
268
- async (displayMode) => {
269
- const wrapper = await mountSizedChart(
270
- {
271
- kind: 'series',
272
- series: [
273
- {
274
- name: 'first',
275
- data: {
276
- kind: 'points',
277
- points: [
278
- { x: 0, y: 0.11 },
279
- { x: 1, y: 0.89 },
280
- ],
281
- },
282
- },
283
- {
284
- name: 'second',
285
- data: {
286
- kind: 'points',
287
- points: [
288
- { x: 0, y: 4.11 },
289
- { x: 1, y: 4.89 },
290
- ],
291
- },
292
- },
293
- ],
294
- },
295
- { displayMode },
296
- )
297
-
298
- const expectedEndValues = [1, 5]
299
- wrapper.findAll('.waveform-chart__track').forEach((track, index) => {
300
- const endTicks = track.findAll('.waveform-chart__axis--y .tick').filter((tick) => {
301
- const match = tick.attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)
302
- return match ? Math.abs(Number(match[1])) <= 1 : false
303
- })
304
-
305
- expect(endTicks).toHaveLength(1)
306
- expect(Number(endTicks[0].text())).toBe(expectedEndValues[index])
307
- })
308
- },
309
- )
310
- })