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', status: 'MANUAL_REQUIRED' as const, triggerSource: 'OUTBOUND_CONFIRM', latestStandardNode: 'DEPARTED_WAREHOUSE' as const, latestNodeObservedAt: '2026-08-20T10:00: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') expect(wrapper.text()).toContain('标准节点与变更历史暂不可用') 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() expect(wrapper.text()).toContain('本页面不会创建或模拟任务') expect(getCarrierScheduleQuery).not.toHaveBeenCalled() expect(getPortSchedule).not.toHaveBeenCalled() }) 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('目的港可提柜后停止') }) })