149 lines
7.2 KiB
TypeScript
149 lines
7.2 KiB
TypeScript
import ElementPlus from 'element-plus'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import TruckAppointmentsView from './TruckAppointmentsView.vue'
|
|
import { listTruckAppointments } from '../api/platformTrucking'
|
|
import type { TruckAppointmentResource } from '../types'
|
|
import { useAuthStore } from '@/core/auth/auth'
|
|
import { readFileSync } from 'node:fs'
|
|
import { resolve } from 'node:path'
|
|
|
|
const source = readFileSync(resolve(process.cwd(), 'src/modules/trucking/pages/TruckAppointmentsView.vue'), 'utf8')
|
|
|
|
vi.mock('../api/platformTrucking', () => ({
|
|
listTruckAppointments: vi.fn(),
|
|
}))
|
|
vi.mock('@/modules/iam', () => ({
|
|
listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }),
|
|
}))
|
|
|
|
const row: TruckAppointmentResource = {
|
|
id: '9001', appointmentNo: 'TA-9001', status: 'DISPATCHED', orderNo: 'ORD-1', oceanBookingNo: 'OB-1', containerBookingId: '77', cabinetNo: 'CAB-1',
|
|
containerNo: 'CONT-1', warehouseName: '义乌仓', shippingCompanyName: '船公司A', bookingAgentName: '代理A', carrierName: '宁波运输', driverName: '王强', vehicleNo: '浙B12345',
|
|
plannedLoadingAt: '2026-08-10T10:00:00', payableAmount: '3200.00', receivableAmount: '3800.00', attachmentCount: 2, attachments: [],
|
|
}
|
|
|
|
describe('TruckAppointmentsView', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(listTruckAppointments).mockReset().mockResolvedValue({ records: [row], page: 1, size: 20, total: 1, pages: 1 })
|
|
setActivePinia(createPinia())
|
|
useAuthStore().permissions = [
|
|
'platform:firstleg:truck-appointment:view',
|
|
'platform:firstleg:truck-appointment:edit',
|
|
]
|
|
})
|
|
|
|
it('loads the real list and renders relationship snapshots', async () => {
|
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
|
await flushPromises()
|
|
expect(listTruckAppointments).toHaveBeenCalledWith({ page: 1, size: 20, keyword: '', status: '', containerBookingId: '', containerType: '' })
|
|
expect(wrapper.text()).toContain('TA-9001')
|
|
expect(wrapper.text()).toContain('CONT-1')
|
|
expect(wrapper.text()).toContain('CAB-1')
|
|
expect(wrapper.text()).toContain('OB-1')
|
|
expect(wrapper.text()).toContain('义乌仓')
|
|
expect(wrapper.text()).toContain('船公司A')
|
|
expect(wrapper.text()).toContain('代理A')
|
|
expect(wrapper.text()).not.toContain('77')
|
|
expect(wrapper.text()).toContain('宁波运输')
|
|
expect(wrapper.find('button[aria-label="查看 TA-9001"]').exists()).toBe(true)
|
|
})
|
|
|
|
it('uses stable accessible labels when appointment identifiers are missing', async () => {
|
|
vi.mocked(listTruckAppointments).mockResolvedValue({
|
|
records: [
|
|
{ ...row, id: '9002', appointmentNo: '' },
|
|
{ ...row, id: undefined, appointmentNo: null } as unknown as TruckAppointmentResource,
|
|
],
|
|
page: 1,
|
|
size: 20,
|
|
total: 2,
|
|
pages: 1,
|
|
})
|
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('button[aria-label="查看 9002"]').exists()).toBe(true)
|
|
expect(wrapper.find('button[aria-label="查看拖车预约详情"]').exists()).toBe(true)
|
|
const labels = wrapper.findAll('button[aria-label]').map((button) => button.attributes('aria-label'))
|
|
expect(labels.every((label) => Boolean(label) && !/undefined|null/.test(label ?? ''))).toBe(true)
|
|
expect(source).toContain("appointmentActionLabel('编辑', row as TruckAppointmentResource)")
|
|
})
|
|
|
|
it('does not expose a technical carrier id when the business name is unavailable', async () => {
|
|
vi.mocked(listTruckAppointments).mockResolvedValue({
|
|
records: [{ ...row, carrierName: '', carrierId: 'carrier-77' }],
|
|
page: 1,
|
|
size: 20,
|
|
total: 1,
|
|
pages: 1,
|
|
})
|
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('已关联承运商')
|
|
expect(wrapper.text()).not.toContain('carrier-77')
|
|
})
|
|
|
|
it('uses the table toolbar for create and keeps a single empty state', () => {
|
|
expect(source).toContain('<template #toolbar-actions>')
|
|
expect(source).not.toContain('<template #toolbar-start>')
|
|
expect(source).not.toContain('PageToolbar')
|
|
expect(source).not.toContain('empty-hint')
|
|
expect(source.match(/empty-text=/g)).toHaveLength(1)
|
|
})
|
|
|
|
it('shows the two frequent row actions directly without a more menu', () => {
|
|
expect(source).not.toContain('platform:firstleg:truck-appointment:view')
|
|
expect(source).toContain("access.can('platform:firstleg:truck-appointment:edit') && ['DRAFT', 'PENDING_DISPATCH', 'RESCHEDULED'].includes(row.status)")
|
|
expect(source).toContain('<Eye :size="15" />详情')
|
|
expect(source).toContain('<Pencil :size="15" />编辑')
|
|
expect(source).not.toContain('MoreHorizontal')
|
|
})
|
|
|
|
it('hides edit for completed appointments rejected by the backend edit contract', async () => {
|
|
vi.mocked(listTruckAppointments).mockResolvedValue({
|
|
records: [{ ...row, status: 'COMPLETED' }],
|
|
page: 1,
|
|
size: 20,
|
|
total: 1,
|
|
pages: 1,
|
|
})
|
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
|
await flushPromises()
|
|
expect(wrapper.find('button[aria-label="编辑 TA-9001"]').exists()).toBe(false)
|
|
})
|
|
|
|
it('shows the detail action with the list permission', async () => {
|
|
useAuthStore().permissions = ['platform:firstleg:truck-appointment:list']
|
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
|
await flushPromises()
|
|
expect(wrapper.find('button[aria-label="查看 TA-9001"]').exists()).toBe(true)
|
|
})
|
|
|
|
it('shows the prototype-backed trucking fee snapshots in the list', () => {
|
|
expect(source).not.toContain("key: 'customerName'")
|
|
expect(source).toContain("key: 'updatedAt'")
|
|
expect(source).toContain("key: 'payableAmount', label: '应付拖车费'")
|
|
expect(source).toContain("key: 'receivableAmount', label: '应收拖车费'")
|
|
expect(source.indexOf("key: 'store'")).toBeLessThan(source.indexOf("key: 'payableAmount'"))
|
|
expect(source.indexOf("key: 'payableAmount'")).toBeLessThan(source.indexOf("key: 'receivableAmount'"))
|
|
expect(source.indexOf("key: 'receivableAmount'")).toBeLessThan(source.indexOf("key: 'plannedLoadingAt'"))
|
|
expect(source).toContain("toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })")
|
|
expect(source).toContain("key: 'attachmentCount'")
|
|
})
|
|
it('does not expose customer filtering or snapshots', () => {
|
|
expect(source).not.toContain('listPlatformTenants')
|
|
expect(source).not.toContain("key: 'customerId'")
|
|
expect(source).not.toContain("label: '客户'")
|
|
expect(source).not.toContain('customerId:')
|
|
expect(source).not.toContain('customerName')
|
|
})
|
|
it('uses bordered list actions instead of text links', () => {
|
|
expect(source).toContain('size="small" type="primary" plain')
|
|
expect(source).toContain('size="small" :aria-label="appointmentActionLabel(\'编辑\'')
|
|
expect(source).not.toContain("truck-appointment:view')\" link")
|
|
expect(source).toContain(':total="total" stripe preference-key="trucking-appointments"')
|
|
})
|
|
})
|