waveform-analysis 0.1.36 → 0.1.37

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 (28) hide show
  1. package/README.md +3 -3
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +902 -806
  4. package/dist/style.css +1 -1
  5. package/dist/types/components/annotation/WaveformAnnotationEditor.vue.d.ts +4 -0
  6. package/dist/types/components/annotation/useWaveformChartAnnotations.d.ts +3 -1
  7. package/dist/types/components/core/constants.d.ts +3 -1
  8. package/dist/types/components/core/useWaveformChartController.d.ts +2 -0
  9. package/package.json +1 -1
  10. package/src/App.test.ts +3 -2
  11. package/src/App.vue +1 -1
  12. package/src/components/WaveformChartView.vue +10 -1
  13. package/src/components/annotation/WaveformAnnotationEditor.css +35 -104
  14. package/src/components/annotation/WaveformAnnotationEditor.vue +87 -37
  15. package/src/components/annotation/components.test.ts +134 -20
  16. package/src/components/annotation/markup.test.ts +18 -2
  17. package/src/components/annotation/markup.ts +33 -0
  18. package/src/components/annotation/useWaveformChartAnnotations.ts +53 -12
  19. package/src/components/core/constants.test.ts +29 -0
  20. package/src/components/core/constants.ts +31 -9
  21. package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +13 -3
  22. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +40 -0
  23. package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +24 -14
  24. package/src/components/waveformChartCases/legendVisibility.test.ts +10 -2
  25. package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +1 -1
  26. package/src/components/waveformChartCases/presentationMode.test.ts +10 -2
  27. package/src/components/waveformChartCases/renderingStyles.test.ts +1 -1
  28. package/src/components/waveformChartCases/xDomainStrategy.test.ts +60 -0
@@ -1,10 +1,18 @@
1
- import { flushPromises } from '@vue/test-utils'
1
+ import { DOMWrapper, flushPromises } from '@vue/test-utils'
2
2
  import { describe, expect, it } from 'vitest'
3
3
 
4
4
  import { flushAnimationFrames, pendingAnimationFrameCount } from '../../test/setup'
5
5
 
6
6
  import { mountSizedChart } from '../../test/waveformChart'
7
7
 
