Files
admin-web/src/modules/suppliers/components/SupplierDialogs.test.ts
T

167 lines
9.7 KiB
TypeScript

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<typeof import('vue-router')>(),
...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<HTMLButtonElement>('.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<HTMLButtonElement>('.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<HTMLButtonElement>('.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<HTMLButtonElement>('.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)
})
})