Files
waveform-analysis/src/App.test.ts

350 lines
15 KiB
TypeScript
Raw Normal View History

import { flushPromises, mount } from '@vue/test-utils'
import { InputNumber, Select } from 'ant-design-vue'
import { describe, expect, it, vi } from 'vitest'
import { ColorPicker } from 'vue3-colorpicker'
import App from './App.vue'
import { WaveformChart, type WaveformData } from './components'
describe('App workspace layout', { timeout: 20_000 }, () => {
it('restores full data and invalidates a pending zoom request', async () => {
vi.useFakeTimers()
const wrapper = mount(App)
try {
await flushPromises()
const chart = wrapper.getComponent(WaveformChart)
const pointCount = (data: WaveformData) =>
data.kind === 'series' && data.series[0]?.data.kind === 'points'
? data.series[0].data.points.length
: 0
const initialPointCount = pointCount(chart.props('data') as WaveformData)
chart.vm.$emit('zoom-end', { start: 0, end: 0.001 })
await wrapper.get('[aria-label="重置波形视图"]').trigger('click')
await vi.advanceTimersByTimeAsync(100)
await flushPromises()
expect(pointCount(chart.props('data') as WaveformData)).toBe(initialPointCount)
} finally {
wrapper.unmount()
vi.useRealTimers()
}
})
it('places controls in the sidebar beside the chart', async () => {
const wrapper = mount(App)
await flushPromises()
const panel = wrapper.get('#waveform-control-panel')
const frameControls = panel.get('.frame-style-controls')
expect(panel.find('h1').exists()).toBe(false)
expect(panel.find('[aria-label="波形展示方式"]').exists()).toBe(true)
expect(panel.find('[aria-label="波形叠加方式"]').exists()).toBe(true)
expect(panel.find('[aria-label="波形网格尺寸"]').exists()).toBe(true)
expect(panel.find('[aria-label="净图模式"]').exists()).toBe(true)
expect(panel.find('[aria-label="显示零值参考线"]').exists()).toBe(true)
const zeroLineControls = panel.get('.zero-line-controls')
expect(zeroLineControls.findAllComponents(ColorPicker)).toHaveLength(1)
expect(zeroLineControls.find('[aria-label="零值参考线线宽"]').exists()).toBe(true)
expect(zeroLineControls.find('[aria-label="零值参考线线型"]').exists()).toBe(true)
expect(frameControls.findAllComponents(ColorPicker)).toHaveLength(2)
expect(frameControls.text()).toContain('边框颜色')
expect(frameControls.text()).toContain('背景颜色')
expect(frameControls.find('[aria-label="图框线宽"]').exists()).toBe(true)
expect(frameControls.find('[aria-label="图框线型"]').exists()).toBe(true)
expect(frameControls.find('[aria-label="显示图框水印"]').exists()).toBe(true)
expect(frameControls.get('.frame-style-control--switch .ant-switch').classes()).toContain(
'ant-switch-small',
)
const titleControls = panel.get('.title-controls')
expect(panel.find('[aria-label="显示标题"]').exists()).toBe(true)
expect(titleControls.find('[aria-label="标题名称"]').exists()).toBe(true)
expect(titleControls.find('[aria-label="标题对齐方式"]').exists()).toBe(true)
expect(titleControls.find('[aria-label="标题字体"]').exists()).toBe(true)
expect(titleControls.find('[aria-label="标题字号"]').exists()).toBe(true)
expect(titleControls.find('[aria-label="标题旋转角度"]').exists()).toBe(true)
expect(titleControls.findAllComponents(ColorPicker)).toHaveLength(1)
expect(panel.find('[aria-label="图例位置"]').exists()).toBe(true)
expect(panel.find('[aria-label="图例排列"]').exists()).toBe(true)
const legendColorControl = panel.get('.legend-color-control')
const legendColorPicker = legendColorControl.getComponent(ColorPicker)
expect(legendColorControl.text()).toContain('背景')
expect(legendColorPicker.props('pureColor')).toBe('rgba(255, 255, 255, 0.7)')
expect(legendColorPicker.props('disableAlpha')).toBe(false)
expect(panel.text()).not.toContain('数据摘要')
expect(wrapper.get('.chart-panel').find('.waveform-chart').exists()).toBe(true)
wrapper.unmount()
})
it('passes clean view and zero-line controls to the chart', async () => {
const wrapper = mount(App)
await flushPromises()
const chart = wrapper.getComponent(WaveformChart)
expect(chart.props('cleanView')).toBe(false)
expect(chart.props('zeroLine')).toMatchObject({ visible: false, color: '#98a2b3', width: 1 })
await wrapper.get('[aria-label="净图模式"]').trigger('click')
await wrapper.get('[aria-label="显示零值参考线"]').trigger('click')
await flushPromises()
expect(chart.props('cleanView')).toBe(true)
expect(chart.props('zeroLine')).toMatchObject({ visible: true, color: '#98a2b3', width: 1 })
wrapper.unmount()
})
it('switches overlaid tracks between single-axis and multi-axis rendering', async () => {
const wrapper = mount(App)
await flushPromises()
const overlayControl = wrapper.get('[aria-label="波形叠加方式"]')
expect(wrapper.get('.waveform-chart').attributes('data-overlay-mode')).toBe('single-axis')
expect(overlayControl.text()).toContain('单值轴')
expect(overlayControl.text()).toContain('多值轴')
await overlayControl.findAll('input[type="radio"]')[1]?.setValue(true)
await flushPromises()
expect(wrapper.get('.waveform-chart').attributes('data-overlay-mode')).toBe('multi-axis')
expect(wrapper.findAll('.waveform-chart__axis--y').length).toBeGreaterThan(1)
wrapper.unmount()
})
it('renders the requested point-only and line-only examples in the first frame', async () => {
const wrapper = mount(App)
await flushPromises()
const firstFrame = wrapper.get('.waveform-chart__track[data-track-index="0"]')
const firstFrameSeries = firstFrame.findAll('.waveform-chart__series')
expect(firstFrameSeries.map((series) => series.attributes('data-series-name'))).toEqual([
'BT2_2M',
'TEST_CH_1',
'TEST_CH_3',
'TEST_CH_4',
'TEST_CH_5',
'纯点无线',
'纯线无点',
])
const pointsOnlySeries = firstFrame.get('.waveform-chart__series[data-series-name="纯点无线"]')
expect(pointsOnlySeries.find('.waveform-chart__line').exists()).toBe(false)
expect(pointsOnlySeries.get('.waveform-chart__points').attributes('data-point-type')).toBe(
'circle',
)
const testChannelFour = firstFrame.get('.waveform-chart__series[data-series-name="TEST_CH_4"]')
expect(testChannelFour.get('.waveform-chart__line').attributes('data-line-type')).toBe('linear')
expect(testChannelFour.get('.waveform-chart__points').attributes('data-point-type')).toBe(
'circle',
)
expect(testChannelFour.find('.waveform-chart__error-bars').exists()).toBe(false)
const lineOnlySeries = firstFrame.get('.waveform-chart__series[data-series-name="纯线无点"]')
expect(lineOnlySeries.get('.waveform-chart__line').attributes('data-line-type')).toBe('linear')
expect(lineOnlySeries.find('.waveform-chart__points').exists()).toBe(false)
const triangleSeries = firstFrame.get('.waveform-chart__series[data-series-name="BT2_2M"]')
expect(triangleSeries.find('.waveform-chart__line').exists()).toBe(false)
expect(triangleSeries.get('.waveform-chart__points').attributes('data-point-type')).toBe(
'triangle',
)
expect(triangleSeries.get('.waveform-chart__error-bar').attributes('stroke')).toBe('#0960bd')
const firstFrameLegend = wrapper.get(
'.waveform-chart__legend-track[data-legend-track-index="0"]',
)
const triangleLegendItem = firstFrameLegend
.findAll('.waveform-chart__legend-item')
.find((item) => item.text().includes('BT2_2M'))
expect(triangleLegendItem).toBeDefined()
const triangleSwatch = triangleLegendItem!.get('.waveform-legend__swatch')
expect(triangleSwatch.find('.waveform-legend__line').exists()).toBe(false)
expect(triangleSwatch.get('.waveform-legend__error-bar').attributes()).toMatchObject({
d: 'M9 2H17M13 2V14M9 14H17',
stroke: '#0960bd',
'stroke-width': '1.5',
})
expect(triangleSwatch.get('.waveform-legend__point').attributes()).toMatchObject({
fill: '#0960bd',
transform: 'translate(13 8)',
})
wrapper.unmount()
})
it('renders the three ECharts-style step modes in frame two', async () => {
const wrapper = mount(App)
await flushPromises()
const secondFrame = wrapper.get('.waveform-chart__track[data-track-index="1"]')
const series = secondFrame.findAll('.waveform-chart__series')
expect(series.map((item) => item.attributes('data-series-name'))).toEqual([
'Step Start',
'Step Middle',
'Step End',
])
expect(
secondFrame.findAll('.waveform-chart__line').map((line) => line.attributes('data-line-type')),
).toEqual(['step-start', 'step-middle', 'step-end'])
expect(secondFrame.findAll('.waveform-chart__points')).toHaveLength(3)
const secondFrameLegend = wrapper.get(
'.waveform-chart__legend-track[data-legend-track-index="1"]',
)
const legendItems = secondFrameLegend.findAll('.waveform-chart__legend-item')
expect(legendItems).toHaveLength(3)
expect(legendItems.map((item) => item.get('.waveform-legend__line').attributes('d'))).toEqual([
'M1 8H25',
'M1 8H25',
'M1 8H25',
])
expect(
legendItems.map((item) => item.get('.waveform-legend__point').attributes('fill')),
).toEqual(['#5470c6', '#91cc75', '#505372'])
expect(
legendItems.map((item) => item.get('.waveform-legend__point').attributes('transform')),
).toEqual(['translate(13 8)', 'translate(13 8)', 'translate(13 8)'])
wrapper.unmount()
})
it('updates title content, text styles, and visibility', async () => {
const wrapper = mount(App)
await flushPromises()
const renderedTitle = () => wrapper.get('.waveform-chart__title-text')
expect(renderedTitle().text()).toMatch(/^Shot:\d+$/)
expect(renderedTitle().attributes('style')).toContain('Microsoft YaHei')
expect(renderedTitle().attributes('style')).toContain('font-size: 14px')
expect(renderedTitle().attributes('style')).toContain('font-weight: 400')
await wrapper.get('input[aria-label="标题名称"]').setValue('实验标题')
await wrapper.get('[aria-label="标题粗体"]').trigger('click')
await wrapper.get('[aria-label="标题斜体"]').trigger('click')
await wrapper.get('[aria-label="标题下划线"]').trigger('click')
await flushPromises()
expect(renderedTitle().text()).toBe('实验标题')
expect(renderedTitle().attributes('style')).toContain('font-weight: 700')
expect(renderedTitle().attributes('style')).toContain('font-style: italic')
expect(renderedTitle().attributes('style')).toContain('text-decoration: underline')
await wrapper.get('[aria-label="恢复标题常规样式"]').trigger('click')
await flushPromises()
expect(renderedTitle().attributes('style')).toContain('font-weight: 400')
expect(renderedTitle().attributes('style')).toContain('font-style: normal')
expect(renderedTitle().attributes('style')).toContain('text-decoration: none')
await wrapper.get('[aria-label="显示标题"]').trigger('click')
await flushPromises()
expect(wrapper.find('.waveform-chart__title-area').exists()).toBe(false)
wrapper.unmount()
})
it('updates every visible frame from the frame style controls', async () => {
const wrapper = mount(App)
await flushPromises()
const frameControls = wrapper.get('.frame-style-controls')
const colorPickers = frameControls.findAllComponents(ColorPicker)
const widthInput = frameControls.findAllComponents(InputNumber)[0]
const styleSelect = frameControls.findAllComponents(Select)[0]
expect(colorPickers).toHaveLength(2)
expect(widthInput).toBeDefined()
expect(styleSelect).toBeDefined()
colorPickers[0].vm.$emit('update:pureColor', 'rgba(220, 38, 38, 0.8)')
colorPickers[1].vm.$emit('update:pureColor', 'rgba(14, 165, 233, 0.25)')
widthInput?.vm.$emit('update:value', 3)
styleSelect?.vm.$emit('update:value', 'dashed')
await flushPromises()
const frames = wrapper.findAll('.waveform-chart__plot-frame')
const backgrounds = wrapper.findAll('.waveform-chart__plot-background')
expect(frames.length).toBeGreaterThan(1)
expect(backgrounds).toHaveLength(frames.length)
frames.forEach((frame) => {
expect(frame.attributes()).toMatchObject({
stroke: 'rgba(220, 38, 38, 0.8)',
'stroke-width': '3',
'stroke-dasharray': '6 4',
})
})
backgrounds.forEach((background) => {
expect(background.attributes('fill')).toBe('rgba(14, 165, 233, 0.25)')
})
wrapper.unmount()
})
it('shows and hides every frame watermark from the frame style controls', async () => {
const wrapper = mount(App)
await flushPromises()
const watermarkToggle = wrapper.get('[aria-label="显示图框水印"]')
const initialWatermarks = wrapper.findAll('.waveform-chart__watermark')
const initialFrameNumbers = initialWatermarks.map((watermark) => watermark.text())
expect(initialWatermarks.length).toBeGreaterThan(1)
await watermarkToggle.trigger('click')
await flushPromises()
expect(wrapper.findAll('.waveform-chart__watermark')).toHaveLength(0)
await watermarkToggle.trigger('click')
await flushPromises()
expect(
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
).toEqual(initialFrameNumbers)
wrapper.unmount()
})
it('updates every visible legend from the alpha-enabled background picker', async () => {
const wrapper = mount(App)
await flushPromises()
const colorPicker = wrapper.get('.legend-color-control').getComponent(ColorPicker)
colorPicker.vm.$emit('update:pureColor', 'rgba(15, 118, 110, 0.35)')
await flushPromises()
const legendPanels = wrapper.findAll('.waveform-legend__panel')
expect(legendPanels.length).toBeGreaterThan(0)
legendPanels.forEach((panel) => {
expect(panel.attributes('style')).toContain('background-color: rgba(15, 118, 110, 0.35)')
})
wrapper.unmount()
})
it('opens and closes the mobile control drawer', async () => {
const wrapper = mount(App)
const toggle = wrapper.get('.mobile-control-toggle')
expect(toggle.attributes('aria-expanded')).toBe('false')
expect(wrapper.get('.control-panel').classes()).not.toContain('is-open')
expect(wrapper.find('.control-backdrop').exists()).toBe(false)
await toggle.trigger('click')
expect(toggle.attributes('aria-expanded')).toBe('true')
expect(wrapper.get('.control-panel').classes()).toContain('is-open')
expect(wrapper.find('.control-backdrop').exists()).toBe(true)
await wrapper.get('.control-backdrop').trigger('click')
expect(toggle.attributes('aria-expanded')).toBe('false')
expect(wrapper.get('.control-panel').classes()).not.toContain('is-open')
await toggle.trigger('click')
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
await flushPromises()
expect(toggle.attributes('aria-expanded')).toBe('false')
expect(wrapper.find('.control-backdrop').exists()).toBe(false)
wrapper.unmount()
})
})