146 lines
8.1 KiB
TypeScript
146 lines
8.1 KiB
TypeScript
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)
|
||
|
|
})
|
||
|
|
})
|