2026-08-20 09:10:36 +08:00
|
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
|
|
|
import ElementPlus from 'element-plus'
|
|
|
|
|
import CarrierScheduleQueryPanel from './CarrierScheduleQueryPanel.vue'
|
|
|
|
|
import { getCarrierScheduleQuery, listCarrierScheduleQueries } from '../api/platformCarrierScheduleQueries'
|
|
|
|
|
import { getPortSchedule } from '../api/platformPortSchedules'
|
|
|
|
|
|
|
|
|
|
vi.mock('../api/platformCarrierScheduleQueries', () => ({
|
|
|
|
|
listCarrierScheduleQueries: vi.fn(),
|
|
|
|
|
getCarrierScheduleQuery: vi.fn(),
|
|
|
|
|
}))
|
|
|
|
|
vi.mock('../api/platformPortSchedules', () => ({ getPortSchedule: vi.fn() }))
|
|
|
|
|
|
|
|
|
|
const queryView = {
|
|
|
|
|
query: {
|
|
|
|
|
id: '11', queryNo: 'CSQ11', customerId: '1', oceanBookingId: '2', containerBookingId: '3',
|
|
|
|
|
carrierCode: 'COSCO', trackingType: 'CONTAINER_NO', trackingNo: 'CSNU1234567',
|
|
|
|
|
officialUrl: 'https://elines.coscoshipping.com/ebusiness/', accessMode: 'DYNAMIC_IFRAME',
|
2026-08-20 18:02:52 +08:00
|
|
|
status: 'MANUAL_REQUIRED' as const, triggerSource: 'OUTBOUND_CONFIRM',
|
|
|
|
|
latestStandardNode: 'DEPARTED_WAREHOUSE' as const, latestNodeObservedAt: '2026-08-20T10:00:00',
|
2026-08-20 09:10:36 +08:00
|
|
|
lastErrorCode: 'DYNAMIC_IFRAME_REQUIRED', lastErrorMessage: '必须人工查询',
|
|
|
|
|
resultPortScheduleId: '21', version: 1,
|
|
|
|
|
},
|
|
|
|
|
expired: true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('CarrierScheduleQueryPanel loading states', () => {
|
|
|
|
|
beforeEach(() => vi.clearAllMocks())
|
|
|
|
|
|
|
|
|
|
it('keeps list facts visible when history and linked schedule both fail', async () => {
|
|
|
|
|
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [queryView], page: 1, size: 200, total: 1, pages: 1 })
|
|
|
|
|
vi.mocked(getCarrierScheduleQuery).mockRejectedValue(new Error('detail unavailable'))
|
|
|
|
|
vi.mocked(getPortSchedule).mockRejectedValue(new Error('schedule unavailable'))
|
|
|
|
|
|
|
|
|
|
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2', containerBookingId: '3' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(listCarrierScheduleQueries).toHaveBeenCalledWith({ page: 1, size: 200, oceanBookingId: '2', containerBookingId: '3' })
|
|
|
|
|
expect(getCarrierScheduleQuery).toHaveBeenCalledWith('11')
|
|
|
|
|
expect(getPortSchedule).toHaveBeenCalledWith('21')
|
|
|
|
|
expect(wrapper.text()).toContain('CSQ11')
|
2026-08-20 18:02:52 +08:00
|
|
|
expect(wrapper.text()).toContain('标准节点与变更历史暂不可用')
|
2026-08-20 09:10:36 +08:00
|
|
|
expect(wrapper.text()).toContain('关联船期结果暂不可用')
|
|
|
|
|
expect(wrapper.text()).toContain('DYNAMIC_IFRAME_REQUIRED')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('shows an explicit non-fabricated empty state and performs no detail reads', async () => {
|
|
|
|
|
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 })
|
|
|
|
|
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
2026-08-20 18:02:52 +08:00
|
|
|
expect(wrapper.text()).toContain('本页面不会创建或模拟任务')
|
2026-08-20 09:10:36 +08:00
|
|
|
expect(getCarrierScheduleQuery).not.toHaveBeenCalled()
|
|
|
|
|
expect(getPortSchedule).not.toHaveBeenCalled()
|
|
|
|
|
})
|
2026-08-20 18:02:52 +08:00
|
|
|
|
|
|
|
|
it('falls back to the booking/container task when an automatic task has no linked port schedule', async () => {
|
|
|
|
|
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [{ ...queryView, query: { ...queryView.query, resultPortScheduleId: null } }], page: 1, size: 200, total: 1, pages: 1 })
|
|
|
|
|
vi.mocked(getCarrierScheduleQuery).mockResolvedValue({ query: { ...queryView, query: { ...queryView.query, resultPortScheduleId: null } }, nodes: [], histories: [] })
|
|
|
|
|
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2', containerBookingId: '3', portScheduleId: '99' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(wrapper.text()).toContain('CSQ11')
|
|
|
|
|
expect(getCarrierScheduleQuery).toHaveBeenCalledWith('11')
|
|
|
|
|
expect(getPortSchedule).not.toHaveBeenCalled()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('renders canonical node location, actual time, source and stopped facts from detail DTO', async () => {
|
|
|
|
|
vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [{ ...queryView, query: { ...queryView.query, status: 'STOPPED', latestStandardNode: 'PICKUP_READY', stoppedAt: '2026-08-20T18:00:00', stopReason: '目的港可提柜后停止', resultPortScheduleId: null }, expired: false }], page: 1, size: 200, total: 1, pages: 1 })
|
|
|
|
|
vi.mocked(getCarrierScheduleQuery).mockResolvedValue({
|
|
|
|
|
query: { query: { ...queryView.query, status: 'STOPPED', latestStandardNode: 'PICKUP_READY', stoppedAt: '2026-08-20T18:00:00', stopReason: '目的港可提柜后停止', resultPortScheduleId: null }, expired: false },
|
|
|
|
|
nodes: [{ id: '31', providerCode: 'COSCO', providerNodeCode: 'PICKUP', standardNode: 'PICKUP_READY', nodeSequence: 60, observedAt: '2026-08-20T17:50:00', estimatedAt: null, actualAt: '2026-08-20T17:45:00', locationCode: 'USLAX', locationName: 'Los Angeles', sourceReference: 'provider-event-31', rawPayloadHash: 'a'.repeat(64), createdAt: '2026-08-20T17:51:00' }],
|
|
|
|
|
histories: [{ id: '41', action: 'STOP', fromVersion: 4, toVersion: 5, reason: '目的港可提柜后停止', operatorId: null, operatorName: '系统', createdAt: '2026-08-20T18:00:00' }],
|
|
|
|
|
})
|
|
|
|
|
const wrapper = mount(CarrierScheduleQueryPanel, { props: { oceanBookingId: '2', containerBookingId: '3' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(wrapper.text()).toContain('已停止')
|
|
|
|
|
expect(wrapper.text()).toContain('可提柜')
|
|
|
|
|
expect(wrapper.text()).toContain('USLAX · Los Angeles')
|
|
|
|
|
expect(wrapper.text()).toContain('2026-08-20 17:45')
|
|
|
|
|
expect(wrapper.text()).toContain('COSCO')
|
|
|
|
|
expect(wrapper.text()).toContain('目的港可提柜后停止')
|
|
|
|
|
})
|
2026-08-20 09:10:36 +08:00
|
|
|
})
|