Files
admin-web/src/modules/overseas-master-data/pages/FleetReportingViews.test.ts
T
2026-10-20 23:34:11 +08:00

73 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, expect, it, vi } from 'vitest'
import { shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils'
import FleetQuotesView from './FleetQuotesView.vue'
import TripOperationsView from './TripOperationsView.vue'
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
import TripOperationsCharts from '../components/TripOperationsCharts.vue'
import * as quotes from '../api/fleetQuotes'
import * as reports from '../api/tripOperations'
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: (p: string) => p.endsWith(':list') }) }))
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
vi.mock('../api/fleetQuotes', () => ({ listFleetQuotes: vi.fn(), setFleetQuoteStatus: vi.fn(), deleteFleetQuote: vi.fn(), exportFleetQuotes: vi.fn(), saveFleetCsv: vi.fn() }))
vi.mock('../api/tripOperations', () => ({ listTripOperations: vi.fn(), exportTripOperations: vi.fn() }))
const wrappers: VueWrapper[] = []
const layout = { template: '<div><slot name="metrics"/><slot name="state"/><slot/></div>' }
function mount(component: object) { const wrapper = shallowMount(component, { global: { stubs: { AdminPage: layout, ListPageLayout: layout, QueryToolbar: true,
DataTable: { name: 'DataTable', props: ['data', 'columns'], template: '<div><header><slot name="toolbar-start"/><slot name="toolbar-actions"/></header><section class="filters"><slot name="filters"/></section><slot name="notices"/></div>' }, ElButton: { template: '<button><slot/></button>' },
ElTooltip: true, LoadErrorState: true, FleetQuoteEditor: true, FleetQuoteImport: true, ApiErrorMeta: true } } }); wrappers.push(wrapper); return wrapper }
afterEach(() => { wrappers.splice(0).forEach(w => w.unmount()); vi.clearAllMocks() })
it('quotes read-only view fetches a page once and hides all write/export tools', async () => {
vi.mocked(quotes.listFleetQuotes).mockResolvedValue({ records: [], total: 0, page: 1, size: 20, pages: 0 })
const wrapper = mount(FleetQuotesView); await flushPromises()
expect(quotes.listFleetQuotes).toHaveBeenCalledTimes(1); expect(wrapper.text()).not.toContain('新增报价'); expect(wrapper.text()).not.toContain('批量导入')
expect(wrapper.findComponent({ name: 'DataTable' }).props('columns')[0]).toMatchObject({ key: 'actions', fixed: 'left' })
const tabs = wrapper.get('header .workbench-list-tabs').findAll('[role="tab"]')
expect(tabs.map(tab => tab.text())).toEqual(['车队采购报价', '外接销售报价'])
expect(wrapper.get('.filters').findComponent(QueryToolbar).exists()).toBe(true)
await tabs[1]!.trigger('click'); await flushPromises()
expect(quotes.listFleetQuotes).toHaveBeenLastCalledWith(expect.objectContaining({ kind: 'SALE', page: 1 }))
expect(tabs[1]!.attributes('aria-selected')).toBe('true')
})
it('operations uses server totals rather than displayed page size', async () => {
vi.mocked(reports.listTripOperations).mockResolvedValue({ page: { records: [], total: 21, page: 1, size: 20, pages: 2 }, summary: { trips: 21, inTransit: 12, completed: 9, physicalPallets: 40, palletSpaces: 40, ownPackages: 70, externalPackages: 10, externalAgreedAmounts: [], costs: { visible: false, unrecordedTrips: null, unallocatedTrips: null, totals: [] } }, charts: { physicalPallets: [], destinationTrips: [], externalAgreedAmounts: [], externalPalletRates: [] } })
const wrapper = mount(TripOperationsView); await flushPromises()
expect(reports.listTripOperations).toHaveBeenCalledTimes(1); expect(wrapper.text()).not.toContain('导出CSV')
expect(wrapper.find('#operations-statistics-details').exists()).toBe(false)
expect(wrapper.findComponent(TripOperationsCharts).props('detailsVisible')).toBe(false)
await wrapper.find('[aria-controls="operations-statistics-details"]').trigger('click')
expect(wrapper.text()).toContain('实际出车 21 次')
expect(wrapper.findComponent(TripOperationsCharts).props('detailsVisible')).toBe(true)
expect(reports.listTripOperations).toHaveBeenCalledTimes(1)
await wrapper.find('[aria-controls="operations-statistics-details"]').trigger('click')
expect(wrapper.find('#operations-statistics-details').exists()).toBe(false)
wrapper.findComponent(TripOperationsCharts).vm.$emit('show-details'); await flushPromises()
expect(wrapper.find('#operations-statistics-details').exists()).toBe(true)
expect(reports.listTripOperations).toHaveBeenCalledTimes(1)
expect(wrapper.findComponent(TripOperationsCharts).props('summary').physicalPallets).toBe(40)
expect(wrapper.find('.operations-overview').element.compareDocumentPosition(wrapper.findComponent({ name: 'DataTable' }).element) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
expect(wrapper.findComponent({ name: 'DataTable' }).props('columns').find((column: { key: string }) => column.key === 'finance')).toMatchObject({ defaultVisible: false })
})
it('operations queries charts and records together and resets with only one request', async () => {
vi.mocked(reports.listTripOperations).mockResolvedValue({ page: { records: [], total: 0, page: 1, size: 20, pages: 0 }, summary: { trips: 0, inTransit: 0, completed: 0, physicalPallets: 0, palletSpaces: 0, ownPackages: 0, externalPackages: 0, externalAgreedAmounts: [], costs: { visible: false, unrecordedTrips: null, unallocatedTrips: null, totals: [] } }, charts: { physicalPallets: [], destinationTrips: [], externalAgreedAmounts: [], externalPalletRates: [] } })
const wrapper = mount(TripOperationsView); await flushPromises()
const initial = vi.mocked(reports.listTripOperations).mock.calls[0]![0]
const toolbar = wrapper.findComponent(QueryToolbar)
toolbar.vm.$emit('update:modelValue', { from: '2026-10-01', to: '2026-10-07', keyword: 'TRIP', sourceType: 'EXTERNAL', status: 'IN_TRANSIT' })
toolbar.vm.$emit('search'); await flushPromises()
expect(reports.listTripOperations).toHaveBeenLastCalledWith(expect.objectContaining({ from: '2026-10-01T00:00:00Z', to: '2026-10-08T00:00:00.000Z', keyword: 'TRIP', sourceType: 'EXTERNAL', status: 'IN_TRANSIT' }))
expect(wrapper.text()).toContain('2026-10-01 至 2026-10-07(UTC)')
toolbar.vm.$emit('update:modelValue', { from: '2026-09-01', to: '2026-09-07' }); await flushPromises()
expect(wrapper.text()).toContain('2026-10-01 至 2026-10-07(UTC)')
toolbar.vm.$emit('reset'); toolbar.vm.$emit('search'); await flushPromises()
expect(reports.listTripOperations).toHaveBeenCalledTimes(3)
expect(reports.listTripOperations).toHaveBeenLastCalledWith(initial)
expect(toolbar.props('modelValue')).toMatchObject({ keyword: '', sourceType: '', status: '' })
})
it('operations shows query failures without leaving stale statistics', async () => {
vi.mocked(reports.listTripOperations).mockRejectedValue(new Error('统计暂不可用'))
const wrapper = mount(TripOperationsView); await flushPromises()
expect(wrapper.findComponent(TripOperationsCharts).exists()).toBe(false)
expect(wrapper.findComponent({ name: 'LoadErrorState' }).attributes('message')).toBe('运营记录加载失败')
expect(wrapper.text()).not.toContain('实际出车 0 次')
})