167 lines
9.7 KiB
TypeScript
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)
|
|
})
|
|
})
|