Files
admin-web/src/modules/customers/customerFeeDialogs.test.ts
T

146 lines
8.1 KiB
TypeScript
Raw Normal View History

import ElementPlus, { ElDialog, ElMessageBox, ElOption } 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 CustomerOverseasStorageFeePanel from './CustomerOverseasStorageFeePanel.vue'
import OverseasStorageFeesView from './pages/OverseasStorageFeesView.vue'
import { getCustomerOverseasStorageFees, listOverseasStorageFees, updateCustomerOverseasStorageFees, type OverseasStorageFeeConfig } from './overseasStorageFee'
vi.mock('./overseasStorageFee', () => ({
getCustomerOverseasStorageFees: vi.fn(),
listOverseasStorageFees: vi.fn(),
updateCustomerOverseasStorageFees: vi.fn(),
}))
const customerId = '9007199254740993'
function configuration(): OverseasStorageFeeConfig {
return { id: 'fee-1', customerId, customerName: '费用测试客户', currency: 'USD', version: 7, createdAt: null, updatedAt: null, tiers: [
{ id: 'storage', feeType: 'STORAGE', billingUnit: 'CBM', startDay: 1, endDay: null, unitPrice: 0.123456, sortNo: 1 },
{ id: 'return', feeType: 'RETURN_STORAGE', billingUnit: 'PALLET', startDay: 1, endDay: null, unitPrice: 2, sortNo: 1 },
] }
}
function button(text: string) {
const found = [...document.querySelectorAll<HTMLButtonElement>('button')].find(item => item.textContent?.trim() === text)
expect(found, text).toBeDefined()
return found!
}
function changeNumber(input: HTMLInputElement, value: string) {
input.value = value
input.dispatchEvent(new Event('input', { bubbles: true }))
input.dispatchEvent(new Event('change', { bubbles: true }))
input.dispatchEvent(new Event('blur', { bubbles: true }))
}
async function open(entry: 'detail' | 'list') {
const wrapper = entry === 'detail'
? mount(CustomerOverseasStorageFeePanel, { attachTo: document.body, props: { customerId, customerName: '费用测试客户' }, global: { plugins: [ElementPlus] } })
: mount(OverseasStorageFeesView, { attachTo: document.body, global: { plugins: [ElementPlus] } })
await flushPromises()
if (entry === 'detail') button('编辑费用').click()
else {
const action = document.querySelector<HTMLButtonElement>('.el-table__body .row-actions button')
expect(action).not.toBeNull()
action!.click()
}
await flushPromises()
return wrapper
}
enableAutoUnmount(afterEach)
afterEach(() => { vi.restoreAllMocks(); ElMessageBox.close() })
beforeEach(() => {
vi.resetAllMocks()
document.body.innerHTML = ''
setActivePinia(createPinia())
const auth = useAuthStore()
auth.token = 'test-session'
auth.permissions = ['platform:customer:list', 'platform:customer:edit', 'platform:overseas-storage-fee:edit']
useAccessPreviewStore().initializeAfterLogin()
vi.mocked(getCustomerOverseasStorageFees).mockImplementation(async () => configuration())
vi.mocked(listOverseasStorageFees).mockImplementation(async () => ({ records: [configuration()], total: 1, page: 1, size: 20 }))
vi.mocked(updateCustomerOverseasStorageFees).mockImplementation(async () => configuration())
})
describe('customer overseas fee dialog entry points', () => {
it.each(['detail', 'list'] as const)('retains %s currency choices, values, version and full customer ID on save', async entry => {
const wrapper = await open(entry)
const dialog = wrapper.findComponent(AppDialog)
expect(dialog.props('title')).toBe(`${entry === 'detail' ? '编辑海外仓费用' : '编辑海外仓阶梯费用'} · 费用测试客户`)
expect(dialog.props('fullscreenable')).toBe(true)
expect(dialog.findAllComponents(ElOption).map(option => option.props('value'))).toEqual(entry === 'detail' ? ['USD', 'CNY', 'HKD', 'EUR', 'GBP'] : ['USD', 'CNY', 'EUR'])
const prices = document.querySelectorAll<HTMLInputElement>('.customer-fee-tier-table input[placeholder^="请输入"]')
expect(prices).toHaveLength(4)
const price = document.querySelector<HTMLInputElement>(entry === 'detail' ? 'input[placeholder="请输入价格"]' : 'input[placeholder="请输入单价"]')!
changeNumber(price, '0.654321')
await flushPromises()
button('新增阶梯').click()
await flushPromises()
expect(document.querySelectorAll('.customer-fee-tier-table tbody tr')).toHaveLength(3)
document.querySelector<HTMLButtonElement>('button[aria-label="展开全屏"]')!.click()
await flushPromises()
expect(document.querySelector('.customer-density-dialog.is-fullscreen')).not.toBeNull()
expect(price.value).toBe('0.654321')
button(entry === 'detail' ? '保存费用' : '确定').click()
await flushPromises()
expect(updateCustomerOverseasStorageFees).toHaveBeenCalledWith(customerId, {
customerId, customerName: '费用测试客户', currency: 'USD', version: 7, tiers: [
{ feeType: 'STORAGE', billingUnit: 'CBM', startDay: 1, endDay: 1, unitPrice: 0.654321 },
{ feeType: 'STORAGE', billingUnit: 'CBM', startDay: 2, endDay: null, unitPrice: 0.654321 },
{ feeType: 'RETURN_STORAGE', billingUnit: 'PALLET', startDay: 1, endDay: null, unitPrice: 2 },
],
})
expect(dialog.props('modelValue')).toBe(false)
})
it('applies dirty confirmation to the detail header close and preserves inputs when declined', async () => {
const wrapper = await open('detail')
const price = document.querySelector<HTMLInputElement>('input[placeholder="请输入价格"]')!
changeNumber(price, '9.123456')
await flushPromises()
const confirmation = vi.spyOn(ElMessageBox, 'confirm').mockRejectedValueOnce('cancel')
document.querySelector<HTMLButtonElement>('.customer-density-dialog .el-dialog__headerbtn')!.click()
await flushPromises()
expect(confirmation).toHaveBeenCalledWith(expect.stringContaining('尚未保存'), '放弃编辑 · 费用测试客户', expect.objectContaining({ customClass: 'admin-density-confirm' }))
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(true)
expect(price.value).toBe('9.123456')
const done = vi.fn()
const closing = wrapper.findComponent(AppDialog).props('beforeClose')!(done)
await flushPromises()
Array.from(document.querySelectorAll<HTMLButtonElement>('.el-message-box .el-button--primary')).at(-1)!.click()
await closing
expect(done).toHaveBeenCalledOnce()
button('取消').click()
await flushPromises()
Array.from(document.querySelectorAll<HTMLButtonElement>('.el-message-box .el-button--primary')).at(-1)!.click()
await flushPromises()
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(false)
expect(updateCustomerOverseasStorageFees).not.toHaveBeenCalled()
})
it.each(['detail', 'list'] as const)('keeps %s inputs and context when saving fails', async entry => {
vi.mocked(updateCustomerOverseasStorageFees).mockRejectedValueOnce(new Error('费用版本冲突'))
const wrapper = await open(entry)
button(entry === 'detail' ? '保存费用' : '确定').click()
await flushPromises()
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(true)
expect(document.body.textContent).toContain(entry === 'detail' ? '海外仓费用保存失败,请刷新后重试' : '保存失败,请刷新后重试')
expect(document.querySelectorAll('.customer-fee-tier-table tbody tr')).toHaveLength(2)
})
it.each(['detail', 'list'] as const)('locks %s close actions during save', async entry => {
let finish!: (value: OverseasStorageFeeConfig) => void
vi.mocked(updateCustomerOverseasStorageFees).mockReturnValueOnce(new Promise(resolve => { finish = resolve }))
const wrapper = await open(entry)
button(entry === 'detail' ? '保存费用' : '确定').click()
await flushPromises()
expect(wrapper.findComponent(AppDialog).props('confirmLoading')).toBe(true)
expect(wrapper.findComponent(ElDialog).props('showClose')).toBe(false)
expect(wrapper.findComponent(ElDialog).props('closeOnPressEscape')).toBe(false)
finish(configuration())
await flushPromises()
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(false)
})
})