import { flushPromises, mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createI18n } from 'vue-i18n' import { createMemoryHistory, createRouter } from 'vue-router' import zhCN from '@/i18n/locales/zh-CN' import ReservationOrderDetailView from '@/views/reservation/ReservationOrderDetailView.vue' import ReservationOrderListView from '@/views/reservation/ReservationOrderListView.vue' import ReservationSourceMessageConversationView from '@/views/reservation/ReservationSourceMessageConversationView.vue' import ReservationTaskListView from '@/views/reservation/ReservationTaskListView.vue' import { useAuthStore } from '@/stores/authStore' import type { ReservationV4OrderTaskListResult, ReservationV4WorkbenchListResult, SourceMessageConversationResult, } from '@/types/reservation' vi.mock('@/services/reservationService', async (importOriginal) => { const actual = await importOriginal() return { ...actual, fetchReservationOrderDetail: vi.fn(), fetchReservationOrders: vi.fn(), fetchReservationTaskList: vi.fn(), fetchReservationV4OrderTasks: vi.fn(), fetchReservationV4WorkbenchItems: vi.fn(), fetchSourceMessageConversation: vi.fn(), } }) const service = await import('@/services/reservationService') function createOrderListResult( displayOrderKey = 'GRP-001', page: { page_num: number; page_size: number; total: number } = { page_num: 1, page_size: 20, total: 1, }, ) { return { items: [ { order_id: displayOrderKey === 'GRP-002' ? '20002' : '20001', hotel_id: 'HOTEL-TEST', order_status: 'ACTIVE', display_order_key: displayOrderKey, temporary_order_no: null, confirmation_number: null, group_code: displayOrderKey, display_name: displayOrderKey, open_task_count: 2, next_processable_task_id: '10002', created_at: '2026-07-08T03:00:00Z', updated_at: '2026-07-08T03:10:00Z', }, ], page, } } function createV4WorkbenchResult( displayOrderKey = 'GRP-001', sourceSubject = 'Booking Request', page: { page_num: number; page_size: number; total: number } = { page_num: 1, page_size: 20, total: 1, }, ): ReservationV4WorkbenchListResult { return { items: [ { item_type: 'ORDER_TASK', target_id: displayOrderKey === 'GRP-NEW' ? '9002' : '9001', hotel_id: 'HOTEL-TEST', source_message_id: '30001', source_message_summary: { source_message_id: '30001', hotel_id: 'HOTEL-TEST', external_message_id: 'msg-30001', external_conversation_id: 'thread-30001', subject: sourceSubject, sender_summary: 'guest@example.test', received_at: '2026-07-08T03:00:00Z', source_sent_at: null, conversation_message_count: 2, }, source_received_at: '2026-07-08T03:00:00Z', display_order_key: displayOrderKey, card_counts: { total_count: 3, readonly_count: 1, pending_confirm_count: 1, review_required_count: 1, confirmed_count: 0, }, next_action_card_id: '9101', notification_status: null, order_task_status: 'OPEN', display_status: 'OPEN', readonly_reason_code: null, created_at: '2026-07-08T03:00:00Z', updated_at: '2026-07-08T03:10:00Z', }, ], page, } } function createV4SourceNotificationWorkbenchResult(routeCode: 'S10' | 'S99' = 'S10'): ReservationV4WorkbenchListResult { return { items: [ { item_type: 'SOURCE_NOTIFICATION', target_id: routeCode === 'S10' ? '7001' : '7002', hotel_id: 'HOTEL-TEST', source_message_id: routeCode === 'S10' ? '39990' : '39999', source_message_summary: { source_message_id: routeCode === 'S10' ? '39990' : '39999', hotel_id: 'HOTEL-TEST', external_message_id: `msg-${routeCode}`, external_conversation_id: 'thread-source-only', subject: routeCode === 'S10' ? 'FYI only' : 'Unclear email', sender_summary: 'guest@example.test', received_at: '2026-07-08T03:00:00Z', source_sent_at: null, conversation_message_count: 1, }, source_received_at: '2026-07-08T03:00:00Z', display_order_key: null, card_counts: null, next_action_card_id: null, notification_status: 'ACK_REQUIRED', order_task_status: null, display_status: 'ACK_REQUIRED', readonly_reason_code: null, created_at: '2026-07-08T03:00:00Z', updated_at: '2026-07-08T03:10:00Z', }, ], page: { page_num: 1, page_size: 20, total: 1, }, } } function createV4OrderTaskListResult( displayOrderKey = 'GRP-002', sourceSubject = 'Booking Update', page: { page_num: number; page_size: number; total: number } = { page_num: 1, page_size: 20, total: 1, }, ): ReservationV4OrderTaskListResult { return { items: [ { order_task: { order_task_id: displayOrderKey === 'GRP-NEW' ? '9002' : '9001', hotel_id: 'HOTEL-TEST', source_message_id: '30002', ai_batch_id: '8001', order_ref: 'order-1', order_context_index: 1, order_id: '20001', target_booking_type: 'GROUP', target_locator_type: 'GROUP_CODE', target_locator_value: displayOrderKey, target_resolution_status: 'RESOLVED', order_task_status: 'OPEN', display_order_key: displayOrderKey, source_received_at: '2026-07-08T04:00:00Z', version: 0, created_at: '2026-07-08T04:00:00Z', updated_at: '2026-07-08T04:10:00Z', }, source_message_summary: { source_message_id: '30002', hotel_id: 'HOTEL-TEST', external_message_id: 'msg-30002', external_conversation_id: 'thread-30002', subject: sourceSubject, sender_summary: 'agent@example.test', received_at: '2026-07-08T04:00:00Z', source_sent_at: null, conversation_message_count: 3, }, card_counts: { total_count: 2, readonly_count: 1, pending_confirm_count: 1, review_required_count: 0, confirmed_count: 0, }, next_action_card_id: '9102', display_status: 'OPEN', availability: { blocked: false, read_only: false, editable: true, confirmable: true, reviewable: false, ackable: false, readonly_reason_code: null, blocked_by_order_task_id: null, blocked_by_card_id: null, blocked_reason: null, }, }, ], page, } } function createOrderDetailResult(displayName = 'GRP-001') { return { order: { order_id: displayName === 'GRP-002' ? '20002' : '20001', hotel_id: 'HOTEL-TEST', order_status: 'ACTIVE', temporary_order_no: null, confirmation_number: null, group_code: displayName, block_code: null, allotment_code: null, display_name: displayName, created_at: '2026-07-08T03:00:00Z', updated_at: '2026-07-08T03:10:00Z', }, tasks: [ { task_id: '10001', task_type: 'NEW_BOOKING', task_subtype: 'NEW_BOOKING', task_status: 'PENDING_CONFIRM', card_name: 'New Booking', queue_sequence: 1, queue_participation: true, can_process: true, readonly_reason_code: null, source_message_id: '30001', source_subject: 'Booking Request', source_sender_summary: 'guest@example.test', source_received_at: '2026-07-08T03:00:00Z', external_conversation_id: null, conversation_message_count: 2, created_at: '2026-07-08T03:00:00Z', }, { task_id: '10002', task_type: 'UPDATE_BOOKING', task_subtype: 'RATE_CHANGE', task_status: 'BLOCKED', card_name: 'Rate Change', queue_sequence: 2, queue_participation: true, can_process: false, readonly_reason_code: 'PREVIOUS_TASK_NOT_FINISHED', source_message_id: '30002', source_subject: 'Booking Update', source_sender_summary: 'agent@example.test', source_received_at: '2026-07-08T04:00:00Z', external_conversation_id: null, conversation_message_count: 3, created_at: '2026-07-08T04:00:00Z', }, ], v4_order_tasks: [ { order_task_id: '9001', order_ref: 'order-ref-1', order_task_status: 'OPEN', card_counts: { total_count: 3, readonly_count: 1, pending_confirm_count: 1, review_required_count: 1, confirmed_count: 0, }, source_message_summary: { source_message_id: '30003', hotel_id: 'HOTEL-TEST', external_message_id: 'm-v4-1', external_conversation_id: 'thread-v4-1', subject: 'V4 Booking Package', sender_summary: 'v4@example.test', received_at: '2026-07-08T05:00:00Z', source_sent_at: null, conversation_message_count: 1, }, source_received_at: '2026-07-08T05:00:00Z', created_at: '2026-07-08T05:00:00Z', updated_at: '2026-07-08T05:10:00Z', latest_activity_at: '2026-07-08T05:10:00Z', }, ], warnings: [], } } function createSourceMessageConversationResult( overrides: Partial = {}, ): SourceMessageConversationResult { const result = createSourceMessageConversationResultBase() return { ...result, ...overrides, } } function createSourceMessageConversationResultBase(): SourceMessageConversationResult { return { conversation: { hotel_id: 'HOTEL-TEST', channel: 'EMAIL', external_conversation_id: 'thread-30001', subject: 'Booking Request', message_count: 2, first_received_at: '2026-07-08T02:00:00Z', last_received_at: '2026-07-08T03:00:00Z', }, messages: [ { id: '30001', external_message_id: 'msg-30001', external_conversation_id: 'thread-30001', sender_summary: 'guest@example.test', subject: 'Booking Request', received_at: '2026-07-08T02:00:00Z', source_sent_at: '2026-07-08T01:58:00Z', text_body: '完整邮件正文', html_body: '原始邮件正文', html_body_sanitized: '清洗后邮件正文', html_sanitize_required: true, html_render_mode: 'SANITIZED_HTML', inline_images: [], attachments: [ { mediaType: 'ATTACHMENT', fileName: 'rooming-list.xlsx', contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', sizeBytes: 1024, externalUrl: 'https://example.test/rooming-list.xlsx', externalMediaId: 'media-1', }, ], related_orders: [ { order_id: '20001', display_order_key: 'GRP-001', order_status: 'ACTIVE', }, ], related_tasks: [ { task_id: '10001', order_id: '20001', task_type: 'NEW_BOOKING', task_subtype: 'NEW_BOOKING', task_status: 'PENDING_CONFIRM', card_name: 'New Booking', }, ], }, ], } } function createAttachment(overrides: Record = {}) { return { mediaType: 'ATTACHMENT', fileName: 'rooming-list.xlsx', contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', sizeBytes: 1024, externalUrl: 'https://example.test/rooming-list.xlsx', externalMediaId: 'media-1', ...overrides, } } function createDeferred() { let resolve!: (value: T) => void let reject!: (reason?: unknown) => void const promise = new Promise((promiseResolve, promiseReject) => { resolve = promiseResolve reject = promiseReject }) return { promise, resolve, reject } } function createAuthPayload() { return { access_token: 'session-token', token_type: 'Bearer', expires_at: '2026-07-09T12:00:00Z', user: { id: '1900000000000000001', username: 'admin', display_name: '系统管理员', super_admin: true, }, default_hotel_id: 'HOTEL-TEST', hotels: [ { hotel_id: 'HOTEL-TEST', hotel_name: '测试酒店', time_zone: 'Asia/Bangkok', default_hotel: true, }, { hotel_id: 'HOTEL-BKK', hotel_name: '曼谷酒店', time_zone: 'Asia/Bangkok', default_hotel: false, }, ], permissions: ['HOTEL_SWITCH'], menus: [], } } async function mountWithPlugins(component: object, initialPath = '/', stubs: Record = {}) { const pinia = createPinia() setActivePinia(pinia) const authStore = useAuthStore() authStore.applyLoginResult(createAuthPayload()) const i18n = createI18n({ legacy: false, locale: 'zh-CN', messages: { 'zh-CN': zhCN, }, }) const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/', component: { template: '
' } }, { path: '/reservation/orders/:orderId', component: { template: '
' } }, { path: '/reservation/tasks/:taskId', component: { template: '
' } }, { path: '/reservation/order-tasks/:orderTaskId', component: { template: '
' } }, { path: '/reservation/source-notifications/:notificationId', component: { template: '
' } }, { path: '/reservation/source-messages/:sourceMessageId/conversation', component: { template: '
' } }, ], }) await router.push(initialPath) await router.isReady() return mount(component, { global: { plugins: [pinia, i18n, router], stubs: { RouterLink: { template: '', }, ...stubs, }, }, }) } describe('reservation P0 views', () => { beforeEach(() => { sessionStorage.clear() localStorage.clear() vi.mocked(service.fetchReservationOrderDetail).mockReset() vi.mocked(service.fetchReservationOrders).mockReset() vi.mocked(service.fetchReservationTaskList).mockReset() vi.mocked(service.fetchReservationV4OrderTasks).mockReset() vi.mocked(service.fetchReservationV4WorkbenchItems).mockReset() vi.mocked(service.fetchSourceMessageConversation).mockReset() }) it('renders active order task source evidence without the stale pending copy', async () => { vi.mocked(service.fetchReservationOrderDetail).mockResolvedValue(createOrderDetailResult()) const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001', { ReservationTaskDetailPanel: { template: '
', }, }) await vi.dynamicImportSettled() expect(wrapper.text()).toContain('Booking Request') expect(wrapper.text()).toContain('guest@example.test') expect(wrapper.text()).toContain('2') expect(wrapper.text()).toContain('前序任务未完成') expect(wrapper.text()).toContain('查看邮件会话') expect(wrapper.text()).toContain(zhCN.order.v4TaskTimeline) expect(wrapper.text()).toContain('order-ref-1') expect(wrapper.text()).toContain('V4 Booking Package') expect(wrapper.text()).not.toContain('PREVIOUS_TASK_NOT_FINISHED') expect(wrapper.text()).not.toContain('来源邮件摘要字段待后端补充') }) it('renders order list items returned by the backend', async () => { vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult()) const wrapper = await mountWithPlugins(ReservationOrderListView) await vi.dynamicImportSettled() const rowText = wrapper.find('tbody').text() expect(wrapper.text()).toContain('GRP-001') expect(rowText).not.toContain('20001') expect(rowText).not.toContain('有效') expect(wrapper.text()).not.toContain('未关闭任务') expect(wrapper.text()).toContain('继续处理') expect(wrapper.text()).not.toContain('接口待接入') }) it('filters order list with order status and business-key fields', async () => { vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult()) const wrapper = await mountWithPlugins(ReservationOrderListView) await vi.dynamicImportSettled() await wrapper.find('select[name="order_status"]').setValue('LOGIC_DELETED') await vi.dynamicImportSettled() await wrapper.find('input[name="group_code"]').setValue('GRP-009') await vi.dynamicImportSettled() await wrapper.find('input[name="confirmation_number"]').setValue('CNF-009') await vi.dynamicImportSettled() expect(service.fetchReservationOrders).toHaveBeenLastCalledWith( expect.objectContaining({ order_status: 'LOGIC_DELETED', group_code: 'GRP-009', confirmation_number: 'CNF-009', page_num: 1, }), ) }) it('paginates order list requests with backend page metadata', async () => { vi.mocked(service.fetchReservationOrders) .mockResolvedValueOnce(createOrderListResult('GRP-001', { page_num: 1, page_size: 20, total: 41 })) .mockResolvedValueOnce(createOrderListResult('GRP-002', { page_num: 2, page_size: 20, total: 41 })) const wrapper = await mountWithPlugins(ReservationOrderListView) await vi.dynamicImportSettled() expect(wrapper.text()).toContain('第 1 / 3 页') await wrapper.findAll('button').find((button) => button.text().includes('下一页'))?.trigger('click') await vi.dynamicImportSettled() expect(service.fetchReservationOrders).toHaveBeenLastCalledWith( expect.objectContaining({ page_num: 2, page_size: 20, }), ) expect(wrapper.text()).toContain('GRP-002') expect(wrapper.text()).toContain('第 2 / 3 页') }) it('renders backend-normalized order pagination metadata', async () => { vi.mocked(service.fetchReservationOrders).mockResolvedValue( createOrderListResult('GRP-001', { page_num: 3, page_size: 10, total: 31 }), ) const wrapper = await mountWithPlugins(ReservationOrderListView) await vi.dynamicImportSettled() expect(wrapper.text()).toContain('第 3 / 4 页') }) it('reloads the order list when the selected hotel changes', async () => { vi.mocked(service.fetchReservationOrders) .mockResolvedValueOnce(createOrderListResult('GRP-001')) .mockResolvedValueOnce(createOrderListResult('GRP-002')) const wrapper = await mountWithPlugins(ReservationOrderListView) await vi.dynamicImportSettled() const authStore = useAuthStore() authStore.setSelectedHotelId('HOTEL-BKK') await vi.dynamicImportSettled() expect(service.fetchReservationOrders).toHaveBeenCalledTimes(2) expect(service.fetchReservationOrders).toHaveBeenLastCalledWith( expect.objectContaining({ page_num: 1, }), ) expect(wrapper.text()).toContain('GRP-002') }) it('renders V4 workbench items returned by the backend', async () => { vi.mocked(service.fetchReservationV4WorkbenchItems).mockResolvedValue(createV4WorkbenchResult('GRP-001', 'Booking Request')) const wrapper = await mountWithPlugins(ReservationTaskListView) await vi.dynamicImportSettled() expect(wrapper.text()).toContain('GRP-001') expect(wrapper.text()).toContain('Booking Request') expect(wrapper.text()).toContain('订单任务') expect(wrapper.text()).toContain('待确认 1') expect(wrapper.text()).toContain('待复核 1') expect(wrapper.text()).toContain('查看邮件会话') expect(wrapper.text()).not.toContain('接口待补') expect(service.fetchReservationV4WorkbenchItems).toHaveBeenCalledWith( expect.objectContaining({ page_num: 1, page_size: 20, }), ) expect(service.fetchReservationTaskList).not.toHaveBeenCalled() }) it('renders task source received time in the source email column', async () => { vi.mocked(service.fetchReservationV4WorkbenchItems).mockResolvedValue(createV4WorkbenchResult('GRP-001', 'Booking Request')) const wrapper = await mountWithPlugins(ReservationTaskListView) await vi.dynamicImportSettled() expect(wrapper.text()).toContain('来源时间') expect(wrapper.text()).toContain('2026-07-08 10:00:00') expect(wrapper.text()).toContain('guest@example.test') }) it('renders V4 S10/S99 source notification rows without a visible order action', async () => { vi.mocked(service.fetchReservationV4WorkbenchItems).mockResolvedValue(createV4SourceNotificationWorkbenchResult('S10')) const wrapper = await mountWithPlugins(ReservationTaskListView) await vi.dynamicImportSettled() expect(wrapper.text()).toContain('FYI only') expect(wrapper.text()).toContain('来源通知') expect(wrapper.text()).toContain('待确认已读') expect(wrapper.text()).toContain('查看通知') expect(wrapper.text()).toContain('查看邮件会话') expect(wrapper.text()).not.toContain('查看订单') }) it('filters V4 workbench with supported backend fields only', async () => { vi.mocked(service.fetchReservationV4WorkbenchItems) .mockResolvedValueOnce(createV4WorkbenchResult('GRP-001', 'Booking Request', { page_num: 1, page_size: 20, total: 45 })) .mockResolvedValueOnce(createV4SourceNotificationWorkbenchResult('S10')) const wrapper = await mountWithPlugins(ReservationTaskListView) await vi.dynamicImportSettled() const itemTypeOptions = wrapper.find('select[name="item_type"]').findAll('option') .map((option) => option.attributes('value')) expect(itemTypeOptions).toEqual(['', 'ORDER_TASK', 'SOURCE_NOTIFICATION']) expect(wrapper.find('select[name="item_type"]').text()).toContain('来源通知') expect(wrapper.find('select[name="item_type"]').text()).not.toContain('INFORMATIONAL_MESSAGE') await wrapper.find('select[name="item_type"]').setValue('SOURCE_NOTIFICATION') await vi.dynamicImportSettled() expect(service.fetchReservationV4WorkbenchItems).toHaveBeenLastCalledWith( expect.objectContaining({ item_type: 'SOURCE_NOTIFICATION', page_num: 1, }), ) const lastTaskRequest = vi.mocked(service.fetchReservationV4WorkbenchItems).mock.calls[ vi.mocked(service.fetchReservationV4WorkbenchItems).mock.calls.length - 1 ]?.[0] ?? {} expect(Object.keys(lastTaskRequest).sort()).toEqual(['item_type', 'keyword', 'page_num', 'page_size']) expect(lastTaskRequest).not.toHaveProperty('task_type') expect(lastTaskRequest).not.toHaveProperty('task_subtype') expect(lastTaskRequest).not.toHaveProperty('result_type') expect(lastTaskRequest).not.toHaveProperty('route_code') }) it('filters and paginates V4 business order tasks with supported backend fields', async () => { vi.mocked(service.fetchReservationV4WorkbenchItems) .mockResolvedValueOnce(createV4WorkbenchResult('GRP-001', 'Booking Request', { page_num: 1, page_size: 20, total: 45 })) vi.mocked(service.fetchReservationV4OrderTasks) .mockResolvedValueOnce(createV4OrderTaskListResult('GRP-001', 'Booking Request', { page_num: 1, page_size: 20, total: 45 })) .mockResolvedValueOnce(createV4OrderTaskListResult('GRP-001', 'Booking Request', { page_num: 1, page_size: 20, total: 45 })) .mockResolvedValueOnce(createV4OrderTaskListResult('GRP-001', 'Booking Request', { page_num: 1, page_size: 20, total: 45 })) .mockResolvedValueOnce(createV4OrderTaskListResult('GRP-002', 'Booking Update', { page_num: 2, page_size: 20, total: 45 })) const wrapper = await mountWithPlugins(ReservationTaskListView) await vi.dynamicImportSettled() expect(wrapper.find('select[name="order_task_status"]').attributes('disabled')).toBeDefined() expect(wrapper.find('select[name="card_status"]').attributes('disabled')).toBeDefined() await wrapper.find('select[name="item_type"]').setValue('ORDER_TASK') await vi.dynamicImportSettled() expect(wrapper.find('select[name="order_task_status"]').attributes('disabled')).toBeUndefined() expect(wrapper.find('select[name="card_status"]').attributes('disabled')).toBeUndefined() await wrapper.find('select[name="order_task_status"]').setValue('OPEN') await vi.dynamicImportSettled() await wrapper.find('select[name="card_status"]').setValue('REVIEW_REQUIRED') await vi.dynamicImportSettled() const taskListCalls = vi.mocked(service.fetchReservationV4OrderTasks).mock.calls const filteredTaskRequest = taskListCalls[taskListCalls.length - 1]?.[0] ?? {} expect(filteredTaskRequest).toMatchObject({ order_task_status: 'OPEN', card_status: 'REVIEW_REQUIRED', page_num: 1, }) await wrapper.findAll('button').find((button) => button.text().includes('下一页'))?.trigger('click') await vi.dynamicImportSettled() expect(service.fetchReservationV4OrderTasks).toHaveBeenLastCalledWith( expect.objectContaining({ order_task_status: 'OPEN', card_status: 'REVIEW_REQUIRED', page_num: 2, page_size: 20, }), ) expect(wrapper.text()).toContain('GRP-002') expect(wrapper.text()).toContain('第 2 / 3 页') }) it('renders backend-normalized task pagination metadata', async () => { vi.mocked(service.fetchReservationV4WorkbenchItems).mockResolvedValue( createV4WorkbenchResult('GRP-001', 'Booking Request', { page_num: 2, page_size: 10, total: 21 }), ) const wrapper = await mountWithPlugins(ReservationTaskListView) await vi.dynamicImportSettled() expect(wrapper.text()).toContain('第 2 / 3 页') }) it('reloads the task list when the selected hotel changes', async () => { vi.mocked(service.fetchReservationV4WorkbenchItems) .mockResolvedValueOnce(createV4WorkbenchResult('GRP-001', 'Booking Request')) .mockResolvedValueOnce(createV4WorkbenchResult('GRP-002', 'Booking Update')) const wrapper = await mountWithPlugins(ReservationTaskListView) await vi.dynamicImportSettled() const authStore = useAuthStore() authStore.setSelectedHotelId('HOTEL-BKK') await vi.dynamicImportSettled() expect(service.fetchReservationV4WorkbenchItems).toHaveBeenCalledTimes(2) expect(service.fetchReservationV4WorkbenchItems).toHaveBeenLastCalledWith( expect.objectContaining({ page_num: 1, }), ) expect(wrapper.text()).toContain('Booking Update') }) it('reloads order detail when the selected hotel changes', async () => { vi.mocked(service.fetchReservationOrderDetail) .mockResolvedValueOnce(createOrderDetailResult('GRP-001')) .mockResolvedValueOnce(createOrderDetailResult('GRP-002')) const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001', { ReservationTaskDetailPanel: { template: '
', }, }) await vi.dynamicImportSettled() const authStore = useAuthStore() authStore.setSelectedHotelId('HOTEL-BKK') await vi.dynamicImportSettled() expect(service.fetchReservationOrderDetail).toHaveBeenCalledTimes(2) expect(service.fetchReservationOrderDetail).toHaveBeenLastCalledWith('20001') expect(wrapper.text()).toContain('GRP-002') }) it('keeps the latest task list response when filters change quickly', async () => { const firstRequest = createDeferred>>() const secondRequest = createDeferred>>() vi.mocked(service.fetchReservationV4WorkbenchItems) .mockReturnValueOnce(firstRequest.promise) .mockReturnValueOnce(secondRequest.promise) const wrapper = await mountWithPlugins(ReservationTaskListView) await wrapper.find('input[type="search"]').setValue('fresh') expect(service.fetchReservationV4WorkbenchItems).toHaveBeenCalledTimes(2) secondRequest.resolve(createV4WorkbenchResult('GRP-NEW', 'New Booking Request')) await flushPromises() expect(wrapper.text()).toContain('GRP-NEW') firstRequest.resolve(createV4WorkbenchResult('GRP-OLD', 'Old Booking Request')) await flushPromises() expect(wrapper.text()).toContain('GRP-NEW') expect(wrapper.text()).not.toContain('GRP-OLD') }) it('renders full source message conversation returned by the backend', async () => { vi.mocked(service.fetchSourceMessageConversation).mockResolvedValue(createSourceMessageConversationResult()) const wrapper = await mountWithPlugins( ReservationSourceMessageConversationView, '/reservation/source-messages/30001/conversation', ) await vi.dynamicImportSettled() expect(service.fetchSourceMessageConversation).toHaveBeenCalledWith('30001') expect(wrapper.text()).toContain('Booking Request') expect(wrapper.text()).toContain('清洗后邮件正文') expect(wrapper.text()).toContain('rooming-list.xlsx') expect(wrapper.find('.message-body').html()).toContain('清洗后邮件正文') expect(wrapper.find('.message-body').html()).not.toContain('