Files
waveform-analysis/src/components/waveformChartCases/sizingAndTitles.test.ts

312 lines
12 KiB
TypeScript
Raw Normal View History

import { flushPromises, mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import { flushAnimationFrames, resizeObservers } from '../../test/setup'
import WaveformChart from '../WaveformChart.vue'
import WaveformChartView from '../WaveformChartView.vue'
import { mountSizedChart } from '../../test/waveformChart'
describe('WaveformChart', () => {
it('uses fixed dimensions when both width and height are specified', async () => {
const wrapper = mount(WaveformChart, {
props: {
data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
width: 640,
height: 420,
},
})
expect(wrapper.attributes('style')).toContain('width: 640px')
expect(wrapper.attributes('style')).toContain('height: 420px')
expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('640')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('420')
resizeObservers.at(-1)?.resize(640, 420)
await flushPromises()
expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('640')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('420')
})
it('allows fixed width and adaptive height to work independently', async () => {
const wrapper = mount(WaveformChart, {
props: {
data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
width: 640,
},
})
expect(wrapper.attributes('style')).toContain('width: 640px')
expect(wrapper.attributes('style')).toContain('height: 100%')
resizeObservers.at(-1)?.resize(640, 500)
await flushPromises()
expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('640')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('500')
})
it('reserves a pagination band based on the chart width instead of the viewport', async () => {
const data = {
kind: 'series' as const,
series: [0, 1].map((index) => ({
id: `series-${index}`,
name: `series ${index}`,
data: { kind: 'samples' as const, values: [index, index + 1], sampleRate: 1 },
})),
}
const narrow = mount(WaveformChart, {
props: { data, width: 390, height: 360, grid: { rowCount: 1, columnCount: 1 } },
})
const wide = mount(WaveformChart, {
props: { data, width: 640, height: 360, grid: { rowCount: 1, columnCount: 1 } },
})
expect(narrow.get('.waveform-chart__svg').attributes('height')).toBe('320')
expect(wide.get('.waveform-chart__svg').attributes('height')).toBe('360')
})
it('allows adaptive width and fixed height to work independently', async () => {
const wrapper = mount(WaveformChart, {
props: {
data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
height: 420,
},
})
expect(wrapper.attributes('style')).toContain('width: 100%')
expect(wrapper.attributes('style')).toContain('height: 420px')
resizeObservers.at(-1)?.resize(900, 420)
await flushPromises()
expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('900')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('420')
})
it('fills both dimensions and responds to container size changes by default', async () => {
const wrapper = mount(WaveformChart, {
props: { data: { kind: 'samples', values: [0, 1], sampleRate: 1 } },
})
expect(wrapper.attributes('style')).toContain('width: 100%')
expect(wrapper.attributes('style')).toContain('height: 100%')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('180')
resizeObservers.at(-1)?.resize(800, 520)
await flushPromises()
expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('800')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('520')
resizeObservers.at(-1)?.resize(500, 300)
await flushPromises()
expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('500')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('300')
})
it('does not render or reserve space for missing, hidden, or blank titles', async () => {
for (const title of [undefined, { visible: false, text: '隐藏标题' }, { text: ' ' }]) {
const wrapper = await mountSizedChart(
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
title ? { title } : {},
)
expect(wrapper.find('.waveform-chart__title-area').exists()).toBe(false)
expect(wrapper.attributes('data-title-area-height')).toBe('0')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('360')
}
})
it.each(['independent', 'separated', 'compact'] as const)(
'keeps the titled empty state inside the drawing area in %s mode',
async (displayMode) => {
const wrapper = await mountSizedChart(
{ kind: 'samples', values: [1], sampleRate: -1 },
{ displayMode, title: { text: '空数据标题' } },
)
expect(wrapper.get('.waveform-chart__title-text').text()).toBe('空数据标题')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('316')
expect(wrapper.get('.waveform-chart__empty').attributes('y')).toBe('158')
},
)
it('renders one chart title with alignment and all supported text styles', async () => {
const wrapper = await mountSizedChart(
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
{
title: {
text: ' shot: #4712 ',
align: 'right',
textStyle: {
color: '#c026d3',
fontSize: 18,
fontFamily: 'Consolas',
rotation: 0,
fontWeight: 700,
fontStyle: 'italic',
textDecoration: 'underline',
letterSpacing: '2px',
},
},
},
)
const area = wrapper.get('.waveform-chart__title-area')
const visual = wrapper.get('.waveform-chart__title-visual')
const title = wrapper.get('.waveform-chart__title-text')
expect(area.attributes('role')).toBe('heading')
expect(area.attributes('style')).toContain('justify-content: flex-end')
expect(title.text()).toBe('shot: #4712')
expect(title.attributes('style')).toContain('color: rgb(192, 38, 211)')
expect(title.attributes('style')).toContain('font-size: 18px')
expect(title.attributes('style')).toContain('font-family: Consolas')
expect(title.attributes('style')).toContain('font-weight: 700')
expect(title.attributes('style')).toContain('font-style: italic')
expect(title.attributes('style')).toContain('text-decoration: underline')
expect(title.attributes('style')).toContain('letter-spacing: 2px')
expect(visual.attributes('style')).toContain('width: 752px')
expect(title.attributes('style')).toContain('width: 752px')
expect(title.attributes('style')).toContain('rotate(0deg)')
expect(wrapper.attributes('data-title-area-height')).toBe('44')
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('316')
})
it('normalizes invalid title numbers and wraps long titles at narrow widths', async () => {
const wrapper = await mountSizedChart(
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
{
title: {
text: '这是一个用于验证窄屏省略行为的很长波形分析标题',
textStyle: { fontSize: Number.NaN, rotation: Number.POSITIVE_INFINITY },
},
},
)
resizeObservers.at(-1)?.resize(160, 360)
await flushPromises()
const title = wrapper.get('.waveform-chart__title-text')
expect(title.attributes('style')).toContain('font-size: 14px')
expect(title.attributes('style')).toContain('Microsoft YaHei')
expect(title.attributes('style')).toContain('font-weight: 400')
expect(title.attributes('style')).toContain('rotate(0deg)')
expect(title.attributes('style')).toContain('white-space: normal')
expect(title.attributes('style')).toContain('overflow-wrap: anywhere')
expect(title.attributes('title')).toBeUndefined()
expect(title.attributes('data-title-wrapped')).toBe('true')
expect(Number(wrapper.attributes('data-title-area-height'))).toBeGreaterThan(44)
})
it.each([45, 90, -90, 180])(
'scales a complete long title into the rotated title area at %s degrees',
async (rotation) => {
const wrapper = await mountSizedChart(
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
{
title: {
text: '这是一个用于验证旋转缩放行为的完整波形分析标题',
textStyle: { rotation },
},
},
)
const titleHeight = Number(wrapper.attributes('data-title-area-height'))
const title = wrapper.get('.waveform-chart__title-text')
expect(titleHeight).toBeGreaterThanOrEqual(44)
expect(titleHeight).toBeLessThanOrEqual(160)
expect(Number(wrapper.get('.waveform-chart__svg').attributes('height'))).toBe(
360 - titleHeight,
)
expect(title.text()).toBe('这是一个用于验证旋转缩放行为的完整波形分析标题')
expect(title.attributes('style')).toContain(`rotate(${rotation}deg)`)
expect(title.attributes('style')).toContain('white-space: nowrap')
expect(Number(title.attributes('data-title-scale'))).toBeLessThanOrEqual(1)
expect(title.attributes('data-title-wrapped')).toBeUndefined()
},
)
it('updates fixed and adaptive drawing heights when the title changes', async () => {
const fixedWrapper = mount(WaveformChart, {
props: {
data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
height: 420,
title: { text: '固定高度标题' },
},
})
expect(fixedWrapper.get('.waveform-chart__svg').attributes('height')).toBe('376')
await fixedWrapper.setProps({ title: { visible: false, text: '固定高度标题' } })
expect(fixedWrapper.get('.waveform-chart__svg').attributes('height')).toBe('420')
const adaptiveWrapper = await mountSizedChart(
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
{ title: { text: '自适应高度标题' } },
)
expect(adaptiveWrapper.get('.waveform-chart__svg').attributes('height')).toBe('316')
resizeObservers.at(-1)?.resize(800, 500)
await flushPromises()
expect(adaptiveWrapper.get('.waveform-chart__svg').attributes('height')).toBe('456')
})
it('includes the title offset in root-relative tooltip positioning', async () => {
const wrapper = await mountSizedChart(
{
kind: 'points',
points: [
{ x: 0, y: 0 },
{ x: 1, y: 5 },
],
},
{ title: { text: 'shot: #4712' }, grid: { rowCount: 1, columnCount: 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: 246 }),
})
overlay.element.dispatchEvent(
new MouseEvent('pointermove', { clientX: overlayWidth / 2, clientY: 100, bubbles: true }),
)
flushAnimationFrames()
await flushPromises()
const tooltipTop = Number.parseFloat(
(wrapper.get('.waveform-chart__tooltip').element as HTMLElement).style.top,
)
expect(tooltipTop).toBeGreaterThanOrEqual(44)
})
it('includes the title offset in annotation editor anchors', async () => {
const wrapper = await mountSizedChart(
{
kind: 'points',
points: [
{ x: 0, y: 0 },
{ x: 1, y: 5 },
],
},
{ title: { text: 'shot: #4712' }, grid: { rowCount: 1, columnCount: 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: 246 }),
})
overlay.element.dispatchEvent(
new MouseEvent('contextmenu', {
clientX: overlayWidth / 2,
clientY: 100,
bubbles: true,
}),
)
await flushPromises()
const controller = wrapper.getComponent(WaveformChartView).props('controller') as unknown as {
annotationInteraction: { editorDraft: { value: { anchor: { y: number } } | null } }
}
expect(controller.annotationInteraction.editorDraft.value?.anchor.y).toBe(162)
})
})