import ElementPlus, { ElMessageBox } from 'element-plus' import { createPinia, setActivePinia } from 'pinia' import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useAuthStore } from '@/core/auth/auth' import { useAccessPreviewStore } from '@/core/access/accessPreview' import AppDialog from '@/shared/components/AppDialog.vue' import SupplierLicenseDialog from './SupplierLicenseDialog.vue' import SuppliersView from '../pages/SuppliersView.vue' import SupplierDetailView from '../pages/SupplierDetailView.vue' import { changeSupplierStatus, getSupplier, getSupplierStats, listSuppliers } from '../api/platformSuppliers' import type { Supplier } from '../types' const { routerPush, routeControl } = vi.hoisted(() => ({ routerPush: vi.fn(), routeControl: { setId: (_id: string) => {} } })) vi.mock('vue-router', async importOriginal => ({ ...await importOriginal(), ...await (async () => { const { reactive } = await import('vue') const route = reactive({ params: { id: '' } }) routeControl.setId = id => { route.params.id = id } return { useRoute: () => route } })(), useRouter: () => ({ push: routerPush }), })) vi.mock('../api/platformSuppliers', () => ({ getSupplier: vi.fn(), getSupplierStats: vi.fn(), listSuppliers: vi.fn(), changeSupplierStatus: vi.fn() })) const privateId = '9007199254740993123' let record: Supplier function supplier(): Supplier { return { id: privateId, tenantId: '0', name: '供应商测试公司', category: 'BOOKING', truckingScope: null, carrierCodes: ['MATSON'], region: '宁波', legalRepresentative: '测试法人', contactName: '联系人', phone: '13800000000', email: null, address: '供应商测试地址', businessLicenseNo: '91440300MA5ABCDE12', licenseFileId: 'private-file-id', status: 'ACTIVE', settlementPeriod: 'MONTHLY_30', remark: null, createdBy: null, createdAt: '', updatedBy: null, updatedAt: '', deleted: false, version: 2 } } function rowButton(text: string) { const found = Array.from(document.querySelectorAll('.el-table__body button')).find(button => button.textContent?.trim() === text) expect(found, text).toBeDefined() return found! } function messageBoxButton(text: string) { const found = Array.from(document.querySelectorAll('.el-message-box button')).find(button => button.textContent?.trim() === text) expect(found, text).toBeDefined() return found! } async function openList() { const wrapper = mount(SuppliersView, { attachTo: document.body, global: { plugins: [ElementPlus] } }) await flushPromises() return wrapper } enableAutoUnmount(afterEach) afterEach(() => { ElMessageBox.close() }) beforeEach(() => { vi.resetAllMocks() document.body.innerHTML = '' setActivePinia(createPinia()) const auth = useAuthStore() auth.token = 'test-session' auth.permissions = ['platform:supplier:list', 'platform:supplier:edit', 'platform:supplier:create', 'platform:supplier:status'] useAccessPreviewStore().initializeAfterLogin() record = supplier() routeControl.setId(privateId) vi.mocked(getSupplier).mockImplementation(async () => record) vi.mocked(getSupplierStats).mockResolvedValue({ booking: 1, customs: 0, trucking: 0, certificate: 0 }) vi.mocked(listSuppliers).mockImplementation(async () => ({ records: [record], page: 1, size: 20, total: 1, pages: 1 })) vi.mocked(changeSupplierStatus).mockImplementation(async (_id, status) => { record = { ...record, status }; return null }) }) describe('supplier shared dialogs', () => { it('shows all linked license facts with a truthful image limitation and no synthetic certificate', async () => { const name = '供应商完整长名称'.repeat(12) const wrapper = mount(SupplierLicenseDialog, { attachTo: document.body, props: { modelValue: true, supplier: { ...record, name } }, global: { plugins: [ElementPlus] } }) await flushPromises() const dialog = wrapper.findComponent(AppDialog) expect(dialog.props()).toMatchObject({ title: `营业执照查看 · ${name}`, width: 'min(560px, calc(100vw - 24px))', showConfirm: false, cancelText: '关闭' }) const facts = document.querySelector('[aria-label="营业执照信息"] table')! expect(facts.querySelectorAll('tr')).toHaveLength(4) expect(facts.textContent).toContain(name) expect(facts.textContent).toContain('测试法人') expect(facts.textContent).toContain(record.businessLicenseNo) expect(facts.textContent).toContain('已关联') expect(facts.textContent).not.toContain(privateId) expect(facts.textContent).not.toContain('private-file-id') expect(document.querySelector('.el-dialog [role="alert"]')?.textContent).toContain('暂无法展示原始影像') expect(document.querySelector('.el-dialog img, .el-dialog iframe')).toBeNull() expect(document.querySelector('.license-card')).toBeNull() expect(document.querySelector('.el-dialog')?.textContent).not.toContain('副本') }) it('keeps missing facts explicit and handles an absent supplier without an ID fallback', async () => { const wrapper = mount(SupplierLicenseDialog, { attachTo: document.body, props: { modelValue: true, supplier: { ...record, name: '', legalRepresentative: '', businessLicenseNo: null, licenseFileId: null } }, global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.findComponent(AppDialog).props('title')).toBe('营业执照查看') expect(document.querySelector('[aria-label="营业执照信息"] table')?.textContent).toContain('未上传') expect(document.querySelectorAll('.license-info-table td')[2]?.textContent).toBe('—') expect(document.querySelector('.el-dialog [role="alert"]')).toBeNull() await wrapper.setProps({ supplier: null }); await flushPromises() expect(document.querySelector('.el-dialog')?.textContent).toContain('暂无营业执照资料') expect(document.querySelector('.license-info-table')).toBeNull() }) it('opens the list license entry and closes through its original footer', async () => { const wrapper = await openList() rowButton('查看').click(); await flushPromises() const dialog = wrapper.findComponent(AppDialog) expect(dialog.props('modelValue')).toBe(true) expect(document.querySelector('.license-info-table')?.textContent).toContain(record.businessLicenseNo) document.querySelector('.el-dialog__footer button')!.click(); await flushPromises() expect(dialog.props('modelValue')).toBe(false) expect(changeSupplierStatus).not.toHaveBeenCalled() }) it('closes an existing license preview when the detail route changes', async () => { vi.mocked(getSupplier).mockImplementation(async id => id === privateId ? record : { ...record, id, name: '另一供应商' }) const wrapper = mount(SupplierDetailView, { attachTo: document.body, global: { plugins: [ElementPlus] } }) await flushPromises() const preview = wrapper.findAll('button').find(button => button.text().includes('查看营业执照'))! await preview.trigger('click'); await flushPromises() expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(true) routeControl.setId('another-supplier'); await flushPromises() expect(getSupplier).toHaveBeenLastCalledWith('another-supplier') expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(false) expect(wrapper.get('.density-detail-title').text()).toBe('另一供应商') }) it.each(['ACTIVE', 'INACTIVE'] as const)('identifies the %s status confirmation and sends the original status request only after confirmation', async status => { record = { ...record, status } const verb = status === 'ACTIVE' ? '停用' : '启用' const next = status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE' await openList() rowButton(verb).click(); await flushPromises() expect(document.querySelector('.el-message-box__title')?.textContent).toBe(`${verb}供应商 · 供应商测试公司`) expect(document.querySelector('.el-message-box')?.classList.contains('admin-density-confirm')).toBe(true) expect(changeSupplierStatus).not.toHaveBeenCalled() messageBoxButton(`确认${verb}`).click(); await flushPromises() expect(changeSupplierStatus).toHaveBeenCalledWith(privateId, next) expect(listSuppliers).toHaveBeenCalledTimes(2) }) it('cancels a status change without sending a write request', async () => { await openList() rowButton('停用').click(); await flushPromises() messageBoxButton('取消').click(); await flushPromises() expect(changeSupplierStatus).not.toHaveBeenCalled() expect(listSuppliers).toHaveBeenCalledTimes(1) }) it('keeps status actions hidden when the current account has only read permission', async () => { useAuthStore().permissions = ['platform:supplier:list'] useAccessPreviewStore().initializeAfterLogin() await openList() const labels = Array.from(document.querySelectorAll('.el-table__body button')).map(button => button.textContent?.trim()) expect(labels).not.toContain('停用') expect(labels).not.toContain('启用') expect(labels).not.toContain('编辑') expect(labels).toContain('查看') expect(changeSupplierStatus).not.toHaveBeenCalled() }) it('retains the list and resets action loading if the status update fails', async () => { vi.mocked(changeSupplierStatus).mockRejectedValueOnce(new Error('network')) await openList() rowButton('停用').click(); await flushPromises() messageBoxButton('确认停用').click(); await flushPromises() expect(changeSupplierStatus).toHaveBeenCalledWith(privateId, 'INACTIVE') expect(rowButton('停用').classList.contains('is-loading')).toBe(false) expect(document.body.textContent).toContain('停用供应商失败') expect(listSuppliers).toHaveBeenCalledTimes(1) }) })