waveform-analysis 0.1.63 → 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.
- package/README.md +9 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +953 -946
- package/dist/style.css +1 -1
- package/dist/types/types/chart.d.ts +2 -1
- package/package.json +1 -1
- package/src/components/WaveformChart.vue +1 -1
- package/src/components/core/xDomain.ts +26 -2
- package/src/components/rendering/WaveformLegend.vue +1 -1
- package/src/components/rendering/WaveformSeriesLayer.vue +1 -1
- package/src/demo/DemoChartHost.vue +1 -0
- package/src/types/chart.ts +2 -1
- package/src/App.test.ts +0 -400
- package/src/components/annotation/components.test.ts +0 -372
- package/src/components/annotation/markup.test.ts +0 -371
- package/src/components/annotation/serialization.test.ts +0 -114
- package/src/components/core/constants.test.ts +0 -29
- package/src/components/core/fixedYDomainLayout.test.ts +0 -204
- package/src/components/core/grid.test.ts +0 -197
- package/src/components/core/latestTaskScheduler.test.ts +0 -51
- package/src/components/core/layout.test.ts +0 -373
- package/src/components/core/title.test.ts +0 -76
- package/src/components/core/useWaveformData.test.ts +0 -95
- package/src/components/core/useWaveformRenderSampling.test.ts +0 -17
- package/src/components/core/xDomain.test.ts +0 -28
- package/src/components/core/yAxisNice.test.ts +0 -174
- package/src/components/core/yAxisSlotLayout.test.ts +0 -165
- package/src/components/core/yDomain.test.ts +0 -56
- package/src/components/interaction/WaveformTooltip.test.ts +0 -276
- package/src/components/interaction/viewportInteractionState.test.ts +0 -101
- package/src/components/utils/useAnimationFrameThrottle.test.ts +0 -53
- package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +0 -257
- package/src/components/waveformChartCases/compactAndSharedHover.test.ts +0 -358
- package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +0 -358
- package/src/components/waveformChartCases/dataLifecycle.test.ts +0 -310
- package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +0 -389
- package/src/components/waveformChartCases/gridFill.test.ts +0 -95
- package/src/components/waveformChartCases/incompleteDataHover.test.ts +0 -155
- package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +0 -328
- package/src/components/waveformChartCases/layoutAndDomains.test.ts +0 -371
- package/src/components/waveformChartCases/legendVisibility.test.ts +0 -350
- package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +0 -400
- package/src/components/waveformChartCases/multiAxisLabelAlignment.test.ts +0 -61
- package/src/components/waveformChartCases/presentationAndAxes.test.ts +0 -345
- package/src/components/waveformChartCases/presentationMode.test.ts +0 -327
- package/src/components/waveformChartCases/renderingStyles.test.ts +0 -283
- package/src/components/waveformChartCases/sizingAndTitles.test.ts +0 -370
- package/src/components/waveformChartCases/trackOrder.test.ts +0 -323
- package/src/components/waveformChartCases/viewportBoxAndPan.test.ts +0 -365
- package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +0 -311
- package/src/components/waveformChartCases/viewportLifecycle.test.ts +0 -132
- package/src/components/waveformChartCases/viewportReset.test.ts +0 -251
- package/src/components/waveformChartCases/wheelZoomOut.test.ts +0 -114
- package/src/components/waveformChartCases/workerSamplingIntegration.test.ts +0 -117
- package/src/components/waveformChartCases/xDomainStrategy.test.ts +0 -168
- package/src/components/waveformChartCases/yAxisLayout.test.ts +0 -103
- package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +0 -213
- package/src/components/waveformChartCases/zoomPointConstraints.test.ts +0 -308
- package/src/components/waveformLegendGeometry.test.ts +0 -21
- package/src/components/waveformNormalization.test.ts +0 -170
- package/src/core/data.test.ts +0 -241
- package/src/core/rendering.test.ts +0 -267
- package/src/core/renderingStrategies.test.ts +0 -90
- package/src/core/samplingReference.test.ts +0 -124
- package/src/core/wasmSampling.test.ts +0 -205
- package/src/core/workerSampling/multiresolution.test.ts +0 -69
- package/src/core/workerSampling/protocol.test.ts +0 -34
- package/src/core/workerSampling/repository.test.ts +0 -400
- package/src/core/workerSampling/repositoryCache.test.ts +0 -163
- package/src/core/workerSampling/repositorySampling.test.ts +0 -106
- package/src/core/workerSampling/repositoryTypedDataset.test.ts +0 -89
- package/src/core/workerSampling/repositoryWasmDataset.test.ts +0 -70
- package/src/data/simulatedWaveforms.test.ts +0 -60
- package/src/demo/plotMarginControls.test.ts +0 -29
- package/src/demo/xAxisLabelControls.test.ts +0 -86
- package/src/test/setup.ts +0 -82
- package/src/test/waveformChart.ts +0 -75
- package/src/utils/domain.test.ts +0 -28
- package/src/utils/formatters.test.ts +0 -131
- package/src/utils/sampling.test.ts +0 -226
- package/src/views/FixedYDomainDemo.test.ts +0 -44
- package/src/views/WasmSamplingDemo.test.ts +0 -13
- package/src/views/wasmSamplingDemoSampling.test.ts +0 -27
|
@@ -1,323 +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 track order', () => {
|
|
8
|
-
const points = (offset: number) => ({
|
|
9
|
-
kind: 'points' as const,
|
|
10
|
-
points: [
|
|
11
|
-
{ x: 0, y: offset },
|
|
12
|
-
{ x: 1, y: offset + 1 },
|
|
13
|
-
],
|
|
14
|
-
})
|
|
15
|
-
const orderedSeries = (trackIds: string[]) => ({
|
|
16
|
-
kind: 'series' as const,
|
|
17
|
-
series: trackIds.map((trackId, index) => ({
|
|
18
|
-
id: trackId,
|
|
19
|
-
trackId,
|
|
20
|
-
name: trackId,
|
|
21
|
-
data: points(index + 1),
|
|
22
|
-
})),
|
|
23
|
-
})
|
|
24
|
-
const mergedFourTrackSeries = () => ({
|
|
25
|
-
kind: 'series' as const,
|
|
26
|
-
series: [
|
|
27
|
-
{ id: 'frame-1-a', trackId: 'frame-1', name: 'frame-1-a', data: points(1) },
|
|
28
|
-
{ id: 'frame-1-b', trackId: 'frame-1', name: 'frame-1-b', data: points(2) },
|
|
29
|
-
{ id: 'frame-3', trackId: 'frame-3', name: 'frame-3', data: points(3) },
|
|
30
|
-
{ id: 'frame-4', trackId: 'frame-4', name: 'frame-4', data: points(4) },
|
|
31
|
-
],
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('keeps a merged source slot empty without adding legend or tooltip data', async () => {
|
|
35
|
-
const wrapper = await mountSizedChart(
|
|
36
|
-
{
|
|
37
|
-
kind: 'series',
|
|
38
|
-
series: [
|
|
39
|
-
{
|
|
40
|
-
id: 'frame-1-a',
|
|
41
|
-
trackId: 'frame-1',
|
|
42
|
-
name: '合并前通道',
|
|
43
|
-
data: {
|
|
44
|
-
kind: 'points',
|
|
45
|
-
points: [
|
|
46
|
-
{ x: 0, y: 1 },
|
|
47
|
-
{ x: 1, y: 2 },
|
|
48
|
-
],
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: 'frame-1-b',
|
|
53
|
-
trackId: 'frame-1',
|
|
54
|
-
name: '合并后通道',
|
|
55
|
-
data: {
|
|
56
|
-
kind: 'points',
|
|
57
|
-
points: [
|
|
58
|
-
{ x: 0, y: 3 },
|
|
59
|
-
{ x: 1, y: 4 },
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: 'frame-3',
|
|
65
|
-
trackId: 'frame-3',
|
|
66
|
-
name: '后续通道',
|
|
67
|
-
data: {
|
|
68
|
-
kind: 'points',
|
|
69
|
-
points: [
|
|
70
|
-
{ x: 0, y: 5 },
|
|
71
|
-
{ x: 1, y: 6 },
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
],
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
displayMode: 'separated',
|
|
79
|
-
grid: { rowCount: 3, trackOrder: ['frame-1', 'frame-2', 'frame-3'] },
|
|
80
|
-
},
|
|
81
|
-
)
|
|
82
|
-
const trackIds = () =>
|
|
83
|
-
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id'))
|
|
84
|
-
|
|
85
|
-
expect(trackIds()).toEqual(['frame-1', 'frame-2', 'frame-3'])
|
|
86
|
-
expect(wrapper.findAll('.waveform-chart__track--empty')).toHaveLength(1)
|
|
87
|
-
expect(
|
|
88
|
-
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
89
|
-
).toEqual(['frame-1-a', 'frame-1-b', 'frame-3'])
|
|
90
|
-
expect(wrapper.findAll('.waveform-chart__legend')).toHaveLength(1)
|
|
91
|
-
|
|
92
|
-
const overlay = wrapper.get('.waveform-chart__overlay--shared')
|
|
93
|
-
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
94
|
-
value: () => ({ left: 0, top: 0, width: 712, height: 290 }),
|
|
95
|
-
})
|
|
96
|
-
overlay.element.dispatchEvent(
|
|
97
|
-
new MouseEvent('pointermove', { clientX: 356, clientY: 150, bubbles: true }),
|
|
98
|
-
)
|
|
99
|
-
flushAnimationFrames()
|
|
100
|
-
await flushPromises()
|
|
101
|
-
|
|
102
|
-
expect(wrapper.findAll('.waveform-chart__tooltip-series')).toHaveLength(3)
|
|
103
|
-
expect(wrapper.get('.waveform-chart__tooltip').text()).not.toContain('frame-2')
|
|
104
|
-
expect(wrapper.findAll('.waveform-chart__crosshair line')).toHaveLength(2)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('keeps incremental watermarks stable when empty ordered tracks are hidden', async () => {
|
|
108
|
-
const wrapper = await mountSizedChart(mergedFourTrackSeries(), {
|
|
109
|
-
frameNumber: 1,
|
|
110
|
-
grid: {
|
|
111
|
-
rowCount: 2,
|
|
112
|
-
columnCount: 1,
|
|
113
|
-
hideEmptyTracks: true,
|
|
114
|
-
trackOrder: ['frame-1', 'frame-2', 'frame-3', 'frame-4'],
|
|
115
|
-
},
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
expect(
|
|
119
|
-
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id')),
|
|
120
|
-
).toEqual(['frame-1', 'frame-3'])
|
|
121
|
-
expect(
|
|
122
|
-
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
123
|
-
).toEqual(['1', '3'])
|
|
124
|
-
|
|
125
|
-
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
126
|
-
|
|
127
|
-
expect(
|
|
128
|
-
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id')),
|
|
129
|
-
).toEqual(['frame-4'])
|
|
130
|
-
expect(
|
|
131
|
-
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
132
|
-
).toEqual(['4'])
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('uses stable track IDs for explicit frame watermark overrides', async () => {
|
|
136
|
-
const wrapper = await mountSizedChart(mergedFourTrackSeries(), {
|
|
137
|
-
frameNumber: 1,
|
|
138
|
-
frameNumbers: { 'frame-1': 'A', 'frame-3': 'C', 'frame-4': 'D' },
|
|
139
|
-
grid: {
|
|
140
|
-
rowCount: 2,
|
|
141
|
-
columnCount: 1,
|
|
142
|
-
hideEmptyTracks: true,
|
|
143
|
-
trackOrder: ['frame-1', 'frame-2', 'frame-3', 'frame-4'],
|
|
144
|
-
},
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
expect(
|
|
148
|
-
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
149
|
-
).toEqual(['A', 'C'])
|
|
150
|
-
|
|
151
|
-
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
152
|
-
|
|
153
|
-
expect(
|
|
154
|
-
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
155
|
-
).toEqual(['D'])
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('keeps the existing incremental watermark fallback when frame mappings are absent', async () => {
|
|
159
|
-
const wrapper = await mountSizedChart(orderedSeries(['frame-1', 'frame-2']), {
|
|
160
|
-
frameNumber: 7,
|
|
161
|
-
grid: { rowCount: 2, columnCount: 1 },
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
expect(
|
|
165
|
-
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
166
|
-
).toEqual(['7', '8'])
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('appends data tracks absent from trackOrder in input order', async () => {
|
|
170
|
-
const wrapper = await mountSizedChart(
|
|
171
|
-
{
|
|
172
|
-
kind: 'series',
|
|
173
|
-
series: [
|
|
174
|
-
{
|
|
175
|
-
id: 'later-a',
|
|
176
|
-
trackId: 'later-a',
|
|
177
|
-
name: 'later-a',
|
|
178
|
-
data: {
|
|
179
|
-
kind: 'points',
|
|
180
|
-
points: [
|
|
181
|
-
{ x: 0, y: 1 },
|
|
182
|
-
{ x: 1, y: 2 },
|
|
183
|
-
],
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
id: 'frame-1',
|
|
188
|
-
trackId: 'frame-1',
|
|
189
|
-
name: 'frame-1',
|
|
190
|
-
data: {
|
|
191
|
-
kind: 'points',
|
|
192
|
-
points: [
|
|
193
|
-
{ x: 0, y: 3 },
|
|
194
|
-
{ x: 1, y: 4 },
|
|
195
|
-
],
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
id: 'later-b',
|
|
200
|
-
trackId: 'later-b',
|
|
201
|
-
name: 'later-b',
|
|
202
|
-
data: {
|
|
203
|
-
kind: 'points',
|
|
204
|
-
points: [
|
|
205
|
-
{ x: 0, y: 5 },
|
|
206
|
-
{ x: 1, y: 6 },
|
|
207
|
-
],
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
},
|
|
212
|
-
{ grid: { rowCount: 2, columnCount: 2, trackOrder: ['frame-2', 'frame-1'] } },
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
expect(
|
|
216
|
-
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id')),
|
|
217
|
-
).toEqual(['frame-2', 'frame-1', 'later-a', 'later-b'])
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
it.each(['independent', 'separated', 'compact'] as const)(
|
|
221
|
-
'reclaims merged empty slots in %s mode when incomplete rows fill',
|
|
222
|
-
async (displayMode) => {
|
|
223
|
-
const wrapper = await mountSizedChart(mergedFourTrackSeries(), {
|
|
224
|
-
displayMode,
|
|
225
|
-
grid: {
|
|
226
|
-
rowCount: 4,
|
|
227
|
-
columnCount: 1,
|
|
228
|
-
fillIncompleteLastRow: true,
|
|
229
|
-
trackOrder: ['frame-1', 'frame-2', 'frame-3', 'frame-4'],
|
|
230
|
-
},
|
|
231
|
-
})
|
|
232
|
-
const tracks = wrapper.findAll('.waveform-chart__track')
|
|
233
|
-
const heights = tracks.map((track) => Number(track.attributes('data-track-height')))
|
|
234
|
-
|
|
235
|
-
expect(tracks.map((track) => track.attributes('data-track-id'))).toEqual([
|
|
236
|
-
'frame-1',
|
|
237
|
-
'frame-3',
|
|
238
|
-
'frame-4',
|
|
239
|
-
])
|
|
240
|
-
expect(wrapper.findAll('.waveform-chart__track--empty')).toHaveLength(0)
|
|
241
|
-
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(4)
|
|
242
|
-
expect(heights[0]).toBeCloseTo(heights[1] ?? Number.NaN)
|
|
243
|
-
expect(heights[1]).toBeCloseTo(heights[2] ?? Number.NaN)
|
|
244
|
-
expect(wrapper.findAll('.waveform-chart__legend')).toHaveLength(1)
|
|
245
|
-
expect(wrapper.get('.waveform-chart__legend').text()).not.toContain('frame-2')
|
|
246
|
-
},
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
it('reclaims only the current page without pulling tracks from the next page', async () => {
|
|
250
|
-
const wrapper = await mountSizedChart(
|
|
251
|
-
orderedSeries(['frame-1', 'frame-3', 'frame-4', 'frame-5', 'frame-6', 'frame-7', 'frame-8']),
|
|
252
|
-
{
|
|
253
|
-
grid: {
|
|
254
|
-
rowCount: 4,
|
|
255
|
-
columnCount: 1,
|
|
256
|
-
fillIncompleteLastRow: true,
|
|
257
|
-
trackOrder: [
|
|
258
|
-
'frame-1',
|
|
259
|
-
'frame-2',
|
|
260
|
-
'frame-3',
|
|
261
|
-
'frame-4',
|
|
262
|
-
'frame-5',
|
|
263
|
-
'frame-6',
|
|
264
|
-
'frame-7',
|
|
265
|
-
'frame-8',
|
|
266
|
-
],
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
)
|
|
270
|
-
const trackIds = () =>
|
|
271
|
-
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id'))
|
|
272
|
-
|
|
273
|
-
expect(trackIds()).toEqual(['frame-1', 'frame-3', 'frame-4'])
|
|
274
|
-
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
275
|
-
expect(trackIds()).toEqual(['frame-5', 'frame-6', 'frame-7', 'frame-8'])
|
|
276
|
-
expect(wrapper.emitted('page-change')?.at(-1)).toEqual([2, 2])
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('retains ordered empty slots when incomplete rows do not fill', async () => {
|
|
280
|
-
const wrapper = await mountSizedChart(orderedSeries(['frame-1', 'frame-3', 'frame-4']), {
|
|
281
|
-
grid: {
|
|
282
|
-
rowCount: 4,
|
|
283
|
-
columnCount: 1,
|
|
284
|
-
fillIncompleteLastRow: false,
|
|
285
|
-
trackOrder: ['frame-1', 'frame-2', 'frame-3', 'frame-4'],
|
|
286
|
-
},
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
expect(
|
|
290
|
-
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id')),
|
|
291
|
-
).toEqual(['frame-1', 'frame-2', 'frame-3', 'frame-4'])
|
|
292
|
-
expect(wrapper.findAll('.waveform-chart__track--empty')).toHaveLength(1)
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('keeps an entirely empty page addressable when incomplete rows fill', async () => {
|
|
296
|
-
const wrapper = await mountSizedChart(
|
|
297
|
-
orderedSeries(['frame-1', 'frame-2', 'frame-3', 'frame-4']),
|
|
298
|
-
{
|
|
299
|
-
grid: {
|
|
300
|
-
rowCount: 4,
|
|
301
|
-
columnCount: 1,
|
|
302
|
-
fillIncompleteLastRow: true,
|
|
303
|
-
trackOrder: [
|
|
304
|
-
'frame-1',
|
|
305
|
-
'frame-2',
|
|
306
|
-
'frame-3',
|
|
307
|
-
'frame-4',
|
|
308
|
-
'frame-5',
|
|
309
|
-
'frame-6',
|
|
310
|
-
'frame-7',
|
|
311
|
-
'frame-8',
|
|
312
|
-
],
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
318
|
-
|
|
319
|
-
expect(wrapper.findAll('.waveform-chart__track--empty')).toHaveLength(4)
|
|
320
|
-
expect(wrapper.emitted('page-change')?.at(-1)).toEqual([2, 2])
|
|
321
|
-
expect(wrapper.get('.ant-pagination-item-active').text()).toBe('2')
|
|
322
|
-
})
|
|
323
|
-
})
|
|
@@ -1,365 +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 WaveformData } from '../waveform'
|
|
6
|
-
|
|
7
|
-
import { gridSeries, mountSizedChart } from '../../test/waveformChart'
|
|
8
|
-
|
|
9
|
-
describe('WaveformChart', () => {
|
|
10
|
-
it('keeps independent multi-column zoom inside the active track domain', async () => {
|
|
11
|
-
const wrapper = await mountSizedChart(gridSeries(2), {
|
|
12
|
-
displayMode: 'independent',
|
|
13
|
-
grid: { rowCount: 1, columnCount: 2 },
|
|
14
|
-
})
|
|
15
|
-
const overlays = wrapper.findAll('.waveform-chart__overlay--independent')
|
|
16
|
-
const endpoints = () =>
|
|
17
|
-
wrapper.findAll('.waveform-chart__axis-endpoint--end').map((item) => item.text())
|
|
18
|
-
const initialEndpoints = endpoints()
|
|
19
|
-
const firstWidth = Number(overlays[0].attributes('width'))
|
|
20
|
-
Object.defineProperty(overlays[0].element, 'getBoundingClientRect', {
|
|
21
|
-
value: () => ({ left: 0, top: 0, width: firstWidth, height: 260 }),
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
overlays[0].element.dispatchEvent(
|
|
25
|
-
new WheelEvent('wheel', {
|
|
26
|
-
deltaY: -4_000,
|
|
27
|
-
clientX: firstWidth - 1,
|
|
28
|
-
clientY: 130,
|
|
29
|
-
bubbles: true,
|
|
30
|
-
cancelable: true,
|
|
31
|
-
}),
|
|
32
|
-
)
|
|
33
|
-
flushAnimationFrames()
|
|
34
|
-
await flushPromises()
|
|
35
|
-
|
|
36
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
37
|
-
expect(domain[0]).toBeGreaterThanOrEqual(0)
|
|
38
|
-
expect(domain[1]).toBeLessThanOrEqual(1)
|
|
39
|
-
expect(endpoints()[0]).not.toBe(initialEndpoints[0])
|
|
40
|
-
expect(endpoints()[1]).toBe(initialEndpoints[1])
|
|
41
|
-
|
|
42
|
-
overlays[0].element.dispatchEvent(
|
|
43
|
-
new WheelEvent('wheel', {
|
|
44
|
-
deltaY: 4_000,
|
|
45
|
-
clientX: firstWidth - 1,
|
|
46
|
-
clientY: 130,
|
|
47
|
-
bubbles: true,
|
|
48
|
-
cancelable: true,
|
|
49
|
-
}),
|
|
50
|
-
)
|
|
51
|
-
flushAnimationFrames()
|
|
52
|
-
await flushPromises()
|
|
53
|
-
|
|
54
|
-
expect(endpoints()[0]).toBe(initialEndpoints[0])
|
|
55
|
-
expect(endpoints()[1]).toBe(initialEndpoints[1])
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('emits one zoom-end payload after a shared zoom gesture completes', async () => {
|
|
59
|
-
vi.useFakeTimers()
|
|
60
|
-
try {
|
|
61
|
-
const wrapper = await mountSizedChart({
|
|
62
|
-
kind: 'points',
|
|
63
|
-
points: [
|
|
64
|
-
{ x: 0, y: 0 },
|
|
65
|
-
{ x: 2, y: 1 },
|
|
66
|
-
],
|
|
67
|
-
})
|
|
68
|
-
const overlay = wrapper.get('.waveform-chart__overlay')
|
|
69
|
-
const overlayWidth = Number(overlay.attributes('width'))
|
|
70
|
-
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
71
|
-
value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
overlay.element.dispatchEvent(
|
|
75
|
-
new WheelEvent('wheel', {
|
|
76
|
-
deltaY: -4000,
|
|
77
|
-
clientX: overlayWidth / 2,
|
|
78
|
-
clientY: 145,
|
|
79
|
-
bubbles: true,
|
|
80
|
-
cancelable: true,
|
|
81
|
-
}),
|
|
82
|
-
)
|
|
83
|
-
expect(wrapper.emitted('zoom-end')).toBeUndefined()
|
|
84
|
-
flushAnimationFrames()
|
|
85
|
-
// The wheel debounce is 200ms: it must not end early, then ends at the boundary.
|
|
86
|
-
await vi.advanceTimersByTimeAsync(199)
|
|
87
|
-
await flushPromises()
|
|
88
|
-
expect(wrapper.emitted('zoom-end')).toBeUndefined()
|
|
89
|
-
|
|
90
|
-
await vi.advanceTimersByTimeAsync(1)
|
|
91
|
-
await flushPromises()
|
|
92
|
-
|
|
93
|
-
const endEvents = wrapper.emitted('zoom-end') ?? []
|
|
94
|
-
expect(endEvents).toHaveLength(1)
|
|
95
|
-
const payload = endEvents[0]?.[0] as { start: number; end: number }
|
|
96
|
-
expect(payload.start).toBeGreaterThanOrEqual(0)
|
|
97
|
-
expect(payload.end).toBeLessThanOrEqual(2)
|
|
98
|
-
expect(payload.start).toBeLessThan(payload.end)
|
|
99
|
-
expect(payload.end - payload.start).toBeCloseTo(2 / 40)
|
|
100
|
-
} finally {
|
|
101
|
-
vi.useRealTimers()
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('zooms only the x axis and identifies the box gesture', async () => {
|
|
106
|
-
const wrapper = await mountSizedChart({
|
|
107
|
-
kind: 'points',
|
|
108
|
-
points: [
|
|
109
|
-
{ x: 0, y: 0 },
|
|
110
|
-
{ x: 1, y: 10 },
|
|
111
|
-
{ x: 2, y: 20 },
|
|
112
|
-
],
|
|
113
|
-
})
|
|
114
|
-
const overlay = wrapper.get('.waveform-chart__overlay--independent')
|
|
115
|
-
const width = Number(overlay.attributes('width'))
|
|
116
|
-
const height = Number(overlay.attributes('height'))
|
|
117
|
-
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
118
|
-
value: () => ({ left: 0, top: 0, width, height }),
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
const dispatchPointer = (type: string, clientX: number, clientY: number) => {
|
|
122
|
-
const event = new MouseEvent(type, { button: 0, clientX, clientY, bubbles: true })
|
|
123
|
-
Object.defineProperty(event, 'pointerId', { value: 7 })
|
|
124
|
-
overlay.element.dispatchEvent(event)
|
|
125
|
-
}
|
|
126
|
-
dispatchPointer('pointerdown', width * 0.25, height / 2)
|
|
127
|
-
dispatchPointer('pointermove', width * 0.75, height / 2 + 2)
|
|
128
|
-
await flushPromises()
|
|
129
|
-
expect(wrapper.find('.waveform-chart__zoom-selection').exists()).toBe(true)
|
|
130
|
-
dispatchPointer('pointerup', width * 0.75, height * 0.75)
|
|
131
|
-
await flushPromises()
|
|
132
|
-
|
|
133
|
-
const payload = wrapper.emitted('zoom-end')?.at(-1)?.[0] as
|
|
134
|
-
| {
|
|
135
|
-
start: number
|
|
136
|
-
end: number
|
|
137
|
-
yStart?: number
|
|
138
|
-
yEnd?: number
|
|
139
|
-
trackIndex: number
|
|
140
|
-
seriesIds: string[]
|
|
141
|
-
gesture: string
|
|
142
|
-
}
|
|
143
|
-
| undefined
|
|
144
|
-
expect(payload).toMatchObject({ trackIndex: 0, seriesIds: ['series-0'], gesture: 'box' })
|
|
145
|
-
expect(payload?.end).toBeGreaterThan(payload?.start ?? Number.POSITIVE_INFINITY)
|
|
146
|
-
expect(payload?.yStart).toBeDefined()
|
|
147
|
-
expect(payload?.yEnd).toBeDefined()
|
|
148
|
-
expect(wrapper.find('.waveform-chart__zoom-selection').exists()).toBe(false)
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('enables space-drag panning only when pannable is true and the pointer is inside', async () => {
|
|
152
|
-
const data: WaveformData = {
|
|
153
|
-
kind: 'points',
|
|
154
|
-
points: Array.from({ length: 5 }, (_, index) => ({ x: index, y: index })),
|
|
155
|
-
}
|
|
156
|
-
const disabled = await mountSizedChart(data)
|
|
157
|
-
const disabledOverlay = disabled.get('.waveform-chart__overlay--independent')
|
|
158
|
-
const disabledWidth = Number(disabledOverlay.attributes('width'))
|
|
159
|
-
const disabledHeight = Number(disabledOverlay.attributes('height'))
|
|
160
|
-
Object.defineProperty(disabledOverlay.element, 'getBoundingClientRect', {
|
|
161
|
-
value: () => ({ left: 0, top: 0, width: disabledWidth, height: disabledHeight }),
|
|
162
|
-
})
|
|
163
|
-
await disabled.trigger('pointerenter')
|
|
164
|
-
window.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space', cancelable: true }))
|
|
165
|
-
const disabledDown = new MouseEvent('pointerdown', {
|
|
166
|
-
button: 0,
|
|
167
|
-
clientX: disabledWidth * 0.25,
|
|
168
|
-
clientY: disabledHeight / 2,
|
|
169
|
-
bubbles: true,
|
|
170
|
-
})
|
|
171
|
-
Object.defineProperty(disabledDown, 'pointerId', { value: 31 })
|
|
172
|
-
disabledOverlay.element.dispatchEvent(disabledDown)
|
|
173
|
-
const disabledMove = new MouseEvent('pointermove', {
|
|
174
|
-
clientX: disabledWidth * 0.75,
|
|
175
|
-
clientY: disabledHeight / 2,
|
|
176
|
-
bubbles: true,
|
|
177
|
-
})
|
|
178
|
-
Object.defineProperty(disabledMove, 'pointerId', { value: 31 })
|
|
179
|
-
disabledOverlay.element.dispatchEvent(disabledMove)
|
|
180
|
-
await flushPromises()
|
|
181
|
-
expect(disabled.find('.waveform-chart__zoom-selection').exists()).toBe(true)
|
|
182
|
-
|
|
183
|
-
const enabled = await mountSizedChart(data, { pannable: true })
|
|
184
|
-
const enabledOverlay = enabled.get('.waveform-chart__overlay--independent')
|
|
185
|
-
const enabledWidth = Number(enabledOverlay.attributes('width'))
|
|
186
|
-
const enabledHeight = Number(enabledOverlay.attributes('height'))
|
|
187
|
-
Object.defineProperty(enabledOverlay.element, 'getBoundingClientRect', {
|
|
188
|
-
value: () => ({ left: 0, top: 0, width: enabledWidth, height: enabledHeight }),
|
|
189
|
-
})
|
|
190
|
-
const boxDown = new MouseEvent('pointerdown', {
|
|
191
|
-
button: 0,
|
|
192
|
-
clientX: enabledWidth * 0.25,
|
|
193
|
-
clientY: enabledHeight / 2,
|
|
194
|
-
bubbles: true,
|
|
195
|
-
})
|
|
196
|
-
Object.defineProperty(boxDown, 'pointerId', { value: 30 })
|
|
197
|
-
enabledOverlay.element.dispatchEvent(boxDown)
|
|
198
|
-
const boxMove = new MouseEvent('pointermove', {
|
|
199
|
-
clientX: enabledWidth * 0.75,
|
|
200
|
-
clientY: enabledHeight / 2,
|
|
201
|
-
bubbles: true,
|
|
202
|
-
})
|
|
203
|
-
Object.defineProperty(boxMove, 'pointerId', { value: 30 })
|
|
204
|
-
enabledOverlay.element.dispatchEvent(boxMove)
|
|
205
|
-
const boxUp = new MouseEvent('pointerup', {
|
|
206
|
-
clientX: enabledWidth * 0.75,
|
|
207
|
-
clientY: enabledHeight / 2,
|
|
208
|
-
bubbles: true,
|
|
209
|
-
})
|
|
210
|
-
Object.defineProperty(boxUp, 'pointerId', { value: 30 })
|
|
211
|
-
enabledOverlay.element.dispatchEvent(boxUp)
|
|
212
|
-
await flushPromises()
|
|
213
|
-
const startBeforePan = enabled.get('.waveform-chart__axis-endpoint--start').text()
|
|
214
|
-
|
|
215
|
-
await enabled.trigger('pointerenter')
|
|
216
|
-
const spaceDown = new KeyboardEvent('keydown', { code: 'Space', cancelable: true })
|
|
217
|
-
window.dispatchEvent(spaceDown)
|
|
218
|
-
const enabledDown = new MouseEvent('pointerdown', {
|
|
219
|
-
button: 0,
|
|
220
|
-
clientX: enabledWidth / 2,
|
|
221
|
-
clientY: enabledHeight / 2,
|
|
222
|
-
bubbles: true,
|
|
223
|
-
})
|
|
224
|
-
Object.defineProperty(enabledDown, 'pointerId', { value: 32 })
|
|
225
|
-
enabledOverlay.element.dispatchEvent(enabledDown)
|
|
226
|
-
const enabledMove = new MouseEvent('pointermove', {
|
|
227
|
-
clientX: enabledWidth / 2 + 20,
|
|
228
|
-
clientY: enabledHeight / 2,
|
|
229
|
-
bubbles: true,
|
|
230
|
-
})
|
|
231
|
-
Object.defineProperty(enabledMove, 'pointerId', { value: 32 })
|
|
232
|
-
enabledOverlay.element.dispatchEvent(enabledMove)
|
|
233
|
-
await flushPromises()
|
|
234
|
-
|
|
235
|
-
expect(spaceDown.defaultPrevented).toBe(true)
|
|
236
|
-
expect(enabled.classes()).toContain('waveform-chart--panning')
|
|
237
|
-
expect(enabled.find('.waveform-chart__zoom-selection').exists()).toBe(false)
|
|
238
|
-
expect(enabled.get('.waveform-chart__axis-endpoint--start').text()).not.toBe(startBeforePan)
|
|
239
|
-
window.dispatchEvent(new KeyboardEvent('keyup', { code: 'Space' }))
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
it('keeps a fixed Y domain through panning and viewport reset', async () => {
|
|
243
|
-
const wrapper = await mountSizedChart(
|
|
244
|
-
{
|
|
245
|
-
kind: 'points',
|
|
246
|
-
points: [
|
|
247
|
-
{ x: 0, y: 0 },
|
|
248
|
-
{ x: 1, y: 100 },
|
|
249
|
-
],
|
|
250
|
-
},
|
|
251
|
-
{ pannable: true, yDomain: [3, 97] },
|
|
252
|
-
)
|
|
253
|
-
const overlay = wrapper.get('.waveform-chart__overlay--independent')
|
|
254
|
-
const width = Number(overlay.attributes('width'))
|
|
255
|
-
const height = Number(overlay.attributes('height'))
|
|
256
|
-
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
257
|
-
value: () => ({ left: 0, top: 0, width, height }),
|
|
258
|
-
})
|
|
259
|
-
const yTickLabels = () =>
|
|
260
|
-
wrapper
|
|
261
|
-
.get('.waveform-chart__axis--y')
|
|
262
|
-
.findAll('.tick text')
|
|
263
|
-
.map((tick) => tick.text())
|
|
264
|
-
const initialLabels = yTickLabels()
|
|
265
|
-
|
|
266
|
-
await wrapper.trigger('pointerenter')
|
|
267
|
-
window.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space', cancelable: true }))
|
|
268
|
-
const down = new MouseEvent('pointerdown', {
|
|
269
|
-
button: 0,
|
|
270
|
-
clientX: width / 2,
|
|
271
|
-
clientY: height / 2,
|
|
272
|
-
bubbles: true,
|
|
273
|
-
})
|
|
274
|
-
Object.defineProperty(down, 'pointerId', { value: 34 })
|
|
275
|
-
overlay.element.dispatchEvent(down)
|
|
276
|
-
const move = new MouseEvent('pointermove', {
|
|
277
|
-
clientX: width / 2 + 20,
|
|
278
|
-
clientY: height / 2 + 40,
|
|
279
|
-
bubbles: true,
|
|
280
|
-
})
|
|
281
|
-
Object.defineProperty(move, 'pointerId', { value: 34 })
|
|
282
|
-
overlay.element.dispatchEvent(move)
|
|
283
|
-
await flushPromises()
|
|
284
|
-
|
|
285
|
-
expect(yTickLabels()).toEqual(initialLabels)
|
|
286
|
-
;(wrapper.vm as unknown as { resetViewport: () => void }).resetViewport()
|
|
287
|
-
await flushPromises()
|
|
288
|
-
expect(yTickLabels()).toEqual(initialLabels)
|
|
289
|
-
window.dispatchEvent(new KeyboardEvent('keyup', { code: 'Space' }))
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
it('does not activate pannable on a chart that the pointer is outside', async () => {
|
|
293
|
-
const data: WaveformData = {
|
|
294
|
-
kind: 'points',
|
|
295
|
-
points: [
|
|
296
|
-
{ x: 0, y: 0 },
|
|
297
|
-
{ x: 1, y: 1 },
|
|
298
|
-
],
|
|
299
|
-
}
|
|
300
|
-
const active = await mountSizedChart(data, { pannable: true })
|
|
301
|
-
const inactive = await mountSizedChart(data, { pannable: true })
|
|
302
|
-
await active.trigger('pointerenter')
|
|
303
|
-
|
|
304
|
-
window.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space', cancelable: true }))
|
|
305
|
-
|
|
306
|
-
const inactiveOverlay = inactive.get('.waveform-chart__overlay--independent')
|
|
307
|
-
const width = Number(inactiveOverlay.attributes('width'))
|
|
308
|
-
const height = Number(inactiveOverlay.attributes('height'))
|
|
309
|
-
Object.defineProperty(inactiveOverlay.element, 'getBoundingClientRect', {
|
|
310
|
-
value: () => ({ left: 0, top: 0, width, height }),
|
|
311
|
-
})
|
|
312
|
-
const down = new MouseEvent('pointerdown', {
|
|
313
|
-
button: 0,
|
|
314
|
-
clientX: width * 0.25,
|
|
315
|
-
clientY: height / 2,
|
|
316
|
-
bubbles: true,
|
|
317
|
-
})
|
|
318
|
-
Object.defineProperty(down, 'pointerId', { value: 33 })
|
|
319
|
-
inactiveOverlay.element.dispatchEvent(down)
|
|
320
|
-
const move = new MouseEvent('pointermove', {
|
|
321
|
-
clientX: width * 0.75,
|
|
322
|
-
clientY: height / 2,
|
|
323
|
-
bubbles: true,
|
|
324
|
-
})
|
|
325
|
-
Object.defineProperty(move, 'pointerId', { value: 33 })
|
|
326
|
-
inactiveOverlay.element.dispatchEvent(move)
|
|
327
|
-
await flushPromises()
|
|
328
|
-
|
|
329
|
-
expect(inactive.find('.waveform-chart__zoom-selection').exists()).toBe(true)
|
|
330
|
-
expect(inactive.classes()).not.toContain('waveform-chart--panning')
|
|
331
|
-
window.dispatchEvent(new KeyboardEvent('keyup', { code: 'Space' }))
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
it('limits box zoom to the configured minimum x span', async () => {
|
|
335
|
-
const wrapper = await mountSizedChart(
|
|
336
|
-
{
|
|
337
|
-
kind: 'points',
|
|
338
|
-
points: [
|
|
339
|
-
{ x: 0, y: 0 },
|
|
340
|
-
{ x: 1, y: 10 },
|
|
341
|
-
{ x: 2, y: 20 },
|
|
342
|
-
],
|
|
343
|
-
},
|
|
344
|
-
{ minZoomSpan: 0.25 },
|
|
345
|
-
)
|
|
346
|
-
const overlay = wrapper.get('.waveform-chart__overlay--independent')
|
|
347
|
-
const width = Number(overlay.attributes('width'))
|
|
348
|
-
const height = Number(overlay.attributes('height'))
|
|
349
|
-
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
350
|
-
value: () => ({ left: 0, top: 0, width, height }),
|
|
351
|
-
})
|
|
352
|
-
const dispatchPointer = (type: string, clientX: number, clientY: number) => {
|
|
353
|
-
const event = new MouseEvent(type, { button: 0, clientX, clientY, bubbles: true })
|
|
354
|
-
Object.defineProperty(event, 'pointerId', { value: 8 })
|
|
355
|
-
overlay.element.dispatchEvent(event)
|
|
356
|
-
}
|
|
357
|
-
dispatchPointer('pointerdown', width / 2, height / 2)
|
|
358
|
-
dispatchPointer('pointermove', width / 2 + 8, height / 2 + 8)
|
|
359
|
-
dispatchPointer('pointerup', width / 2 + 8, height / 2 + 8)
|
|
360
|
-
await flushPromises()
|
|
361
|
-
|
|
362
|
-
const payload = wrapper.emitted('zoom-end')?.at(-1)?.[0] as { start: number; end: number }
|
|
363
|
-
expect(payload.end - payload.start).toBeGreaterThanOrEqual(0.25 - 1e-8)
|
|
364
|
-
})
|
|
365
|
-
})
|