waveform-analysis 0.1.63 → 0.1.65
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 +21 -5
- package/dist/index.cjs +1 -1
- package/dist/index.js +986 -964
- package/dist/style.css +1 -1
- package/dist/types/components/core/constants.d.ts +1 -1
- package/dist/types/components/core/trackLayoutBuilder.d.ts +1 -0
- package/dist/types/components/core/yAxisConstants.d.ts +1 -1
- package/dist/types/types/chart.d.ts +6 -1
- package/package.json +1 -1
- package/src/App.vue +2 -2
- package/src/components/WaveformChart.css +4 -0
- package/src/components/WaveformChart.vue +2 -2
- package/src/components/core/constants.ts +3 -3
- package/src/components/core/layout.ts +1 -1
- package/src/components/core/trackLayoutBuilder.ts +42 -1
- package/src/components/core/useWaveformLayout.ts +2 -1
- package/src/components/core/useWaveformPresentation.ts +1 -1
- package/src/components/core/xDomain.ts +26 -2
- package/src/components/core/yAxisConstants.ts +1 -1
- package/src/components/rendering/WaveformLegend.vue +1 -1
- package/src/components/rendering/WaveformSeriesLayer.vue +1 -1
- package/src/components/rendering/WaveformTrack.css +5 -2
- package/src/components/rendering/WaveformTrack.vue +7 -1
- package/src/components/rendering/WaveformTrackAxes.vue +8 -2
- package/src/demo/DemoChartHost.vue +1 -0
- package/src/types/chart.ts +6 -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,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
|
-
})
|