8
+ function getAnnotationEditor() {
9
+ const editor = Array.from(document.body.querySelectorAll<HTMLElement>('[role="dialog"]'))
10
+ .filter((element) => element.closest('.waveform-annotation-editor'))
11
+ .at(-1)
12
+ if (!editor) throw new Error('Expected annotation editor modal to be mounted')
13
+ return new DOMWrapper(editor)
14
+ }
15
+
8
16
  describe('WaveformChart', () => {
9
17
  it('zooms only the active independent track and resets when the mode changes', async () => {
10
18
  const wrapper = await mountSizedChart({
@@ -172,9 +180,10 @@ describe('WaveformChart', () => {
172
180
  )
173
181
  await flushPromises()
174
182
 
175
- expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(true)
176
- await wrapper.get('textarea[aria-label="标注文本"]').setValue('峰值点')
177
- await wrapper.get('.waveform-annotation-editor button.is-primary').trigger('click')
183
+ const editor = getAnnotationEditor()
184
+ await editor.get('textarea[aria-label="标注文本"]').setValue('峰值点')
185
+ await editor.get('button.ant-btn-primary').trigger('click')
186
+ await flushPromises()
178
187
 
179
188
  const annotations = wrapper.emitted('update:annotations')?.at(-1)?.[0] as
180
189
  Array<{ seriesId: string; x: number; y: number; text: string }> | undefined
@@ -209,10 +218,10 @@ describe('WaveformChart', () => {
209
218
  }),
210
219
  )
211
220
  await flushPromises()
212
- expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(true)
213
- expect(wrapper.get('.waveform-annotation-editor').attributes('aria-modal')).toBe('true')
214
- expect(wrapper.find('.waveform-annotation-editor__panel').exists()).toBe(true)
215
- const textarea = wrapper.get('textarea[aria-label="标注文本"]')
221
+ const editor = getAnnotationEditor()
222
+ expect(editor.attributes('role')).toBe('dialog')
223
+ expect(editor.find('.waveform-annotation-editor__content').exists()).toBe(true)
224
+ const textarea = editor.get('textarea[aria-label="标注文本"]')
216
225
  const textareaContextMenu = new MouseEvent('contextmenu', {
217
226
  bubbles: true,
218
227
  cancelable: true,
@@ -220,7 +229,8 @@ describe('WaveformChart', () => {
220
229
  expect(textarea.element.dispatchEvent(textareaContextMenu)).toBe(true)
221
230
  expect(textareaContextMenu.defaultPrevented).toBe(false)
222
231
  await textarea.setValue('右键标注')
223
- await wrapper.get('.waveform-annotation-editor button.is-primary').trigger('click')
232
+ await editor.get('button.ant-btn-primary').trigger('click')
233
+ await flushPromises()
224
234
 
225
235
  // Annotation snaps to nearest sample point (x=1, y=5)
226
236
  expect(wrapper.emitted('update:annotations')?.at(-1)?.[0]).toMatchObject([
@@ -278,20 +288,20 @@ describe('WaveformChart', () => {
278
288
  )
279
289
  await flushPromises()
280
290
  expect(
281
- wrapper
282
- .find('select[aria-label="选择标注波形"]')
291
+ getAnnotationEditor()
292
+ .get('select[aria-label="选择标注波形"]')
283
293
  .findAll('option')
284
294
  .map((item) => item.text()),
285
295
  ).toEqual(['通道 A'])
286
296
 
287
- await wrapper.get('button[aria-label="关闭标注编辑器"]').trigger('click')
297
+ await getAnnotationEditor().get('.ant-modal-close').trigger('click')
288
298
  overlays[0].element.dispatchEvent(
289
299
  new MouseEvent('contextmenu', { clientX: 356, clientY: 230, bubbles: true }),
290
300
  )
291
301
  await flushPromises()
292
302
  expect(
293
- wrapper
294
- .find('select[aria-label="选择标注波形"]')
303
+ getAnnotationEditor()
304
+ .get('select[aria-label="选择标注波形"]')
295
305
  .findAll('option')
296
306
  .map((item) => item.text()),
297
307
  ).toEqual(['通道 B'])
@@ -7,6 +7,14 @@ import WaveformChartView from '../WaveformChartView.vue'
7
7
 
8
8
  import { gridSeries, mountSizedChart, visibilitySeries } from '../../test/waveformChart'
9
9
 
10
+ function annotationEditorExists() {
11
+ return Boolean(
12
+ Array.from(document.body.querySelectorAll<HTMLElement>('[role="dialog"]'))
13
+ .filter((element) => element.closest('.waveform-annotation-editor'))
14
+ .at(-1),
15
+ )
16
+ }
17
+
10
18
  describe('WaveformChart', () => {
11
19
  it('resolves legend positions by stable track id across pages', async () => {
12
20
  const wrapper = await mountSizedChart(
@@ -273,7 +281,7 @@ describe('WaveformChart', () => {
273
281
  )
274
282
  flushAnimationFrames()
275
283
  await flushPromises()
276
- expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(true)
284
+ expect(annotationEditorExists()).toBe(true)
277
285
 
278
286
  const controller = wrapper.getComponent(WaveformChartView).props('controller') as unknown as {
279
287
  annotationInteraction: { editorDraft: { value: { annotation: { seriesId: string } } | null } }
@@ -287,7 +295,7 @@ describe('WaveformChart', () => {
287
295
  expect(item).toBeDefined()
288
296
  await item!.trigger('click')
289
297
  await flushPromises()
290
- expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(false)
298
+ expect(annotationEditorExists()).toBe(false)
291
299
  })
292
300
 
293
301
  it('renders independent cells with separate x axes and overlays', async () => {
@@ -208,7 +208,7 @@ describe('WaveformChart', () => {
208
208
  legend
209
209
  .findAll('.waveform-legend__swatch')
210
210
  .map((swatch) => swatch.get('path').attributes('stroke')),
211
- ).toEqual(['#0960bd', '#389e0d'])
211
+ ).toEqual(['#0960bd', '#2ca02c'])
212
212
  expect(wrapper.findAll('.waveform-chart__watermark').map((item) => item.text())).toEqual([
213
213
  '1',
214
214
  '2',
@@ -5,6 +5,14 @@ import { flushAnimationFrames } from '../../test/setup'
5
5
  import { mountSizedChart } from '../../test/waveformChart'
6
6
  import type { WaveformData, WaveformDisplayMode } from '../data/types'
7
7
 
8
+ function annotationEditorExists() {
9
+ return Boolean(
10
+ Array.from(document.body.querySelectorAll<HTMLElement>('[role="dialog"]'))
11
+ .filter((element) => element.closest('.waveform-annotation-editor'))
12
+ .at(-1),
13
+ )
14
+ }
15
+
8
16
  const presentationData: WaveformData = {
9
17
  kind: 'series',
10
18
  series: [
@@ -282,13 +290,13 @@ describe('WaveformChart presentation mode', () => {
282
290
  }),
283
291
  )
284
292
  await flushPromises()
285
- expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(true)
293
+ expect(annotationEditorExists()).toBe(true)
286
294
 
287
295
  await wrapper.setProps({ presentationMode: true })
288
296
  await flushPromises()
289
297
 
290
298
  expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(false)
291
- expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(false)
299
+ expect(annotationEditorExists()).toBe(false)
292
300
  expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([null])
293
301
 
294
302
  await wrapper.setProps({ presentationMode: false })
@@ -181,7 +181,7 @@ describe('WaveformChart', () => {
181
181
  ])
182
182
  expect(stepSwatchPaths[0]?.attributes()).toMatchObject({
183
183
  d: 'M1 8H25',
184
- stroke: '#389e0d',
184
+ stroke: '#2ca02c',
185
185
  'stroke-width': '1.5',
186
186
  })
187
187
  expect(stepSwatchPaths[1]?.attributes()).toMatchObject({
@@ -93,4 +93,64 @@ describe('WaveformChart x domain strategy', () => {
93
93
  expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('10000')
94
94
  expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('15000')
95
95
  })
96
+
97
+ it('keeps an explicit viewport wider than the data and leaves uncovered time blank', async () => {
98
+ const wrapper = await mountSizedChart(
99
+ {
100
+ kind: 'points',
101
+ points: [
102
+ { x: 0, y: 0 },
103
+ { x: 10, y: 1 },
104
+ ],
105
+ },
106
+ { timeUnit: 's', initialXDomain: [-5, 20] },
107
+ )
108
+
109
+ const track = wrapper.get('.waveform-chart__track')
110
+ const width = Number(track.attributes('data-track-width'))
111
+ const path = track.get('.waveform-chart__line').attributes('d') ?? ''
112
+ const xCoordinates = Array.from(path.matchAll(/(?:M|L)([\d.-]+)/g)).map((match) =>
113
+ Number(match[1]),
114
+ )
115
+
116
+ expect(track.get('.waveform-chart__axis-endpoint--start').text()).toBe('-5')
117
+ expect(track.get('.waveform-chart__axis-endpoint--end').text()).toBe('20')
118
+ expect(xCoordinates.length).toBeGreaterThan(0)
119
+ expect(Math.min(...xCoordinates)).toBeGreaterThanOrEqual(0)
120
+ expect(Math.max(...xCoordinates)).toBeLessThanOrEqual(width)
121
+ })
122
+
123
+ it('applies independent explicit viewports per track, including empty time regions', async () => {
124
+ const wrapper = await mountSizedChart(
125
+ {
126
+ kind: 'series',
127
+ series: [
128
+ {
129
+ id: 'first',
130
+ name: '第一轨',
131
+ data: { kind: 'points', points: [{ x: 0, y: 0 }, { x: 10, y: 1 }] },
132
+ },
133
+ {
134
+ id: 'second',
135
+ name: '第二轨',
136
+ data: { kind: 'points', points: [{ x: 100, y: 0 }, { x: 110, y: 1 }] },
137
+ },
138
+ ],
139
+ },
140
+ {
141
+ displayMode: 'independent',
142
+ grid: { rowCount: 1, columnCount: 2 },
143
+ timeUnit: 's',
144
+ initialXDomains: { first: [-5, 20], second: [95, 120] },
145
+ },
146
+ )
147
+
148
+ const tracks = wrapper.findAll('.waveform-chart__track')
149
+ expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-5')
150
+ expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('20')
151
+ expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('95')
152
+ expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('120')
153
+ expect(tracks[0]?.find('.waveform-chart__line').exists()).toBe(true)
154
+ expect(tracks[1]?.find('.waveform-chart__line').exists()).toBe(true)
155
+ })
96
156
  })