Files
admin-web/src/modules/suppliers/pages/SupplierRouteReuse.behavior.test.ts
T

88 lines
4.7 KiB
TypeScript
Raw Normal View History

2026-08-14 14:27:03 +08:00
import { flushPromises, mount } from '@vue/test-utils'
import { reactive, nextTick } from 'vue'
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import ElementPlus from 'element-plus'
import type { Component } from 'vue'
import type { Supplier } from '@/modules/suppliers/types'
const mocks = vi.hoisted(() => ({
route: null as null | { params: { id: string } },
push: vi.fn(), getSupplier: vi.fn(), updateSupplier: vi.fn(),
can: vi.fn((permission: string) => permission === 'platform:supplier:list' || permission === 'platform:supplier:edit' || permission === 'platform:supplier:create'),
}))
const routeState = reactive({ params: { id: 'A' } })
mocks.route = routeState
let SupplierFormView: Component
let SupplierDetailView: Component
vi.mock('vue-router', () => ({ useRoute: () => mocks.route, useRouter: () => ({ push: mocks.push }) }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: mocks.can }) }))
vi.mock('@/core/unsaved-work/useUnsavedWorkRegistration', () => ({ useUnsavedWorkRegistration: () => ({ captureBaseline: vi.fn() }) }))
vi.mock('@/modules/suppliers/api/platformSuppliers', () => ({ getSupplier: mocks.getSupplier, updateSupplier: mocks.updateSupplier, createSupplier: vi.fn() }))
function supplier(id: string, name: string): Supplier {
2026-08-17 16:45:46 +08:00
return { id, tenantId: '0', name, category: 'BOOKING', region: '宁波', legalRepresentative: '法人', contactName: '联系人', phone: '13800000000', email: null, address: null, businessLicenseNo: null, licenseFileId: null, status: 'ACTIVE', settlementPeriod: 'MONTHLY_30', remark: null, createdBy: null, createdAt: '', updatedBy: null, updatedAt: '', deleted: false, version: 1 }
2026-08-14 14:27:03 +08:00
}
function deferred<T>() {
let resolve!: (value: T) => void
const promise = new Promise<T>((next) => { resolve = next })
return { promise, resolve }
}
const stubs = {
AdminPage: { template: '<main><slot /></main>' },
DetailPageLayout: { template: '<article><slot name="header"/><slot /></article>' },
FormSection: { template: '<section><slot /></section>' },
ApiErrorMeta: { template: '<span />' },
LoadErrorState: { template: '<div class="load-error"><slot name="details" /></div>' },
SupplierLicenseDialog: { template: '<div />' },
}
describe('supplier route reuse', () => {
beforeAll(async () => {
SupplierFormView = (await import('@/modules/suppliers/pages/SupplierFormView.vue')).default
SupplierDetailView = (await import('@/modules/suppliers/pages/SupplierDetailView.vue')).default
})
beforeEach(() => {
routeState.params.id = 'A'
mocks.push.mockReset(); mocks.getSupplier.mockReset(); mocks.updateSupplier.mockReset().mockResolvedValue(null)
mocks.can.mockClear()
})
it('reloads form A to B, ignores late A, and submits B', async () => {
const requestA = deferred<Supplier>(); const requestB = deferred<Supplier>()
mocks.getSupplier.mockImplementation((id: string) => id === 'A' ? requestA.promise : requestB.promise)
const wrapper = mount(SupplierFormView, { global: { plugins: [ElementPlus], stubs } })
expect(mocks.getSupplier).toHaveBeenCalledWith('A')
routeState.params.id = 'B'
await nextTick()
expect(mocks.getSupplier).toHaveBeenLastCalledWith('B')
requestB.resolve(supplier('B', '供应商B')); await flushPromises()
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('供应商B')
requestA.resolve(supplier('A', '供应商A')); await flushPromises()
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('供应商B')
expect((wrapper.find('input').element as HTMLInputElement).value).not.toBe('供应商A')
const saveButton = wrapper.findAll('button').find((button) => button.text().includes('保存'))
expect(saveButton).toBeDefined()
await saveButton!.trigger('click')
await flushPromises()
expect(mocks.updateSupplier).toHaveBeenCalledWith('B', expect.objectContaining({ name: '供应商B' }))
expect(mocks.push).toHaveBeenCalledWith({ name: 'supplier-list' })
})
it('reloads detail A to B when the route is reused', async () => {
const requestA = deferred<Supplier>(); const requestB = deferred<Supplier>()
mocks.getSupplier.mockImplementation((id: string) => id === 'A' ? requestA.promise : requestB.promise)
const wrapper = mount(SupplierDetailView, { global: { plugins: [ElementPlus], stubs } })
routeState.params.id = 'B'; await nextTick()
requestB.resolve(supplier('B', '供应商B')); await flushPromises()
expect(wrapper.text()).toContain('供应商B')
requestA.resolve(supplier('A', '供应商A')); await flushPromises()
expect(wrapper.text()).toContain('供应商B')
expect(wrapper.text()).not.toContain('供应商A')
})
})