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:
325
src/components/waveformChartCases/legendVisibility.test.ts
Normal file
325
src/components/waveformChartCases/legendVisibility.test.ts
Normal file
@@ -0,0 +1,325 @@
|
||||
import { flushPromises } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { flushAnimationFrames } from '../../test/setup'
|
||||
import { type WaveformData } from '../waveform'
|
||||
import WaveformChartView from '../WaveformChartView.vue'
|
||||
|
||||
import { gridSeries, mountSizedChart, visibilitySeries } from '../../test/waveformChart'
|
||||
|
||||
describe('WaveformChart', () => {
|
||||
it('resolves legend positions by stable track id across pages', async () => {
|
||||
const wrapper = await mountSizedChart(
|
||||
{
|
||||
kind: 'series',
|
||||
series: Array.from({ length: 8 }, (_, index) => ({
|
||||
id: `series-${index}`,
|
||||
trackId: `frame-${Math.floor(index / 2)}`,
|
||||
name: `series ${index}`,
|
||||
data: {
|
||||
kind: 'points' as const,
|
||||
points: [
|
||||
{ x: 0, y: index },
|
||||
{ x: 1, y: index + 1 },
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
grid: { rowCount: 2, columnCount: 1, showPagination: true },
|
||||
legend: {
|
||||
position: 'left',
|
||||
orientation: 'auto',
|
||||
trackPositions: {
|
||||
'frame-0': 'top',
|
||||
'frame-1': 'bottom-right',
|
||||
'frame-2': 'bottom',
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const legendForTrack = (trackId: string) =>
|
||||
wrapper.get(`[data-legend-track-id="${trackId}"] .waveform-chart__legend`)
|
||||
|
||||
expect(legendForTrack('frame-0').attributes('data-position')).toBe('top')
|
||||
expect(legendForTrack('frame-0').attributes('data-orientation')).toBe('horizontal')
|
||||
expect(legendForTrack('frame-1').attributes('data-position')).toBe('bottom-right')
|
||||
expect(legendForTrack('frame-1').attributes('data-orientation')).toBe('vertical')
|
||||
|
||||
await wrapper.setProps({
|
||||
legend: {
|
||||
position: 'left',
|
||||
orientation: 'vertical',
|
||||
trackPositions: { 'frame-0': 'top', 'frame-1': 'bottom-right', 'frame-2': 'bottom' },
|
||||
},
|
||||
})
|
||||
expect(legendForTrack('frame-0').attributes('data-orientation')).toBe('vertical')
|
||||
|
||||
await wrapper.get('.ant-pagination-next button').trigger('click')
|
||||
expect(legendForTrack('frame-2').attributes('data-position')).toBe('bottom')
|
||||
expect(legendForTrack('frame-2').attributes('data-orientation')).toBe('vertical')
|
||||
expect(legendForTrack('frame-3').attributes('data-position')).toBe('left')
|
||||
expect(legendForTrack('frame-3').attributes('data-orientation')).toBe('vertical')
|
||||
})
|
||||
|
||||
it('applies a configurable alpha background to every visible legend', async () => {
|
||||
const wrapper = await mountSizedChart(
|
||||
{
|
||||
kind: 'series',
|
||||
series: Array.from({ length: 4 }, (_, index) => ({
|
||||
id: `series-${index}`,
|
||||
trackId: `frame-${Math.floor(index / 2)}`,
|
||||
name: `series ${index}`,
|
||||
data: {
|
||||
kind: 'points',
|
||||
points: [
|
||||
{ x: 0, y: index },
|
||||
{ x: 1, y: index + 1 },
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
grid: { rowCount: 2, columnCount: 1 },
|
||||
legend: { backgroundColor: 'rgba(14, 165, 233, 0.25)' },
|
||||
},
|
||||
)
|
||||
|
||||
const legendPanels = wrapper.findAll('.waveform-legend__panel')
|
||||
expect(legendPanels).toHaveLength(2)
|
||||
legendPanels.forEach((panel) => {
|
||||
expect(panel.attributes('style')).toContain('background-color: rgba(14, 165, 233, 0.25)')
|
||||
})
|
||||
|
||||
await wrapper.setProps({ legend: { backgroundColor: '' } })
|
||||
wrapper.findAll('.waveform-legend__panel').forEach((panel) => {
|
||||
expect(panel.attributes('style')).toContain('background-color: rgba(255, 255, 255, 0.7)')
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps legends display-only unless interaction is explicitly enabled', async () => {
|
||||
const wrapper = await mountSizedChart(visibilitySeries(), {
|
||||
grid: { rowCount: 1, columnCount: 1 },
|
||||
})
|
||||
|
||||
const items = wrapper.findAll('.waveform-chart__legend-item')
|
||||
expect(items).toHaveLength(2)
|
||||
expect(items.every((item) => item.attributes('disabled') !== undefined)).toBe(true)
|
||||
expect(wrapper.get('.waveform-legend__panel').classes()).not.toContain(
|
||||
'waveform-legend__panel--interactive',
|
||||
)
|
||||
expect(wrapper.emitted('update:hidden-series-ids')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('toggles series, axes, tooltips, and annotations from an interactive legend', async () => {
|
||||
const wrapper = await mountSizedChart(visibilitySeries(), {
|
||||
annotations: [{ id: 'high-note', seriesId: 'high', x: 15, y: 1500, text: '高值' }],
|
||||
grid: { rowCount: 1, columnCount: 1 },
|
||||
legend: { interactive: true },
|
||||
overlayMode: 'multi-axis',
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(2)
|
||||
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(true)
|
||||
const annotationLayer = wrapper.get('.waveform-annotation-layer').element
|
||||
const legendLayer = wrapper.get('.waveform-chart__legend-layer').element
|
||||
expect(
|
||||
annotationLayer.compareDocumentPosition(legendLayer) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||
).toBeTruthy()
|
||||
const highLegendItem = wrapper.findAll('.waveform-chart__legend-item')[1]
|
||||
expect(highLegendItem.attributes('aria-pressed')).toBe('true')
|
||||
|
||||
await highLegendItem.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(
|
||||
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
||||
).toEqual(['low'])
|
||||
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(1)
|
||||
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(false)
|
||||
expect(wrapper.findAll('.waveform-chart__legend-item')[1].classes()).toContain('is-hidden')
|
||||
expect(wrapper.findAll('.waveform-chart__legend-item')[1].attributes('aria-pressed')).toBe(
|
||||
'false',
|
||||
)
|
||||
expect(wrapper.emitted('update:hidden-series-ids')?.at(-1)).toEqual([['high']])
|
||||
expect(wrapper.emitted('series-visibility-change')?.at(-1)).toEqual([
|
||||
{ seriesId: 'high', visible: false, hiddenSeriesIds: ['high'] },
|
||||
])
|
||||
|
||||
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__tooltip-series')).toHaveLength(1)
|
||||
expect(wrapper.get('.waveform-chart__tooltip-series').text()).toContain('低量程')
|
||||
|
||||
await wrapper.findAll('.waveform-chart__legend-item')[1].trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(2)
|
||||
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(true)
|
||||
expect(wrapper.emitted('series-visibility-change')?.at(-1)).toEqual([
|
||||
{ seriesId: 'high', visible: true, hiddenSeriesIds: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('waits for controlled visibility updates and preserves unknown controlled IDs', async () => {
|
||||
const wrapper = await mountSizedChart(visibilitySeries(), {
|
||||
grid: { rowCount: 1, columnCount: 1 },
|
||||
hiddenSeriesIds: ['high', 'temporarily-absent'],
|
||||
legend: { interactive: true },
|
||||
})
|
||||
|
||||
expect(
|
||||
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
||||
).toEqual(['low'])
|
||||
await wrapper.findAll('.waveform-chart__legend-item')[0].trigger('click')
|
||||
expect(wrapper.emitted('update:hidden-series-ids')?.at(-1)).toEqual([
|
||||
['high', 'temporarily-absent', 'low'],
|
||||
])
|
||||
expect(
|
||||
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
||||
).toEqual(['low'])
|
||||
|
||||
await wrapper.setProps({ hiddenSeriesIds: ['low'] })
|
||||
await flushPromises()
|
||||
expect(
|
||||
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
||||
).toEqual(['high'])
|
||||
})
|
||||
|
||||
it('retains uncontrolled visibility by stable ID and clears removed IDs', async () => {
|
||||
const original = visibilitySeries()
|
||||
const wrapper = await mountSizedChart(original, {
|
||||
grid: { rowCount: 1, columnCount: 1 },
|
||||
legend: { interactive: true },
|
||||
})
|
||||
await wrapper.findAll('.waveform-chart__legend-item')[1].trigger('click')
|
||||
|
||||
const reversed: WaveformData = {
|
||||
kind: 'series',
|
||||
series: [...(original.kind === 'series' ? original.series : [])].reverse(),
|
||||
}
|
||||
await wrapper.setProps({ data: reversed })
|
||||
await flushPromises()
|
||||
expect(
|
||||
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
||||
).toEqual(['low'])
|
||||
|
||||
await wrapper.setProps({
|
||||
data: {
|
||||
kind: 'series',
|
||||
series: original.kind === 'series' ? [original.series[0]!] : [],
|
||||
},
|
||||
})
|
||||
await flushPromises()
|
||||
await wrapper.setProps({ data: original })
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('keeps a recoverable legend and stops chart interaction when every series is hidden', async () => {
|
||||
const wrapper = await mountSizedChart(visibilitySeries(), {
|
||||
defaultHiddenSeriesIds: ['low', 'high'],
|
||||
grid: { rowCount: 1, columnCount: 1 },
|
||||
legend: { interactive: true },
|
||||
overlayMode: 'multi-axis',
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(0)
|
||||
expect(wrapper.findAll('.waveform-chart__axis')).toHaveLength(0)
|
||||
expect(wrapper.findAll('.waveform-chart__overlay')).toHaveLength(0)
|
||||
expect(wrapper.findAll('.waveform-chart__legend-item')).toHaveLength(2)
|
||||
expect(wrapper.get('.waveform-track__no-visible-series').text()).toBe('暂无可见曲线')
|
||||
|
||||
await wrapper.findAll('.waveform-chart__legend-item')[0].trigger('click')
|
||||
await flushPromises()
|
||||
expect(
|
||||
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
||||
).toEqual(['low'])
|
||||
expect(wrapper.findAll('.waveform-chart__axis--x')).toHaveLength(1)
|
||||
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(1)
|
||||
expect(wrapper.findAll('.waveform-chart__overlay--independent')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('closes an annotation editor when its series is hidden from the legend', async () => {
|
||||
const wrapper = await mountSizedChart(visibilitySeries(), {
|
||||
grid: { rowCount: 1, columnCount: 1 },
|
||||
legend: { interactive: true },
|
||||
})
|
||||
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('contextmenu', {
|
||||
clientX: overlayWidth * 0.75,
|
||||
clientY: overlayHeight / 2,
|
||||
bubbles: true,
|
||||
}),
|
||||
)
|
||||
flushAnimationFrames()
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(true)
|
||||
|
||||
const controller = wrapper.getComponent(WaveformChartView).props('controller') as unknown as {
|
||||
annotationInteraction: { editorDraft: { value: { annotation: { seriesId: string } } | null } }
|
||||
}
|
||||
const draftSeriesId = controller.annotationInteraction.editorDraft.value?.annotation.seriesId
|
||||
const item = wrapper
|
||||
.findAll('.waveform-chart__legend-item')
|
||||
.find((legendItem) =>
|
||||
legendItem.text().includes(draftSeriesId === 'high' ? '高量程' : '低量程'),
|
||||
)
|
||||
expect(item).toBeDefined()
|
||||
await item!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.waveform-annotation-editor').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders independent cells with separate x axes and overlays', async () => {
|
||||
const wrapper = await mountSizedChart(gridSeries(4), {
|
||||
displayMode: 'independent',
|
||||
grid: { rowCount: 2, columnCount: 2 },
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.waveform-chart__track')).toHaveLength(4)
|
||||
expect(wrapper.findAll('.waveform-chart__axis--x')).toHaveLength(4)
|
||||
expect(wrapper.findAll('.waveform-chart__overlay--independent')).toHaveLength(4)
|
||||
const tracks = wrapper.findAll('.waveform-chart__track')
|
||||
const firstTop = Number(tracks[0].attributes('data-track-top'))
|
||||
const secondRowTop = Number(tracks[2].attributes('data-track-top'))
|
||||
const firstHeight = Number(tracks[0].attributes('data-track-height'))
|
||||
expect(secondRowTop).toBeGreaterThan(firstTop + firstHeight + 20)
|
||||
})
|
||||
|
||||
it('reserves horizontal clearance for Y-axis labels in multi-column grids', async () => {
|
||||
for (const displayMode of ['independent', 'separated', 'compact'] as const) {
|
||||
const wrapper = await mountSizedChart(gridSeries(4), {
|
||||
displayMode,
|
||||
grid: { rowCount: 2, columnCount: 2 },
|
||||
})
|
||||
const tracks = wrapper.findAll('.waveform-chart__track')
|
||||
const firstLeft = Number(tracks[0].attributes('data-track-left') ?? 0)
|
||||
const firstWidth = Number(tracks[0].attributes('data-track-width'))
|
||||
const secondLeft = Number(tracks[1].attributes('data-track-left'))
|
||||
const labelX = Number(tracks[1].attributes('data-y-axis-label-x'))
|
||||
|
||||
expect(secondLeft - (firstLeft + firstWidth)).toBeGreaterThanOrEqual(Math.abs(labelX) + 16)
|
||||
expect(tracks[1].find('.waveform-chart__y-axis-label-bg').exists()).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user