120 lines
7.2 KiB
TypeScript
120 lines
7.2 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import type { GaugeSeriesOption, BarSeriesOption } from 'echarts'
|
|
import { adminWorkbenchChartTheme } from '@/styles/adminWorkbenchChartTheme'
|
|
import { barChartOption, progressGaugeOption } from './dashboardChartOptions'
|
|
|
|
describe('self-operated ECharts configurations', () => {
|
|
it('wraps a concise tooltip and escapes source text rather than inserting markup', () => {
|
|
const option = barChartOption([{ key: 'sku', label: '<img src=x>', secondary: 'A&B "\'货主', value: '12000.123456', detail: 'very long detail belongs in the dialog' }], '<件>', '热销', true)
|
|
const tooltip = option.tooltip as { formatter: (value: { dataIndex: number }) => string; extraCssText: string }
|
|
const content = tooltip.formatter({ dataIndex: 0 })
|
|
expect(content).toContain('<img src=x>')
|
|
expect(content).toContain('A&B "'货主')
|
|
expect(content).toContain('12,000.123456')
|
|
expect(content).toContain('<件>')
|
|
expect(content).not.toContain('<img')
|
|
expect(content).not.toContain('very long detail')
|
|
expect(tooltip.extraCssText).toContain('white-space: normal')
|
|
expect(option.tooltip).toMatchObject({ backgroundColor: adminWorkbenchChartTheme.colors.surface, borderColor: adminWorkbenchChartTheme.colors.border })
|
|
})
|
|
it('renders unknown progress as an empty track without a zero progress value', () => {
|
|
const unknown = (progressGaugeOption(null).series as GaugeSeriesOption[])[0]!
|
|
expect(unknown.progress?.show).toBe(false)
|
|
expect(unknown.data).toEqual([])
|
|
const known = (progressGaugeOption(12.1).series as GaugeSeriesOption[])[0]!
|
|
expect(known.progress?.show).toBe(true)
|
|
expect(known.data).toEqual([{ value: 12.1 }])
|
|
expect(known.itemStyle).toMatchObject({ color: adminWorkbenchChartTheme.colors.primary })
|
|
expect(known.axisLine?.lineStyle?.color).toEqual([[1, adminWorkbenchChartTheme.colors.primarySoft]])
|
|
const zero = (progressGaugeOption(0).series as GaugeSeriesOption[])[0]!
|
|
expect(zero.progress?.show).toBe(true)
|
|
expect(zero.data).toEqual([{ value: 0 }])
|
|
})
|
|
it('keeps indeterminate activity distinct from known completion and honors reduced motion', () => {
|
|
const matchMedia = vi.fn(() => ({ matches: true }))
|
|
vi.stubGlobal('window', { matchMedia })
|
|
try {
|
|
const running = progressGaugeOption(null, true)
|
|
expect(running.animation).toBe(false)
|
|
expect((running.series as GaugeSeriesOption[])[0]).toMatchObject({
|
|
progress: { show: true, roundCap: true },
|
|
data: [{ value: 18 }],
|
|
})
|
|
expect(progressGaugeOption(12.1).animation).toBe(false)
|
|
expect(matchMedia).toHaveBeenCalledWith('(prefers-reduced-motion: reduce)')
|
|
matchMedia.mockReturnValue({ matches: false })
|
|
expect(progressGaugeOption(12.1).animation).toBe(true)
|
|
expect(progressGaugeOption(null, true).animation).toBe(false)
|
|
} finally {
|
|
vi.unstubAllGlobals()
|
|
}
|
|
})
|
|
it('keeps the seven bucket order, zero, null and common numeric scale', () => {
|
|
const rows = Array.from({ length: 7 }, (_, index) => ({ key: `bucket-${index}`, label: `分组${index}`, value: index === 6 ? null : index, detail: '' }))
|
|
const option = barChartOption(rows, 'SKU', '库存分布')
|
|
expect(option.yAxis).toMatchObject({ inverse: true, data: rows.map(row => row.key) })
|
|
expect(option.xAxis).toMatchObject({ type: 'value', min: 0 })
|
|
expect((option.series as BarSeriesOption[])[0]?.data?.[0]).toMatchObject({ value: 0 })
|
|
expect((option.series as BarSeriesOption[])[0]?.data?.[6]).toMatchObject({ value: null })
|
|
expect((option.series as BarSeriesOption[])[0]).not.toHaveProperty('barMinHeight')
|
|
expect((option.series as BarSeriesOption[])[0]).not.toHaveProperty('showBackground')
|
|
})
|
|
it('assigns bucket colors by semantic key even when the rows are reordered', () => {
|
|
const expected: Array<[string, string]> = [
|
|
['UNKNOWN', adminWorkbenchChartTheme.colors.unknown],
|
|
['DAYS_GT_90', adminWorkbenchChartTheme.colors.teal],
|
|
['NO_AVAILABLE', adminWorkbenchChartTheme.colors.rose],
|
|
['DAYS_30_60', adminWorkbenchChartTheme.colors.primaryLight],
|
|
['NO_OUTBOUND_IN_WINDOW', adminWorkbenchChartTheme.colors.amber],
|
|
['DAYS_0_30', adminWorkbenchChartTheme.colors.primary],
|
|
['DAYS_60_90', adminWorkbenchChartTheme.colors.tealLight],
|
|
]
|
|
const rows = expected.map(([key]) => ({ key, label: key, value: 1, detail: '' }))
|
|
for (const orderedRows of [rows, [...rows].reverse()]) {
|
|
const option = barChartOption(orderedRows, 'SKU', '库存分布')
|
|
const data = (option.series as BarSeriesOption[])[0]!.data!
|
|
for (const [index, row] of orderedRows.entries()) {
|
|
expect(data[index]).toMatchObject({ itemStyle: { color: expected.find(([key]) => key === row.key)?.[1] } })
|
|
}
|
|
}
|
|
})
|
|
it('uses one blue for ranking and preserves real values instead of normalizing bar lengths', () => {
|
|
const option = barChartOption([
|
|
{ key: 'large', label: 'SKU-LARGE', value: '15000', detail: '' },
|
|
{ key: 'small', label: 'SKU-SMALL', value: '150', detail: '' },
|
|
{ key: 'zero', label: 'SKU-ZERO', value: 0, detail: '' },
|
|
{ key: 'unknown', label: 'SKU-UNKNOWN', value: null, detail: '' },
|
|
], '件', '热销', true)
|
|
const series = (option.series as BarSeriesOption[])[0]!
|
|
expect(series.data).toEqual([15000, 150, 0, null].map(value => ({
|
|
value, itemStyle: { color: adminWorkbenchChartTheme.colors.primary, borderRadius: [0, 6, 6, 0] },
|
|
})))
|
|
expect(series).not.toHaveProperty('barMinHeight')
|
|
expect(series).not.toHaveProperty('showBackground')
|
|
expect(option.xAxis).toMatchObject({ min: 0 })
|
|
const label = series.label as { formatter: (value: { dataIndex: number }) => string }
|
|
expect(label.formatter({ dataIndex: 0 })).toBe('15,000')
|
|
expect(label.formatter({ dataIndex: 2 })).toBe('0')
|
|
expect(label.formatter({ dataIndex: 3 })).toBe('—')
|
|
})
|
|
it('rounds day labels and tooltips to two decimals while retaining the full chart value', () => {
|
|
const option = barChartOption([{ key: 'sku', label: 'SKU', value: '1234.56789', detail: '' }], '天', '预计消化时间', true)
|
|
const series = (option.series as BarSeriesOption[])[0]!
|
|
expect(series.data?.[0]).toMatchObject({ value: 1234.56789 })
|
|
const label = series.label as { formatter: (value: { dataIndex: number }) => string }
|
|
const tooltip = option.tooltip as { formatter: (value: { dataIndex: number }) => string }
|
|
expect(label.formatter({ dataIndex: 0 })).toBe('1,234.57')
|
|
expect(tooltip.formatter({ dataIndex: 0 })).toContain('1,234.57 天')
|
|
})
|
|
it('retains every quantity digit in chart labels and tooltips for values beyond safe integers', () => {
|
|
const value = '12345678901234567890.123450000001'
|
|
const expected = '12,345,678,901,234,567,890.123450000001'
|
|
const option = barChartOption([{ key: 'sku', label: 'SKU', value, detail: '' }], '件', '热销', true)
|
|
const series = (option.series as BarSeriesOption[])[0]!
|
|
const label = series.label as { formatter: (value: { dataIndex: number }) => string }
|
|
const tooltip = option.tooltip as { formatter: (value: { dataIndex: number }) => string }
|
|
expect(label.formatter({ dataIndex: 0 })).toBe(expected)
|
|
expect(tooltip.formatter({ dataIndex: 0 })).toContain(`${expected} 件`)
|
|
})
|
|
})
|