|
|
|
@@ -2,19 +2,23 @@ import ElementPlus, { ElMessageBox } from 'element-plus'
|
|
|
|
|
import { createPinia } from 'pinia'
|
|
|
|
|
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils'
|
|
|
|
|
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
|
|
|
import { defineComponent } from 'vue'
|
|
|
|
|
import { createMemoryHistory, createRouter, RouterView } from 'vue-router'
|
|
|
|
|
import CapacityInquiryDialog from './CapacityInquiryDialog.vue'
|
|
|
|
|
import CapacityOfferSettingsDialog from './CapacityOfferSettingsDialog.vue'
|
|
|
|
|
import CapacityOfferDetailView from '../pages/CapacityOfferDetailView.vue'
|
|
|
|
|
import type { CapacityOffer } from '../capacityOfferTypes'
|
|
|
|
|
import type { FleetTrip } from '../tripTypes'
|
|
|
|
|
import { useUnsavedWorkStore } from '@/core/unsaved-work/unsavedWork'
|
|
|
|
|
import { listCapacityOffers } from '../api/capacityOffers'
|
|
|
|
|
import { getCapacityOffer, listCapacityOffers } from '../api/capacityOffers'
|
|
|
|
|
import { isWorkspaceRoute, workspaceRouteId } from '@/core/workspace-tabs/routeWorkspace'
|
|
|
|
|
|
|
|
|
|
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
|
|
|
|
|
vi.mock('../api/capacityOffers', () => ({ listCapacityOffers: vi.fn(), listOfferCustomers: vi.fn(), listOfferQuotes: vi.fn(), saveCapacityInquiry: vi.fn(), saveCapacityOffer: vi.fn(), commandCapacityOffer: vi.fn() }))
|
|
|
|
|
vi.mock('../api/capacityOffers', () => ({ listCapacityOffers: vi.fn(), getCapacityOffer: vi.fn(), getCapacityInquiry: vi.fn(), listCapacityInquiries: vi.fn(), listOfferCustomers: vi.fn(), listOfferQuotes: vi.fn(), saveCapacityInquiry: vi.fn(), saveCapacityOffer: vi.fn(), commandCapacityOffer: vi.fn(), commandCapacityInquiry: vi.fn() }))
|
|
|
|
|
const offer: CapacityOffer = { offerNo: 'CO-CLOSE', tripNo: 'TR-CLOSE', status: 'OPEN', recordVersion: 1, tripRecordVersion: 2, warehouseId: '9', warehouseName: '测试仓', plannedDepartureAt: '2026-10-20T12:00:00Z', cutoffAt: '2026-10-20T10:00:00Z', contactName: null, contactPhone: null, remark: null, destinations: [{ destinationKey: 'FBA:ONT8', name: 'ONT8', deliveryIndex: 1, remaining: null }], segments: [], capacityKnown: false, canCreateInquiry: true, canConfirm: false, blockReason: '待核对', actions: { canEdit: true, canPublish: false, canPause: true, canResume: false, canClose: true } }
|
|
|
|
|
const wrappers: VueWrapper[] = []
|
|
|
|
|
beforeEach(() => { vi.clearAllMocks(); vi.mocked(listCapacityOffers).mockResolvedValue({ records: [offer], total: 1, page: 1, size: 1, pages: 1 }) })
|
|
|
|
|
afterEach(async () => { ElMessageBox.close(); wrappers.splice(0).forEach(wrapper => wrapper.unmount()); await flushPromises(); document.body.innerHTML = '' })
|
|
|
|
|
beforeEach(() => { vi.clearAllMocks(); vi.mocked(listCapacityOffers).mockResolvedValue({ records: [offer], total: 1, page: 1, size: 1, pages: 1 }); vi.mocked(getCapacityOffer).mockResolvedValue({ offer, inquiries: { records: [], total: 0, page: 1, size: 20, pages: 0 } }) })
|
|
|
|
|
afterEach(async () => { ElMessageBox.close(); wrappers.splice(0).forEach(wrapper => wrapper.unmount()); await flushPromises(); document.body.innerHTML = ''; vi.restoreAllMocks() })
|
|
|
|
|
function mountDialog(kind: 'inquiry' | 'settings') {
|
|
|
|
|
const pinia = createPinia()
|
|
|
|
|
const wrapper = kind === 'inquiry'
|
|
|
|
@@ -56,3 +60,46 @@ it.each(['inquiry', 'settings'] as const)('keeps the real %s dialog and edited i
|
|
|
|
|
accept.click(); await flushPromises()
|
|
|
|
|
expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('blocks browser back with the real router, cached detail and native dialog, without duplicating the global prompt', async () => {
|
|
|
|
|
const pinia = createPinia(), registry = useUnsavedWorkStore(pinia), confirms = vi.spyOn(ElMessageBox, 'confirm')
|
|
|
|
|
const router = createRouter({ history: createMemoryHistory(), routes: [
|
|
|
|
|
{ path: '/pool', name: 'overseas-master-data-capacity-offers', component: { template: '<p data-testid="pool-list">运力列表</p>' }, meta: { requiresAuth: true } },
|
|
|
|
|
{ path: '/pool/:offerNo', name: 'overseas-master-data-capacity-offer-detail', component: CapacityOfferDetailView, meta: { requiresAuth: true } },
|
|
|
|
|
{ path: '/outside', name: 'outside', component: { template: '<p>外部页面</p>' }, meta: { workspaceTab: false } },
|
|
|
|
|
] })
|
|
|
|
|
// Match the application's intentional policy: cross-workspace switching skips the global prompt.
|
|
|
|
|
router.beforeEach(async (to, from) => {
|
|
|
|
|
const fromId = workspaceRouteId(from)
|
|
|
|
|
if (!fromId || isWorkspaceRoute(to) && workspaceRouteId(to) !== fromId || !registry.findForWorkspace(fromId).length) return true
|
|
|
|
|
try { await ElMessageBox.confirm('当前页面有未保存内容', '全局离开确认', { confirmButtonText: '放弃并继续', cancelButtonText: '继续填写' }); registry.removeWorkspaces([fromId]); return true } catch { return false }
|
|
|
|
|
})
|
|
|
|
|
const Host = defineComponent({ components: { RouterView }, template: '<RouterView v-slot="{ Component, route }"><KeepAlive><component :is="Component" :key="route.name" /></KeepAlive></RouterView>' })
|
|
|
|
|
await router.push('/pool'); await router.isReady()
|
|
|
|
|
const wrapper = mount(Host, { attachTo: document.body, global: { plugins: [pinia, router, ElementPlus] } }); wrappers.push(wrapper)
|
|
|
|
|
await router.push('/pool/CO-CLOSE'); await flushPromises()
|
|
|
|
|
await wrapper.findAll('button').find(button => button.text() === '登记意向')!.trigger('click'); await flushPromises()
|
|
|
|
|
const input = wrapper.get<HTMLInputElement>('input[placeholder="选填"]')
|
|
|
|
|
await input.setValue('UI-修复后保留输入'); await flushPromises()
|
|
|
|
|
input.element.focus(); await input.trigger('keydown', { key: 'Escape', code: 'Escape' }); await flushPromises(); await continueEditing()
|
|
|
|
|
confirms.mockClear()
|
|
|
|
|
router.back(); await flushPromises()
|
|
|
|
|
expect(router.currentRoute.value.name).toBe('overseas-master-data-capacity-offer-detail')
|
|
|
|
|
expect(confirms).toHaveBeenCalledOnce(); expect(wrapper.get('.app-dialog').isVisible()).toBe(true)
|
|
|
|
|
await continueEditing()
|
|
|
|
|
expect(router.currentRoute.value.name).toBe('overseas-master-data-capacity-offer-detail')
|
|
|
|
|
expect(input.element.value).toBe('UI-修复后保留输入'); expect(getCapacityOffer).toHaveBeenCalledExactlyOnceWith('CO-CLOSE')
|
|
|
|
|
confirms.mockClear(); router.back(); await flushPromises()
|
|
|
|
|
const message = [...document.querySelectorAll('.el-message-box')].at(-1)!
|
|
|
|
|
const leave = [...message.querySelectorAll('button')].find(button => button.textContent?.trim() === '放弃并离开') as HTMLButtonElement
|
|
|
|
|
expect(leave).toBeTruthy(); leave.click(); await flushPromises()
|
|
|
|
|
expect(router.currentRoute.value.name).toBe('overseas-master-data-capacity-offers')
|
|
|
|
|
expect(confirms).toHaveBeenCalledOnce(); expect(getCapacityOffer).toHaveBeenCalledExactlyOnceWith('CO-CLOSE')
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 50)); await router.push('/pool/CO-CLOSE'); await flushPromises()
|
|
|
|
|
await wrapper.findAll('button').find(button => button.text() === '登记意向')!.trigger('click'); await flushPromises()
|
|
|
|
|
await wrapper.get<HTMLInputElement>('input[placeholder="选填"]').setValue('另一次修改'); await flushPromises()
|
|
|
|
|
confirms.mockClear(); const navigation = router.push('/outside'); await flushPromises()
|
|
|
|
|
expect(confirms).toHaveBeenCalledOnce(); expect(document.body.textContent).toContain('全局离开确认')
|
|
|
|
|
await continueEditing(); await navigation
|
|
|
|
|
expect(router.currentRoute.value.name).toBe('overseas-master-data-capacity-offer-detail')
|
|
|
|
|
})
|
|
|
|
|