style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user