import { flushPromises } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import { type WaveformData } from '../waveform' import { gridSeries, mountSizedChart } from '../../test/waveformChart' describe('WaveformChart', () => { it('keeps clip path ids unique across chart instances', async () => { const data: WaveformData = { kind: 'series', series: [ { id: 'channel-1', name: '通道 1', data: { kind: 'points', points: [ { x: 0, y: 0 }, { x: 1, y: 1 }, ], }, }, ], } const first = await mountSizedChart(data) const second = await mountSizedChart(data) const firstClipPathId = first.get('clipPath').attributes('id') const secondClipPathId = second.get('clipPath').attributes('id') expect(firstClipPathId).toBeTruthy() expect(secondClipPathId).toBeTruthy() expect(firstClipPathId).not.toBe(secondClipPathId) expect(first.get('[clip-path]').attributes('clip-path')).toContain(firstClipPathId) expect(second.get('[clip-path]').attributes('clip-path')).toContain(secondClipPathId) first.unmount() second.unmount() }) it('does not reuse Y scales between chart instances with the same default series ID', async () => { const first = await mountSizedChart({ kind: 'points', points: [ { x: 0, y: 0 }, { x: 1, y: 1 }, ], }) const second = await mountSizedChart({ kind: 'points', points: [ { x: 0, y: 10_000 }, { x: 1, y: 20_000 }, ], }) expect(first.get('.waveform-chart__axis--y').text()).not.toContain('E') expect(second.get('.waveform-chart__axis--y').text()).toContain('E+04 ') expect(second.find('.waveform-chart__axis-exponent--y').exists()).toBe(false) }) it('renders a configurable zero line only when the Y domain contains zero', async () => { const wrapper = await mountSizedChart( { kind: 'points', points: [ { x: 0, y: -2 }, { x: 1, y: 4 }, ], }, { zeroLine: { visible: true, color: '#475467', width: 2, dash: '3 2' } }, ) const zeroLine = wrapper.get('.waveform-chart__zero-line') expect(zeroLine.attributes()).toMatchObject({ stroke: '#475467', 'stroke-width': '2', 'stroke-dasharray': '3 2', 'data-y-axis-index': '0', }) expect(zeroLine.attributes('y1')).toBe(zeroLine.attributes('y2')) await wrapper.setProps({ zeroLine: { visible: false } }) expect(wrapper.find('.waveform-chart__zero-line').exists()).toBe(false) const positive = await mountSizedChart( { kind: 'points', points: [ { x: 0, y: 2 }, { x: 1, y: 4 }, ], }, { zeroLine: { visible: true } }, ) expect(positive.find('.waveform-chart__zero-line').exists()).toBe(false) }) it('renders zero lines from each visible Y axis in multi-axis mode', async () => { const wrapper = await mountSizedChart( { kind: 'series', series: [ { id: 'small', trackId: 'overlay', name: 'small', data: { kind: 'points', points: [ { x: 0, y: -1 }, { x: 1, y: 3 }, ], }, }, { id: 'large', trackId: 'overlay', name: 'large', data: { kind: 'points', points: [ { x: 0, y: -10 }, { x: 1, y: 2 }, ], }, }, ], }, { overlayMode: 'multi-axis', zeroLine: { visible: true } }, ) const zeroLines = wrapper.findAll('.waveform-chart__zero-line') expect(zeroLines).toHaveLength(2) expect(zeroLines.map((line) => line.attributes('data-y-axis-index'))).toEqual(['0', '1']) expect(zeroLines[0].attributes('y1')).not.toBe(zeroLines[1].attributes('y1')) }) it('hides X and Y axis lines independently while preserving axis text and the frame', async () => { const wrapper = await mountSizedChart({ kind: 'series', series: [ { id: 'channel', name: 'Voltage', data: { kind: 'points', points: [ { x: 0, y: -1 }, { x: 10_000, y: 1 }, ], }, }, ], }) const xAxis = wrapper.get('.waveform-chart__axis--x') const yAxis = wrapper.get('.waveform-chart__axis--y') expect(xAxis.classes()).not.toContain('waveform-track__axis--line-hidden') expect(yAxis.classes()).not.toContain('waveform-track__axis--line-hidden') await wrapper.setProps({ axes: { x: { lineVisible: false } } }) await flushPromises() expect(xAxis.classes()).toContain('waveform-track__axis--line-hidden') expect(yAxis.classes()).not.toContain('waveform-track__axis--line-hidden') expect(xAxis.get('path.domain').attributes('display')).toBe('none') expect( xAxis.findAll('.tick line').every((line) => line.attributes('display') === undefined), ).toBe(true) expect(yAxis.get('path.domain').attributes('display')).toBeUndefined() await wrapper.setProps({ axes: { x: { lineVisible: false }, y: { lineVisible: false }, }, grid: { rowCount: 1, columnCount: 1, trackLines: { channel: { horizontal: false, vertical: false } }, }, frameStyle: { borderColor: '#dc2626', borderWidth: 2 }, }) await flushPromises() expect(xAxis.classes()).toContain('waveform-track__axis--line-hidden') expect(yAxis.classes()).toContain('waveform-track__axis--line-hidden') expect(yAxis.get('path.domain').attributes('display')).toBe('none') expect( yAxis.findAll('.tick line').every((line) => line.attributes('display') === undefined), ).toBe(true) expect(xAxis.findAll('text').length).toBeGreaterThan(0) expect(yAxis.findAll('text').length).toBeGreaterThan(0) expect(wrapper.findAll('.waveform-chart__axis-endpoint')).toHaveLength(2) expect(wrapper.get('.waveform-chart__y-axis-label').text()).toBe('Voltage') expect(wrapper.findAll('[data-grid-direction]')).toHaveLength(0) expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({ stroke: '#dc2626', 'stroke-width': '2', }) await wrapper.setProps({ axes: { x: { lineVisible: true }, y: { lineVisible: true } } }) await flushPromises() expect(xAxis.get('path.domain').attributes('display')).toBeUndefined() expect(yAxis.get('path.domain').attributes('display')).toBeUndefined() }) it('preserves a titled multi-axis plot and hides auxiliary layers in clean view', async () => { const data: WaveformData = { kind: 'series', series: [ { id: 'first', trackId: 'overlay', name: 'first', data: { kind: 'points', points: [ { x: 0, y: -1 }, { x: 1, y: 1 }, ], }, }, { id: 'second', trackId: 'overlay', name: 'second', data: { kind: 'points', points: [ { x: 0, y: 10_000 }, { x: 10_000, y: 20_000 }, ], }, }, { id: 'third', name: 'third', data: { kind: 'points', points: [ { x: 0, y: 2 }, { x: 10_000, y: 3 }, ], }, }, ], } const sharedProps = { grid: { rowCount: 1, columnCount: 1, showPagination: true }, overlayMode: 'multi-axis' as const, frameNumber: 1, annotations: [{ id: 'note', seriesId: 'first', x: 0.5, y: 0, text: 'hidden note' }], zeroLine: { visible: true }, title: { text: 'hidden title' }, axes: { x: { lineVisible: false }, y: { lineVisible: false } }, frameStyle: { borderColor: '#dc2626', borderWidth: 2, borderStyle: 'dashed' as const }, } const regularWrapper = await mountSizedChart(data, sharedProps) const wrapper = await mountSizedChart(data, { ...sharedProps, cleanView: true, }) const regularTrack = regularWrapper.get('.waveform-chart__track') const cleanTrack = wrapper.get('.waveform-chart__track') const geometryAttributes = [ 'data-track-left', 'data-track-top', 'data-track-width', 'data-track-height', ] expect(wrapper.get('.waveform-chart').attributes('data-chart-left-margin')).toBe( regularWrapper.get('.waveform-chart').attributes('data-chart-left-margin'), ) expect(wrapper.get('.waveform-chart').attributes('data-title-area-height')).toBe( regularWrapper.get('.waveform-chart').attributes('data-title-area-height'), ) geometryAttributes.forEach((attribute) => { expect(cleanTrack.attributes(attribute)).toBe(regularTrack.attributes(attribute)) }) expect(wrapper.get('.waveform-chart').classes()).toContain('waveform-chart--clean') expect(getComputedStyle(wrapper.get('.waveform-chart').element).borderColor).toBe( 'rgba(0, 0, 0, 0)', ) expect(wrapper.findAll('.waveform-chart__series')).toHaveLength(2) expect(wrapper.find('.waveform-chart__overlay--independent').exists()).toBe(true) expect(wrapper.get('.waveform-chart__title-area').attributes('aria-hidden')).toBe('true') expect(wrapper.find('.waveform-chart__title-visual').exists()).toBe(false) const xAxis = wrapper.get('.waveform-chart__axis--x') const yAxes = wrapper.findAll('.waveform-chart__axis--y') expect(yAxes).toHaveLength(2) expect(xAxis.findAll('.tick line').length).toBeGreaterThan(0) expect(xAxis.findAll('.tick text').length).toBeGreaterThan(0) yAxes.forEach((axis) => { expect(axis.findAll('.tick line').length).toBeGreaterThan(0) expect(axis.findAll('.tick text').length).toBeGreaterThan(0) expect(axis.get('path.domain').attributes('display')).toBe('none') }) expect(xAxis.get('path.domain').attributes('display')).toBe('none') expect(wrapper.findAll('.waveform-chart__axis-endpoint')).toHaveLength(2) expect(wrapper.find('.waveform-chart__axis-exponent--x').exists()).toBe(false) expect(yAxes.some((axis) => axis.text().includes('E+04 '))).toBe(true) expect(wrapper.find('.waveform-chart__axis-exponent--y').exists()).toBe(false) expect(wrapper.find('.waveform-chart__grid').exists()).toBe(false) expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({ stroke: '#dc2626', 'stroke-width': '2', 'stroke-dasharray': '6 4', }) expect(wrapper.find('.waveform-chart__plot-background').exists()).toBe(false) expect(wrapper.find('.waveform-chart__watermark').exists()).toBe(false) expect(wrapper.find('.waveform-chart__legend-layer').exists()).toBe(false) expect(wrapper.find('.waveform-chart__label').exists()).toBe(false) expect(wrapper.find('.waveform-chart__y-axis-label').exists()).toBe(false) expect(wrapper.find('.waveform-chart__zero-line').exists()).toBe(false) expect(wrapper.find('.waveform-annotation-layer').exists()).toBe(false) expect(wrapper.find('.ant-pagination').exists()).toBe(false) }) it('preserves every track geometry in a multi-column clean view', async () => { const props = { displayMode: 'independent' as const, grid: { rowCount: 2, columnCount: 2 }, } const regularWrapper = await mountSizedChart(gridSeries(4), props) const cleanWrapper = await mountSizedChart(gridSeries(4), { ...props, cleanView: true }) const geometryAttributes = [ 'data-track-left', 'data-track-top', 'data-track-width', 'data-track-height', ] const regularTracks = regularWrapper.findAll('.waveform-chart__track') const cleanTracks = cleanWrapper.findAll('.waveform-chart__track') expect(cleanTracks).toHaveLength(regularTracks.length) cleanTracks.forEach((track, index) => { geometryAttributes.forEach((attribute) => { expect(track.attributes(attribute)).toBe(regularTracks[index].attributes(attribute)) }) }) }) })