refactor(chart): enforce 400-line source limit
Some checks failed
Package component / package (push) Failing after 4m19s
Some checks failed
Package component / package (push) Failing after 4m19s
This commit is contained in:
305
src/components/waveformChartCases/frameGridAndFormatting.test.ts
Normal file
305
src/components/waveformChartCases/frameGridAndFormatting.test.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import { flushPromises } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { flushAnimationFrames } from '../../test/setup'
|
||||
import { type WaveformData } from '../waveform'
|
||||
|
||||
import { gridSeries, mountSizedChart } from '../../test/waveformChart'
|
||||
|
||||
describe('WaveformChart', () => {
|
||||
it('renders reference grid styling and an optional frame watermark', async () => {
|
||||
const wrapper = await mountSizedChart(
|
||||
{
|
||||
kind: 'points',
|
||||
points: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 1, y: 1 },
|
||||
],
|
||||
},
|
||||
{ frameNumber: 12 },
|
||||
)
|
||||
|
||||
expect(wrapper.findAll('.waveform-chart__grid--major line').length).toBeGreaterThan(0)
|
||||
expect(wrapper.findAll('.waveform-chart__grid--minor line').length).toBeGreaterThan(0)
|
||||
expect(wrapper.find('.waveform-chart__plot-frame').exists()).toBe(true)
|
||||
expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({
|
||||
fill: 'none',
|
||||
stroke: '#1f2937',
|
||||
'stroke-width': '1',
|
||||
})
|
||||
expect(
|
||||
wrapper.get('.waveform-chart__plot-frame').attributes('stroke-dasharray'),
|
||||
).toBeUndefined()
|
||||
expect(wrapper.get('.waveform-chart__plot-background').attributes('fill')).toBe('transparent')
|
||||
expect(wrapper.get('.waveform-chart__watermark').text()).toBe('12')
|
||||
expect(getComputedStyle(wrapper.get('.waveform-chart__watermark').element).userSelect).toBe(
|
||||
'none',
|
||||
)
|
||||
expect(wrapper.get('.waveform-chart__line').attributes('stroke')).toBe('#0960bd')
|
||||
})
|
||||
|
||||
it('controls horizontal and vertical grid lines independently by track ID', async () => {
|
||||
const data = gridSeries(2)
|
||||
if (data.kind === 'series') {
|
||||
data.series[0].trackId = 'frame-a'
|
||||
data.series[1].trackId = 'frame-b'
|
||||
}
|
||||
const wrapper = await mountSizedChart(data, {
|
||||
grid: {
|
||||
rowCount: 1,
|
||||
columnCount: 2,
|
||||
trackLines: {
|
||||
'frame-a': { horizontal: false },
|
||||
'frame-b': { vertical: false },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const tracks = wrapper.findAll('.waveform-chart__track')
|
||||
expect(tracks).toHaveLength(2)
|
||||
expect(tracks[0].findAll('[data-grid-direction="horizontal"]')).toHaveLength(0)
|
||||
expect(tracks[0].findAll('[data-grid-direction="vertical"]').length).not.toBe(0)
|
||||
expect(tracks[1].findAll('[data-grid-direction="vertical"]')).toHaveLength(0)
|
||||
expect(tracks[1].findAll('[data-grid-direction="horizontal"]').length).not.toBe(0)
|
||||
})
|
||||
|
||||
it('keeps per-track grid line visibility attached across pagination', async () => {
|
||||
const wrapper = await mountSizedChart(gridSeries(3), {
|
||||
grid: {
|
||||
rowCount: 1,
|
||||
columnCount: 1,
|
||||
trackLines: {
|
||||
'channel-1': { horizontal: false, vertical: false },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('[data-grid-direction]')).not.toHaveLength(0)
|
||||
await wrapper.get('.ant-pagination-next button').trigger('click')
|
||||
expect(wrapper.findAll('[data-grid-direction]')).toHaveLength(0)
|
||||
await wrapper.get('.ant-pagination-next button').trigger('click')
|
||||
expect(wrapper.findAll('[data-grid-direction]')).not.toHaveLength(0)
|
||||
})
|
||||
|
||||
it('applies one custom frame style to every non-empty track', async () => {
|
||||
const wrapper = await mountSizedChart(gridSeries(2), {
|
||||
grid: { rowCount: 2, columnCount: 1 },
|
||||
frameStyle: {
|
||||
borderColor: 'rgba(255, 0, 0, 0.7)',
|
||||
borderWidth: 2.5,
|
||||
borderStyle: 'dashed',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.2)',
|
||||
},
|
||||
})
|
||||
|
||||
const tracks = wrapper.findAll('.waveform-chart__track')
|
||||
const frames = wrapper.findAll('.waveform-chart__plot-frame')
|
||||
const backgrounds = wrapper.findAll('.waveform-chart__plot-background')
|
||||
|
||||
expect(frames).toHaveLength(2)
|
||||
expect(backgrounds).toHaveLength(2)
|
||||
frames.forEach((frame) => {
|
||||
expect(frame.attributes()).toMatchObject({
|
||||
stroke: 'rgba(255, 0, 0, 0.7)',
|
||||
'stroke-width': '2.5',
|
||||
'stroke-dasharray': '6 4',
|
||||
})
|
||||
})
|
||||
backgrounds.forEach((background) => {
|
||||
expect(background.attributes('fill')).toBe('rgba(16, 185, 129, 0.2)')
|
||||
})
|
||||
|
||||
tracks.forEach((track) => {
|
||||
const renderingLayers = track.findAll(
|
||||
'.waveform-chart__plot-background, .waveform-chart__grid',
|
||||
)
|
||||
expect(renderingLayers[0].classes()).toContain('waveform-chart__plot-background')
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to the default width for invalid frame widths', async () => {
|
||||
const wrapper = await mountSizedChart(gridSeries(1), {
|
||||
frameStyle: { borderWidth: -1 },
|
||||
})
|
||||
|
||||
expect(wrapper.get('.waveform-chart__plot-frame').attributes('stroke-width')).toBe('1')
|
||||
|
||||
await wrapper.setProps({ frameStyle: { borderWidth: Number.NaN } })
|
||||
expect(wrapper.get('.waveform-chart__plot-frame').attributes('stroke-width')).toBe('1')
|
||||
})
|
||||
|
||||
it('renders a dotted frame with rounded dots', async () => {
|
||||
const wrapper = await mountSizedChart(gridSeries(1), {
|
||||
frameStyle: { borderStyle: 'dotted' },
|
||||
})
|
||||
|
||||
expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({
|
||||
'stroke-dasharray': '1 3',
|
||||
'stroke-linecap': 'round',
|
||||
})
|
||||
})
|
||||
|
||||
it('continues minor x-grid lines beyond the final major tick to the exact endpoint', async () => {
|
||||
const wrapper = await mountSizedChart({
|
||||
kind: 'points',
|
||||
points: [
|
||||
{ x: -8, y: 0 },
|
||||
{ x: 4.9903, y: 1 },
|
||||
],
|
||||
})
|
||||
const majorGridPositions = wrapper
|
||||
.findAll('.waveform-chart__grid--major line')
|
||||
.map((line) => Number(line.attributes('x1')))
|
||||
.filter(Number.isFinite)
|
||||
const minorGridPositions = wrapper
|
||||
.findAll('.waveform-chart__grid--minor line')
|
||||
.map((line) => Number(line.attributes('x1')))
|
||||
.filter(Number.isFinite)
|
||||
const trackWidth = Number(wrapper.get('.waveform-chart__track').attributes('data-track-width'))
|
||||
|
||||
expect(Math.max(...minorGridPositions)).toBeGreaterThan(Math.max(...majorGridPositions))
|
||||
expect(Math.max(...minorGridPositions)).toBeLessThan(trackWidth)
|
||||
expect(wrapper.get('.waveform-chart__axis-endpoint--end').attributes('x')).toBe(
|
||||
String(trackWidth),
|
||||
)
|
||||
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('4.99')
|
||||
expect(wrapper.get('.waveform-chart__axis-exponent--x').text()).toBe('E+03')
|
||||
})
|
||||
|
||||
it('uses one shared scientific exponent only for large and tiny Y-axis domains', async () => {
|
||||
const cases = [
|
||||
{ values: [0, 50], exponent: null },
|
||||
{ values: [0, 254], exponent: 'E+02' },
|
||||
{ values: [0, 0.0002], exponent: 'E-04' },
|
||||
]
|
||||
|
||||
for (const { values, exponent } of cases) {
|
||||
const wrapper = await mountSizedChart({
|
||||
kind: 'points',
|
||||
points: values.map((y, x) => ({ x, y })),
|
||||
})
|
||||
const labels = wrapper
|
||||
.get('.waveform-chart__axis--y')
|
||||
.findAll('.tick text')
|
||||
.map((tick) => tick.text())
|
||||
const exponentLabel = wrapper.find('.waveform-chart__axis-exponent--y')
|
||||
|
||||
if (exponent === null) {
|
||||
expect(exponentLabel.exists()).toBe(false)
|
||||
} else {
|
||||
expect(exponentLabel.text()).toBe(exponent)
|
||||
expect(labels.every((label) => !label.startsWith('E'))).toBe(true)
|
||||
expect(labels.every((label) => /^-?\d+\.\d{2}$/.test(label))).toBe(true)
|
||||
}
|
||||
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
it('uses milliseconds by default and supports seconds with a custom label', async () => {
|
||||
const data: WaveformData = {
|
||||
kind: 'points',
|
||||
points: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 1, y: 1 },
|
||||
],
|
||||
}
|
||||
const millisecondsChart = await mountSizedChart(data)
|
||||
const millisecondTicks = millisecondsChart
|
||||
.get('.waveform-chart__axis--x')
|
||||
.findAll('.tick text')
|
||||
.map((tick) => tick.text())
|
||||
|
||||
expect(millisecondsChart.text()).toContain('时间(ms)')
|
||||
expect(millisecondTicks.length).toBeGreaterThan(0)
|
||||
expect(millisecondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
|
||||
expect(millisecondsChart.get('.waveform-chart__axis-exponent--x').text()).toBe('E+03')
|
||||
expect(millisecondsChart.find('.waveform-chart__watermark').exists()).toBe(false)
|
||||
|
||||
const secondsChart = await mountSizedChart(data, { timeUnit: 's', xLabel: 'Elapsed time' })
|
||||
expect(secondsChart.text()).toContain('Elapsed time')
|
||||
expect(secondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
|
||||
expect(secondsChart.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('pins the exact visible range values to both x-axis endpoints', async () => {
|
||||
const wrapper = await mountSizedChart({
|
||||
kind: 'samples',
|
||||
values: Array.from({ length: 2000 }, (_, index) => Math.sin(index / 10)),
|
||||
sampleRate: 1000,
|
||||
})
|
||||
const start = wrapper.get('.waveform-chart__axis-endpoint--start')
|
||||
const end = wrapper.get('.waveform-chart__axis-endpoint--end')
|
||||
const middleTickLabels = wrapper
|
||||
.get('.waveform-chart__axis--x')
|
||||
.findAll('.tick text')
|
||||
.map((tick) => tick.text())
|
||||
|
||||
expect(start.attributes('x')).toBe('0')
|
||||
expect(start.attributes('text-anchor')).toBe('start')
|
||||
expect(start.text()).toBe('0.00')
|
||||
expect(end.attributes('x')).toBe(
|
||||
wrapper.get('.waveform-chart__track').attributes('data-track-width'),
|
||||
)
|
||||
expect(end.attributes('text-anchor')).toBe('end')
|
||||
expect(end.text()).toBe('2.00')
|
||||
expect(middleTickLabels.length).toBeGreaterThan(0)
|
||||
expect(middleTickLabels).not.toContain('0.00')
|
||||
expect(wrapper.get('.waveform-chart__axis-exponent--x').text()).toBe('E+03')
|
||||
expect(wrapper.findAll('.waveform-chart__grid--major line').length).toBeGreaterThan(
|
||||
middleTickLabels.length,
|
||||
)
|
||||
const middleTick = wrapper.get('.waveform-chart__axis--x .tick text')
|
||||
const endpointGroup = wrapper.get('.waveform-chart__axis-endpoints')
|
||||
const xAxis = wrapper.get('.waveform-chart__axis--x')
|
||||
expect(start.attributes('y')).toBe(middleTick.attributes('y'))
|
||||
expect(start.attributes('dy')).toBe(middleTick.attributes('dy'))
|
||||
expect(end.attributes('y')).toBe(middleTick.attributes('y'))
|
||||
expect(end.attributes('dy')).toBe(middleTick.attributes('dy'))
|
||||
expect(endpointGroup.attributes('font-family')).toBe(xAxis.attributes('font-family'))
|
||||
expect(endpointGroup.attributes('font-size')).toBe(xAxis.attributes('font-size'))
|
||||
})
|
||||
|
||||
it('keeps zoom-change domains in source seconds', async () => {
|
||||
const wrapper = await mountSizedChart({
|
||||
kind: 'points',
|
||||
points: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 2, y: 1 },
|
||||
],
|
||||
})
|
||||
const overlay = wrapper.get('.waveform-chart__overlay')
|
||||
const overlayWidth = Number(overlay.attributes('width'))
|
||||
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
||||
value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
|
||||
})
|
||||
|
||||
const initialStart = wrapper.get('.waveform-chart__axis-endpoint--start').text()
|
||||
const initialEnd = wrapper.get('.waveform-chart__axis-endpoint--end').text()
|
||||
|
||||
overlay.element.dispatchEvent(
|
||||
new WheelEvent('wheel', {
|
||||
deltaY: -4000,
|
||||
clientX: overlayWidth / 2,
|
||||
clientY: 145,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
)
|
||||
flushAnimationFrames()
|
||||
await flushPromises()
|
||||
|
||||
const emittedDomain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as
|
||||
[number, number] | undefined
|
||||
expect(emittedDomain).toBeDefined()
|
||||
expect(emittedDomain?.[0]).toBeGreaterThanOrEqual(0)
|
||||
expect(emittedDomain?.[1]).toBeLessThanOrEqual(2)
|
||||
expect(wrapper.get('.waveform-chart__axis-endpoint--start').attributes('x')).toBe('0')
|
||||
expect(wrapper.get('.waveform-chart__axis-endpoint--end').attributes('x')).toBe(
|
||||
String(overlayWidth),
|
||||
)
|
||||
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe(initialStart)
|
||||
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).not.toBe(initialEnd)
|
||||
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toContain('.')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user