284 lines
9.7 KiB
TypeScript
284 lines
9.7 KiB
TypeScript
import { flushPromises } from '@vue/test-utils'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import { flushAnimationFrames } from '../../test/setup'
|
|
import { type WaveformData } from '../waveform'
|
|
|
|
import { mountSizedChart } from '../../test/waveformChart'
|
|
|
|
describe('WaveformChart', () => {
|
|
it('places start, middle, and end step transitions at the expected X positions', async () => {
|
|
const lineTypes = ['step-start', 'step-middle', 'step-end', 'step-after'] as const
|
|
const wrapper = await mountSizedChart({
|
|
kind: 'series',
|
|
series: lineTypes.map((lineType) => ({
|
|
id: lineType,
|
|
trackId: 'steps',
|
|
name: lineType,
|
|
lineType,
|
|
data: {
|
|
kind: 'points',
|
|
points: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 2, y: 10 },
|
|
],
|
|
},
|
|
})),
|
|
})
|
|
const pathCoordinates = (lineType: (typeof lineTypes)[number]) =>
|
|
Array.from(
|
|
(
|
|
wrapper.get(`.waveform-chart__line[data-series-id="${lineType}"]`).attributes('d') ?? ''
|
|
).matchAll(/[ML]([\d.-]+),([\d.-]+)/g),
|
|
(match) => ({ x: Number(match[1]), y: Number(match[2]) }),
|
|
)
|
|
|
|
const start = pathCoordinates('step-start')
|
|
const middle = pathCoordinates('step-middle')
|
|
const end = pathCoordinates('step-end')
|
|
const after = pathCoordinates('step-after')
|
|
expect(start.map((point) => point.x)).toEqual([start[0]?.x, start[0]?.x, start.at(-1)?.x])
|
|
expect(middle[1]?.x).toBe(middle[2]?.x)
|
|
expect(middle[1]?.x).toBe((middle[0]!.x + middle.at(-1)!.x) / 2)
|
|
expect(end.map((point) => point.x)).toEqual([end[0]?.x, end.at(-1)?.x, end.at(-1)?.x])
|
|
expect(after).toEqual(end)
|
|
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('renders per-series lines, point symbols, error bars, and matching legend swatches', async () => {
|
|
const wrapper = await mountSizedChart({
|
|
kind: 'series',
|
|
series: [
|
|
{
|
|
id: 'triangle-errors',
|
|
trackId: 'styled-track',
|
|
name: '三角误差',
|
|
lineType: 'none',
|
|
pointType: 'triangle',
|
|
errorBar: { visible: true },
|
|
data: {
|
|
kind: 'points',
|
|
points: [
|
|
{ x: 0, y: 1, error: 0.25 },
|
|
{ x: 1, y: 2, error: 0.5 },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
id: 'line-only',
|
|
trackId: 'styled-track',
|
|
name: '纯线',
|
|
lineType: 'linear',
|
|
lineStyle: 'dashed',
|
|
pointType: 'none',
|
|
data: {
|
|
kind: 'points',
|
|
points: [
|
|
{ x: 0, y: 2 },
|
|
{ x: 1, y: 3 },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
id: 'step-errors',
|
|
trackId: 'styled-track',
|
|
name: '阶梯误差',
|
|
lineType: 'step-after',
|
|
lineStyle: 'dash-dot',
|
|
pointType: 'circle',
|
|
errorBar: { visible: true, color: '#222222', width: 2, capWidth: 10 },
|
|
data: {
|
|
kind: 'points',
|
|
points: [
|
|
{ x: 0, y: 3, lowerError: 0.5, upperError: 1 },
|
|
{ x: 1, y: 4 },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
id: 'errors-only',
|
|
trackId: 'styled-track',
|
|
name: '纯误差棒',
|
|
lineType: 'none',
|
|
pointType: 'none',
|
|
errorBar: { visible: true },
|
|
data: {
|
|
kind: 'points',
|
|
points: [
|
|
{ x: 0, y: 4, error: 0.5 },
|
|
{ x: 1, y: 5, error: 0.5 },
|
|
],
|
|
},
|
|
},
|
|
],
|
|
})
|
|
|
|
expect(wrapper.find('.waveform-chart__line[data-series-id="triangle-errors"]').exists()).toBe(
|
|
false,
|
|
)
|
|
const stepLine = wrapper.get('.waveform-chart__line[data-series-id="step-errors"]')
|
|
expect(stepLine.attributes('data-line-type')).toBe('step-after')
|
|
expect(stepLine.attributes('data-line-style')).toBe('dash-dot')
|
|
expect(stepLine.attributes('stroke-dasharray')).toBe('8 5 1.5 5')
|
|
expect(
|
|
wrapper
|
|
.get('.waveform-chart__line[data-series-id="line-only"]')
|
|
.attributes('stroke-dasharray'),
|
|
).toBe('8 5')
|
|
expect(stepLine.attributes('d')).toMatch(/^M[\d.-]+,([\d.-]+)L[\d.-]+,\1L/)
|
|
expect(
|
|
wrapper
|
|
.get('.waveform-chart__points[data-series-id="triangle-errors"]')
|
|
.attributes('data-point-type'),
|
|
).toBe('triangle')
|
|
expect(wrapper.findAll('.waveform-chart__point')).toHaveLength(2)
|
|
expect(
|
|
wrapper
|
|
.get('.waveform-chart__points[data-series-id="triangle-errors"] .waveform-chart__point')
|
|
.attributes('d'),
|
|
).toMatch(/M.*M/)
|
|
expect(wrapper.findAll('.waveform-chart__error-bar')).toHaveLength(3)
|
|
expect(
|
|
wrapper
|
|
.get('.waveform-chart__error-bars[data-series-id="step-errors"] .waveform-chart__error-bar')
|
|
.attributes('stroke'),
|
|
).toBe('#222222')
|
|
const errorsOnlySeries = wrapper.get('.waveform-chart__series[data-series-id="errors-only"]')
|
|
expect(errorsOnlySeries.find('.waveform-chart__line').exists()).toBe(false)
|
|
expect(errorsOnlySeries.find('.waveform-chart__point').exists()).toBe(false)
|
|
expect(errorsOnlySeries.find('.waveform-chart__error-bar').exists()).toBe(true)
|
|
|
|
const swatches = wrapper.findAll('.waveform-legend__swatch')
|
|
expect(swatches.map((swatch) => swatch.attributes('data-line-type'))).toEqual([
|
|
'none',
|
|
'linear',
|
|
'step-after',
|
|
'none',
|
|
])
|
|
expect(swatches[0]?.attributes('data-error-bar-visible')).toBe('true')
|
|
expect(swatches.map((swatch) => swatch.attributes('data-line-style'))).toEqual([
|
|
'solid',
|
|
'dashed',
|
|
'dash-dot',
|
|
'solid',
|
|
])
|
|
expect(swatches[1]?.get('.waveform-legend__line').attributes('stroke-dasharray')).toBe('8 5')
|
|
expect(swatches[2]?.get('.waveform-legend__line').attributes('stroke-dasharray')).toBe(
|
|
'8 5 1.5 5',
|
|
)
|
|
expect(swatches[2]?.attributes('data-error-bar-visible')).toBe('true')
|
|
expect(swatches[3]?.attributes('data-error-bar-visible')).toBe('true')
|
|
expect(swatches[0]!.findAll('path').map((path) => path.classes())).toEqual([
|
|
['waveform-legend__error-bar'],
|
|
['waveform-legend__point'],
|
|
])
|
|
const stepSwatchPaths = swatches[2]!.findAll('path')
|
|
expect(stepSwatchPaths.map((path) => path.classes())).toEqual([
|
|
['waveform-legend__line'],
|
|
['waveform-legend__error-bar'],
|
|
['waveform-legend__point'],
|
|
])
|
|
expect(stepSwatchPaths[0]?.attributes()).toMatchObject({
|
|
d: 'M1 8H25',
|
|
stroke: '#389e0d',
|
|
'stroke-width': '1.5',
|
|
})
|
|
expect(stepSwatchPaths[1]?.attributes()).toMatchObject({
|
|
d: 'M8 2H18M13 2V14M8 14H18',
|
|
stroke: '#222222',
|
|
'stroke-width': '2',
|
|
'stroke-linecap': 'butt',
|
|
})
|
|
expect(stepSwatchPaths[2]?.attributes('transform')).toBe('translate(13 8)')
|
|
expect(swatches[3]!.findAll('path').map((path) => path.classes())).toEqual([
|
|
['waveform-legend__error-bar'],
|
|
])
|
|
|
|
const renderedSeriesNodes = wrapper
|
|
.findAll('.waveform-chart__line, .waveform-chart__point, .waveform-chart__error-bar')
|
|
.map((node) => node.element)
|
|
const overlay = wrapper.get('.waveform-chart__overlay--independent')
|
|
const overlayWidth = Number(overlay.attributes('width'))
|
|
const overlayHeight = Number(overlay.attributes('height'))
|
|
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
value: () => ({ left: 0, top: 0, width: overlayWidth, height: overlayHeight }),
|
|
})
|
|
overlay.element.dispatchEvent(
|
|
new MouseEvent('pointermove', {
|
|
clientX: overlayWidth / 2,
|
|
clientY: overlayHeight / 2,
|
|
bubbles: true,
|
|
}),
|
|
)
|
|
flushAnimationFrames()
|
|
await flushPromises()
|
|
expect(
|
|
wrapper
|
|
.findAll('.waveform-chart__line, .waveform-chart__point, .waveform-chart__error-bar')
|
|
.map((node) => node.element),
|
|
).toEqual(renderedSeriesNodes)
|
|
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('binds overlaid series to at most four value axes in multi-axis mode', async () => {
|
|
const data: WaveformData = {
|
|
kind: 'series',
|
|
series: Array.from({ length: 5 }, (_, index) => ({
|
|
id: `overlaid-${index}`,
|
|
trackId: 'shared-frame',
|
|
name: `叠加通道 ${index + 1}`,
|
|
data: {
|
|
kind: 'points',
|
|
points: [
|
|
{ x: 0, y: index * 100 },
|
|
{ x: 1, y: index * 100 + 10 },
|
|
],
|
|
},
|
|
})),
|
|
}
|
|
const wrapper = await mountSizedChart(data, { overlayMode: 'multi-axis' })
|
|
|
|
expect(wrapper.attributes('data-overlay-mode')).toBe('multi-axis')
|
|
expect(
|
|
wrapper
|
|
.findAll('.waveform-chart__axis--y')
|
|
.map((axis) => axis.attributes('data-y-axis-side')),
|
|
).toEqual(['left', 'left', 'right', 'right'])
|
|
expect(
|
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-y-axis-index')),
|
|
).toEqual(['0', '1', '2', '3', '3'])
|
|
expect(wrapper.findAll('.waveform-track__multi-axis-title')).toHaveLength(4)
|
|
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('does not render empty multi-axis title backgrounds', async () => {
|
|
const data: WaveformData = {
|
|
kind: 'series',
|
|
series: [
|
|
{
|
|
id: 'empty-title-a',
|
|
trackId: 'shared-frame',
|
|
name: '',
|
|
data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
|
|
},
|
|
{
|
|
id: 'empty-title-b',
|
|
trackId: 'shared-frame',
|
|
name: ' ',
|
|
data: { kind: 'samples', values: [10, 20], sampleRate: 1 },
|
|
},
|
|
],
|
|
}
|
|
const wrapper = await mountSizedChart(data, { overlayMode: 'multi-axis', yLabel: '' })
|
|
|
|
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(2)
|
|
expect(wrapper.findAll('.waveform-track__multi-axis-title')).toHaveLength(0)
|
|
expect(wrapper.findAll('.waveform-chart__y-axis-label-bg')).toHaveLength(0)
|
|
|
|
wrapper.unmount()
|
|
})
|
|
})
|