vi.mock('@/shared/components/AppDialog.vue', () => ({ default: { props: ['modelValue'], template: '
' } })) import ElementPlus from 'element-plus' import { mount } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' import { defineComponent } from 'vue' vi.mock('vue-echarts', () => ({ default: defineComponent({ name: 'VChart', props: ['option', 'autoresize', 'initOptions'], template: '
' }) })) import SkuTrend from './SkuTrend.vue' import type { DailyResponse } from '../types' import { adminWorkbenchChartTheme } from '@/styles/adminWorkbenchChartTheme' function data(status = 'COMPLETE'): DailyResponse { return { context: { salesRunId: null, inventoryRunId: null, salesScopeHash: null, inventoryScopeHash: null, queryAsOf: '', queryExpiresAt: '', businessTimezone: 'Asia/Shanghai', anchorDate: '2026-10-13' }, summary: { sourceAccount: 'test', customerCode: 'OWNER', warehouseCode: 'WH', sku: 'SKU', productName: null, quantityUnit: 'PCS', outboundQuantity: '30', observedQuantity: '30', dataStatus: status, reasonCodes: [], completeDayCount: 30, partialDayCount: 0, unknownDayCount: 0 }, points: Array.from({ length: 30 }, (_, index) => ({ businessDate: `2026-09-${String(index + 1).padStart(2, '0')}`, outboundQuantity: status === 'UNKNOWN' ? null : '1', observedQuantity: status === 'UNKNOWN' ? null : '1', dataStatus: status, quantityUnit: 'PCS', reasonCodes: [] })), } } describe('SKU daily trend axes', () => { it('shows both axes, intermediate dates, zero and maximum ticks for a 30-day window', () => { const w = mount(SkuTrend, { props: { data: data() }, global: { plugins: [ElementPlus] } }) const chart = w.findComponent({ name: 'VChart' }) const option = chart.props('option') expect(option.xAxis).toMatchObject({ type: 'category', name: '日期(北京时间)' }) expect(option.xAxis.data).toHaveLength(30) expect(option.yAxis).toMatchObject({ type: 'value', min: 0, name: '出库量(件)' }) expect(option.series[1].data).toEqual(Array(30).fill(1)) expect(option.tooltip.trigger).toBe('axis') expect(option.tooltip).toMatchObject({ backgroundColor: adminWorkbenchChartTheme.colors.surface, borderColor: adminWorkbenchChartTheme.colors.border }) expect(option.series[0].itemStyle.color).toBe(adminWorkbenchChartTheme.colors.primary) expect(option.yAxis.splitLine).toEqual(adminWorkbenchChartTheme.splitLine) expect(w.get('section').classes()).toContain('admin-workbench') expect(w.find('.workbench-panel .workbench-chart-title').text()).toContain('SKU · 每日出库趋势') expect(w.find('.workbench-meta').text()).toContain('所属货主:OWNER') expect(w.findAll('.workbench-note').some(note => note.text().includes('完整出库量:30 件'))).toBe(true) expect(chart.props('initOptions')).toEqual({ renderer: 'canvas' }) expect(w.get('.sku-trend').find('svg').exists()).toBe(false) w.unmount() }) it('retains axes and date markers for an unknown window without drawing zero points', () => { const w = mount(SkuTrend, { props: { data: data('UNKNOWN') }, global: { plugins: [ElementPlus] } }) expect(w.text()).toContain('未知日期不作为零销量') const option = w.findComponent({ name: 'VChart' }).props('option') expect(option.series[0].data.every((point: { value: number | null }) => point.value === null)).toBe(true) expect(option.series).toHaveLength(1) expect(option.xAxis.data).toHaveLength(30) w.unmount() }) it('retains partial segments, real zero and the coverage evidence dialog after theming', async () => { const response = data() response.points = [ { ...response.points[0]!, outboundQuantity: '0', observedQuantity: '0' }, { ...response.points[1]!, outboundQuantity: null, observedQuantity: '3', dataStatus: 'PARTIAL', coveredUntil: '2026-09-02 12:00:00' }, { ...response.points[2]!, outboundQuantity: null, observedQuantity: null, dataStatus: 'UNKNOWN' }, { ...response.points[3]!, outboundQuantity: '8', observedQuantity: '8' }, ] const w = mount(SkuTrend, { props: { data: response }, global: { plugins: [ElementPlus] } }) const option = w.findComponent({ name: 'VChart' }).props('option') expect(option.series[0].data.map((point: { value: number | null }) => point.value)).toEqual([0, 3, null, 8]) expect(option.series[1]).toMatchObject({ data: [0, 3, null, null], lineStyle: { type: 'dashed', color: adminWorkbenchChartTheme.colors.primary } }) expect(option.series).toHaveLength(2) expect(w.find('table').exists()).toBe(false) const evidence = w.findAll('button').find(button => button.text() === '每日数量、完整性及覆盖证据')! await evidence.trigger('click') expect(w.get('table').text()).toContain('北京时间日期') expect(w.get('table').text()).toContain('2026-09-02 12:00:00') expect(w.findAll('tbody tr')).toHaveLength(4) await w.setProps({ data: data('UNKNOWN') }) expect(w.find('table').exists()).toBe(false) w.unmount() }) })