Files
admin-web/src/modules/warehouse-resources/pallets/pages/PalletsView.test.ts
T
2026-08-04 21:01:39 +08:00

318 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import ElementPlus from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import { listPlatformTenants as listPlatformCustomers, type PlatformTenantResource as PlatformCustomerResource } from '@/modules/iam'
import { listPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
import DataTable from '@/shared/components/DataTable.vue'
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
import {
createPlatformPallet,
getPlatformPallet,
listPlatformPallets,
type PlatformPalletResource,
} from '../api/platformPallets'
import PalletsView from './PalletsView.vue'
vi.mock('@/modules/iam', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/modules/iam')>()
const listPlatformTenants = vi.fn<typeof actual.listPlatformTenants>()
return { ...actual, listPlatformTenants }
})
vi.mock('@/modules/warehouses', () => ({ listPlatformWarehouses: vi.fn() }))
vi.mock('../api/platformPallets', () => ({
listPlatformPallets: vi.fn(),
getPlatformPallet: vi.fn(),
createPlatformPallet: vi.fn(),
updatePlatformPallet: vi.fn(),
deletePlatformPallet: vi.fn(),
}))
class ResizeObserverStub implements ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}
function createMemoryStorage(): Storage {
const values = new Map<string, string>()
return {
get length() { return values.size },
clear: () => values.clear(),
getItem: (key) => values.get(key) ?? null,
key: (index) => [...values.keys()][index] ?? null,
removeItem: (key) => values.delete(key),
setItem: (key, value) => values.set(key, value),
}
}
const customer = (id: number | string, tenantName: string): PlatformCustomerResource => ({
id, tenantCode: String(id), tenantName, status: 'ACTIVE', legalName: tenantName,
legalRepresentative: null, unifiedSocialCreditCode: null, contactName: null,
contactPhone: null, contactEmail: null, servicePackage: null, contactWechat: null,
salesOwnerName: null, paymentTermDays: null, enterpriseType: null, establishedDate: null,
registeredCapital: null, registeredCapitalCurrency: null, registeredAddress: null,
internalRemark: null, createdAt: null, approvedAt: null,
})
const warehouse: PlatformWarehouseResource = {
id: '19001', tenantId: 10001, warehouseCode: 'CN-YW-01', warehouseName: '义乌收货仓',
warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai', contactName: null,
contactPhone: null, contactEmail: null, status: 'ACTIVE', remark: null,
}
const pallet: PlatformPalletResource = {
id: '20001', tenantId: 10001, warehouseId: '19001', palletCode: 'TP-001', palletType: '木托盘',
capacityCbm: '1.200000', remark: '标准川字托盘', createdAt: '2026-08-04T09:00:00', updatedAt: '2026-08-04T10:00:00',
}
async function mountView(permissions = [
'platform:pallet:list', 'platform:pallet:create', 'platform:pallet:edit', 'platform:pallet:delete',
]) {
vi.stubGlobal('localStorage', createMemoryStorage())
vi.stubGlobal('sessionStorage', createMemoryStorage())
const pinia = createPinia()
setActivePinia(pinia)
const auth = useAuthStore()
auth.token = 'token'
auth.permissions = permissions
useAccessPreviewStore().initializeAfterLogin()
const wrapper = mount(PalletsView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
await flushPromises()
return wrapper
}
async function setFilterContext(wrapper: VueWrapper, customerId = '10001', warehouseId = '') {
wrapper.findComponent(QueryToolbar).vm.$emit('update:modelValue', {
customerId,
warehouseId: '',
palletCode: '',
palletType: '',
})
await flushPromises()
if (!warehouseId) return
wrapper.findComponent(QueryToolbar).vm.$emit('update:modelValue', {
customerId,
warehouseId,
palletCode: '',
palletType: '',
})
await flushPromises()
}
async function loadRows(wrapper: VueWrapper, warehouseId = '') {
await setFilterContext(wrapper, '10001', warehouseId)
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
customerId: '10001',
warehouseId,
palletCode: '',
palletType: '',
})
await flushPromises()
}
enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('PalletsView', () => {
beforeEach(() => {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
vi.mocked(listPlatformCustomers).mockReset().mockResolvedValue({
records: [customer(10001, '默认企业'), customer('9223372036854775806', '雪花企业')], page: 1, size: 200, total: 2, pages: 1,
})
vi.mocked(listPlatformWarehouses).mockReset().mockResolvedValue({ records: [warehouse], page: 1, size: 200, total: 1, pages: 1 })
vi.mocked(listPlatformPallets).mockReset().mockResolvedValue({ records: [pallet], page: 1, size: 20, total: 1, pages: 1 })
vi.mocked(getPlatformPallet).mockReset().mockResolvedValue(pallet)
vi.mocked(createPlatformPallet).mockReset().mockResolvedValue('20002')
})
it('starts with all customers and uses compact semantic filter widths', async () => {
const wrapper = await mountView()
const toolbar = wrapper.findComponent(QueryToolbar)
const fields = toolbar.props('fields')
expect(toolbar.props('modelValue')).toMatchObject({ customerId: '', warehouseId: '' })
expect(fields.map((field) => [field.key, field.width])).toEqual([
['customerId', 260],
['warehouseId', 240],
['palletCode', 240],
['palletType', 180],
])
expect(wrapper.find('.pallet-filter-toolbar').exists()).toBe(true)
expect(fields.find((field) => field.key === 'customerId')?.placeholder).toBe('全部客户')
expect(listPlatformWarehouses).not.toHaveBeenCalled()
expect(listPlatformPallets).toHaveBeenCalledWith({
page: 1, size: 20,
})
})
it('loads warehouses after customer selection and omits an empty warehouse id when searching', async () => {
const wrapper = await mountView()
await loadRows(wrapper)
expect(listPlatformWarehouses).toHaveBeenCalledWith(10001, 1, 200)
expect(listPlatformPallets).toHaveBeenCalledWith({
page: 1, size: 20, tenantId: 10001,
})
expect(wrapper.text()).toContain('TP-001')
expect(wrapper.text()).toContain('木托盘')
expect(wrapper.text()).toContain('1.2 m³')
expect(wrapper.text()).toContain('义乌收货仓')
expect(wrapper.text()).toContain('托盘列表')
expect(wrapper.text()).not.toContain('启停托盘')
expect(wrapper.text()).not.toContain('全部状态')
expect(wrapper.text()).toContain('全部托盘类型')
expect(wrapper.find('input[aria-label="所属企业"]').exists()).toBe(true)
expect(wrapper.find('input[aria-label="所属仓库"]').exists()).toBe(true)
expect(wrapper.find('.platform-tenant-selector').exists()).toBe(false)
expect(wrapper.text()).not.toContain('租户')
expect(wrapper.find('.data-table').exists()).toBe(true)
expect(wrapper.find('.table-actions.pallet-row-actions').exists()).toBe(true)
expect(wrapper.findAll('thead th').map((item) => item.text().trim())[0]).toBe('操作')
})
it('queries all customers explicitly without adding a customer id', async () => {
vi.mocked(listPlatformPallets).mockResolvedValue({ records: [pallet], page: 1, size: 20, total: 41, pages: 3 })
const wrapper = await mountView()
const toolbar = wrapper.findComponent(QueryToolbar)
toolbar.vm.$emit('search', { customerId: '', warehouseId: '', palletCode: '', palletType: '' })
await flushPromises()
expect(listPlatformPallets).toHaveBeenLastCalledWith({
page: 1, size: 20,
})
})
it('resets customer and dependent warehouse filters without sending an empty id', async () => {
const wrapper = await mountView()
await setFilterContext(wrapper, '10001', '19001')
const toolbar = wrapper.findComponent(QueryToolbar)
toolbar.vm.reset()
await flushPromises()
expect(toolbar.props('modelValue')).toMatchObject({ customerId: '', warehouseId: '' })
expect(listPlatformPallets).toHaveBeenCalledTimes(2)
expect(listPlatformPallets).toHaveBeenLastCalledWith({
page: 1, size: 20,
})
expect(toolbar.props('fields').find((field) => field.key === 'customerId')?.placeholder).toBe('全部客户')
})
it('preserves a snowflake customer id when switching the enterprise filter', async () => {
const wrapper = await mountView()
const snowflakeId = '9223372036854775806'
wrapper.findComponent(QueryToolbar).vm.$emit('update:modelValue', {
customerId: snowflakeId,
warehouseId: '',
palletCode: '',
palletType: '',
})
await flushPromises()
expect(listPlatformWarehouses).toHaveBeenLastCalledWith(snowflakeId, 1, 200)
})
it('applies pallet filters only after search', async () => {
const wrapper = await mountView()
await setFilterContext(wrapper, '10001', '19001')
await wrapper.find('input[placeholder="托盘编码(模糊查询)"]').setValue('TP-00')
expect(listPlatformPallets).toHaveBeenCalledTimes(1)
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
customerId: '10001', warehouseId: '19001', palletCode: 'TP-00', palletType: '木托盘',
})
await flushPromises()
expect(listPlatformPallets).toHaveBeenLastCalledWith({
page: 1, size: 20, tenantId: 10001, warehouseId: 19001, palletCode: 'TP-00', palletType: '木托盘',
})
})
it('creates a pallet in the selected warehouse with the real request fields', async () => {
const wrapper = await mountView()
await setFilterContext(wrapper, '10001', '19001')
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增托盘')
await createButton?.trigger('click')
await flushPromises()
await wrapper.find('input[placeholder="例如 TP-001"]').setValue('TP-002')
const palletTypeItem = wrapper.findAll('.el-form-item').find((item) => item.text().includes('托盘类型'))
palletTypeItem?.findComponent({ name: 'ElSelect' }).vm.$emit('update:modelValue', '塑料托盘')
const capacity = wrapper.findComponent({ name: 'ElInputNumber' })
capacity.vm.$emit('update:modelValue', 1.5)
await wrapper.find('textarea[placeholder="可填写规格、材质或现场识别说明"]').setValue('蓝色标准托盘')
const saveButton = wrapper.findAll('button').find((button) => button.text().trim() === '保存托盘')
await saveButton?.trigger('click')
await flushPromises()
expect(createPlatformPallet).toHaveBeenCalledWith(10001, {
warehouseId: 19001, palletCode: 'TP-002', palletType: '塑料托盘', capacityCbm: 1.5, remark: '蓝色标准托盘',
})
})
it('hides all write actions without write permissions', async () => {
const wrapper = await mountView(['platform:pallet:list'])
expect(wrapper.find('.panel-heading button').exists()).toBe(false)
const actions = wrapper.find('.pallet-row-actions').text()
expect(actions).not.toContain('编辑')
expect(actions).not.toContain('删除')
expect(actions).toContain('查看')
})
it('shows a recoverable list failure instead of demo data', async () => {
vi.mocked(listPlatformPallets).mockRejectedValueOnce(new Error('Gateway unavailable'))
const wrapper = await mountView()
expect(wrapper.text()).toContain('托盘列表暂时无法加载')
expect(wrapper.text()).toContain('Gateway unavailable')
expect(wrapper.text()).toContain('重新加载')
expect(wrapper.text()).not.toContain('TP-001')
})
it('stops dependent requests when the enterprise filter cannot load', async () => {
vi.mocked(listPlatformCustomers).mockRejectedValueOnce(new Error('Gateway unavailable'))
const wrapper = await mountView()
expect(wrapper.text()).toContain('所属企业列表暂时无法加载')
expect(wrapper.text()).toContain('重新加载')
expect(listPlatformWarehouses).not.toHaveBeenCalled()
expect(listPlatformPallets).toHaveBeenCalledWith({
page: 1, size: 20,
})
})
it('shows only the pallet fields exposed by the backend contract', async () => {
const crossCustomerPallet = { ...pallet, tenantId: '9223372036854775806' }
vi.mocked(listPlatformPallets).mockResolvedValue({ records: [crossCustomerPallet], page: 1, size: 20, total: 1, pages: 1 })
vi.mocked(getPlatformPallet).mockResolvedValue(crossCustomerPallet)
const wrapper = await mountView()
await loadRows(wrapper)
expect(wrapper.findComponent(DataTable).props('data')).toEqual([crossCustomerPallet])
const tableBody = wrapper.find('.el-table__body-wrapper')
const viewButton = tableBody.findAll('.pallet-row-actions button').find((button) => button.text().trim() === '查看')
await viewButton?.trigger('click')
await flushPromises()
expect(getPlatformPallet).toHaveBeenCalledWith('9223372036854775806', 19001, '20001')
expect(document.body.textContent).toContain('所属企业')
expect(document.body.textContent).toContain('雪花企业(9223372036854775806)')
expect(document.body.textContent).toContain('托盘编码')
expect(document.body.textContent).toContain('托盘类型')
expect(document.body.textContent).toContain('托盘容积')
expect(document.body.textContent).toContain('归属信息')
expect(document.body.textContent).toContain('托盘规格')
expect(document.body.textContent).toContain('记录信息')
expect(document.body.textContent).not.toContain('企业 ID')
expect(document.body.textContent).not.toContain('租户')
expect(document.body.textContent).not.toContain('托盘状态')
expect(document.body.textContent).not.toContain('最大承重')
})
})