Files
admin-web/src/modules/warehouse-resources/pallets/pages/PalletsView.test.ts
T

341 lines
15 KiB
TypeScript
Raw Normal View History

2026-08-04 21:01:39 +08:00
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'
2026-08-06 14:55:17 +08:00
import { listAllPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
2026-08-04 21:01:39 +08:00
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'
2026-08-06 14:55:17 +08:00
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
2026-08-04 21:01:39 +08:00
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 warehouse: PlatformWarehouseResource = {
2026-08-06 13:56:04 +08:00
id: '19001', tenantId: 0, warehouseCode: 'CN-YW-01', warehouseName: '义乌收货仓',
2026-08-04 21:01:39 +08:00
warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai', contactName: null,
contactPhone: null, contactEmail: null, status: 'ACTIVE', remark: null,
}
2026-08-06 14:55:17 +08:00
const fbaWarehouse: PlatformWarehouseResource = {
...warehouse,
id: '19002',
warehouseCode: 'FBA-ONT8',
warehouseName: 'ONT8 FBA 仓',
warehouseType: 'FBA',
}
const overseasWarehouse: PlatformWarehouseResource = {
...warehouse,
id: '19003',
warehouseCode: 'US-LAX-01',
warehouseName: '洛杉矶海外仓',
warehouseType: 'OVERSEAS',
}
2026-08-04 21:01:39 +08:00
const pallet: PlatformPalletResource = {
2026-08-06 13:56:04 +08:00
id: '20001', tenantId: 0, warehouseId: '19001', palletCode: 'TP-001', palletType: '木托盘',
2026-08-06 14:55:17 +08:00
capacityCbm: '1.200000', weightKg: '8.000', color: '黄色', remark: '标准川字托盘', createdAt: '2026-08-04T09:00:00', updatedAt: '2026-08-04T10:00:00',
2026-08-04 21:01:39 +08: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
}
2026-08-05 15:54:59 +08:00
async function setWarehouseFilter(wrapper: VueWrapper, warehouseId = '') {
2026-08-04 21:01:39 +08:00
wrapper.findComponent(QueryToolbar).vm.$emit('update:modelValue', {
warehouseId,
palletCode: '',
palletType: '',
})
await flushPromises()
}
async function loadRows(wrapper: VueWrapper, warehouseId = '') {
2026-08-05 15:54:59 +08:00
await setWarehouseFilter(wrapper, warehouseId)
2026-08-04 21:01:39 +08:00
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
warehouseId,
palletCode: '',
palletType: '',
})
await flushPromises()
}
enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('PalletsView', () => {
beforeEach(() => {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
2026-08-06 14:55:17 +08:00
vi.mocked(listAllPlatformWarehouses).mockReset().mockResolvedValue([warehouse, fbaWarehouse, overseasWarehouse])
2026-08-04 21:01:39 +08:00
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')
})
2026-08-06 14:55:17 +08:00
it('starts with all warehouse candidates for querying using compact filters', async () => {
2026-08-04 21:01:39 +08:00
const wrapper = await mountView()
const toolbar = wrapper.findComponent(QueryToolbar)
const fields = toolbar.props('fields')
2026-08-05 15:54:59 +08:00
expect(toolbar.props('modelValue')).toMatchObject({ warehouseId: '' })
2026-08-04 21:01:39 +08:00
expect(fields.map((field) => [field.key, field.width])).toEqual([
2026-08-05 15:54:59 +08:00
['warehouseId', 300],
2026-08-04 21:01:39 +08:00
['palletCode', 240],
['palletType', 180],
])
expect(wrapper.find('.pallet-filter-toolbar').exists()).toBe(true)
2026-08-05 15:54:59 +08:00
expect(fields.find((field) => field.key === 'warehouseId')?.placeholder).toBe('全部仓库')
2026-08-06 14:55:17 +08:00
expect(fields.find((field) => field.key === 'warehouseId')?.options).toEqual([
{ label: '义乌收货仓(CN-YW-01)', value: '19001' },
{ label: 'ONT8 FBA 仓(FBA-ONT8)', value: '19002' },
{ label: '洛杉矶海外仓(US-LAX-01)', value: '19003' },
])
expect(listAllPlatformWarehouses).toHaveBeenCalledWith()
2026-08-04 21:01:39 +08:00
expect(listPlatformPallets).toHaveBeenCalledWith({
page: 1, size: 20,
})
})
2026-08-05 15:54:59 +08:00
it('loads all warehouses independently and omits an empty warehouse id when searching', async () => {
2026-08-04 21:01:39 +08:00
const wrapper = await mountView()
await loadRows(wrapper)
2026-08-06 14:55:17 +08:00
expect(listAllPlatformWarehouses).toHaveBeenCalledTimes(1)
expect(listAllPlatformWarehouses).toHaveBeenCalledWith()
2026-08-04 21:01:39 +08:00
expect(listPlatformPallets).toHaveBeenCalledWith({
2026-08-05 15:54:59 +08:00
page: 1, size: 20,
2026-08-04 21:01:39 +08:00
})
expect(wrapper.text()).toContain('TP-001')
expect(wrapper.text()).toContain('木托盘')
expect(wrapper.text()).toContain('1.2 m³')
2026-08-06 14:55:33 +08:00
expect(wrapper.text()).toContain('8 kg')
expect(wrapper.text()).toContain('黄色')
expect(wrapper.text()).not.toContain('更新时间')
2026-08-04 21:01:39 +08:00
expect(wrapper.text()).toContain('托盘列表')
expect(wrapper.text()).not.toContain('启停托盘')
expect(wrapper.text()).not.toContain('全部状态')
expect(wrapper.text()).toContain('全部托盘类型')
2026-08-05 15:54:59 +08:00
expect(wrapper.find('input[aria-label="客户"]').exists()).toBe(false)
2026-08-04 21:01:39 +08:00
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)
2026-08-05 15:54:59 +08:00
toolbar.vm.$emit('search', { warehouseId: '', palletCode: '', palletType: '' })
2026-08-04 21:01:39 +08:00
await flushPromises()
expect(listPlatformPallets).toHaveBeenLastCalledWith({
page: 1, size: 20,
})
})
2026-08-05 15:54:59 +08:00
it('resets the warehouse filter without sending an empty id', async () => {
2026-08-04 21:01:39 +08:00
const wrapper = await mountView()
2026-08-05 15:54:59 +08:00
await setWarehouseFilter(wrapper, '19001')
2026-08-04 21:01:39 +08:00
const toolbar = wrapper.findComponent(QueryToolbar)
toolbar.vm.reset()
await flushPromises()
2026-08-05 15:54:59 +08:00
expect(toolbar.props('modelValue')).toMatchObject({ warehouseId: '' })
2026-08-04 21:01:39 +08:00
expect(listPlatformPallets).toHaveBeenCalledTimes(2)
expect(listPlatformPallets).toHaveBeenLastCalledWith({
page: 1, size: 20,
})
2026-08-05 15:54:59 +08:00
expect(toolbar.props('fields').find((field) => field.key === 'warehouseId')?.placeholder).toBe('全部仓库')
2026-08-04 21:01:39 +08:00
})
2026-08-05 15:54:59 +08:00
it('does not expose customer ownership in warehouse choices', async () => {
2026-08-04 21:01:39 +08:00
const wrapper = await mountView()
2026-08-05 15:54:59 +08:00
const warehouseField = wrapper.findComponent(QueryToolbar).props('fields').find((field) => field.key === 'warehouseId')
2026-08-04 21:01:39 +08:00
2026-08-06 14:55:17 +08:00
expect(listAllPlatformWarehouses).toHaveBeenCalledTimes(1)
expect(warehouseField?.options).toEqual([
{ label: '义乌收货仓(CN-YW-01)', value: '19001' },
{ label: 'ONT8 FBA 仓(FBA-ONT8)', value: '19002' },
{ label: '洛杉矶海外仓(US-LAX-01)', value: '19003' },
])
})
it('does not offer editing for a pallet outside a domestic warehouse', async () => {
vi.mocked(listPlatformPallets).mockResolvedValue({
records: [{ ...pallet, id: '20002', warehouseId: fbaWarehouse.id }, pallet], page: 1, size: 20, total: 2, pages: 1,
})
const wrapper = await mountView()
const bodyRows = wrapper.findAll('.el-table__body-wrapper tbody tr')
expect(bodyRows[0]?.text()).toContain('查看')
expect(bodyRows[0]?.text()).not.toContain('编辑')
expect(bodyRows[1]?.text()).toContain('编辑')
2026-08-04 21:01:39 +08:00
})
it('applies pallet filters only after search', async () => {
const wrapper = await mountView()
2026-08-05 15:54:59 +08:00
await setWarehouseFilter(wrapper, '19001')
2026-08-04 21:01:39 +08:00
await wrapper.find('input[placeholder="托盘编码(模糊查询)"]').setValue('TP-00')
expect(listPlatformPallets).toHaveBeenCalledTimes(1)
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
2026-08-05 15:54:59 +08:00
warehouseId: '19001', palletCode: 'TP-00', palletType: '木托盘',
2026-08-04 21:01:39 +08:00
})
await flushPromises()
expect(listPlatformPallets).toHaveBeenLastCalledWith({
2026-08-05 15:54:59 +08:00
page: 1, size: 20, warehouseId: 19001, palletCode: 'TP-00', palletType: '木托盘',
2026-08-04 21:01:39 +08:00
})
})
it('creates a pallet in the selected warehouse with the real request fields', async () => {
const wrapper = await mountView()
2026-08-05 15:54:59 +08:00
await setWarehouseFilter(wrapper, '19001')
2026-08-04 21:01:39 +08:00
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增托盘')
await createButton?.trigger('click')
await flushPromises()
const palletTypeItem = wrapper.findAll('.el-form-item').find((item) => item.text().includes('托盘类型'))
palletTypeItem?.findComponent({ name: 'ElSelect' }).vm.$emit('update:modelValue', '塑料托盘')
2026-08-06 14:55:17 +08:00
const inputNumbers = wrapper.findAllComponents({ name: 'ElInputNumber' })
inputNumbers[0]?.vm.$emit('update:modelValue', 1.5)
inputNumbers[1]?.vm.$emit('update:modelValue', 10)
await wrapper.find('input[placeholder="例如 黄色"]').setValue('黑色')
await wrapper.find('textarea[placeholder="可填写其他说明"]').setValue('标准托盘')
2026-08-04 21:01:39 +08:00
const saveButton = wrapper.findAll('button').find((button) => button.text().trim() === '保存托盘')
await saveButton?.trigger('click')
await flushPromises()
2026-08-06 13:56:04 +08:00
expect(createPlatformPallet).toHaveBeenCalledWith(0, {
2026-08-06 14:55:17 +08:00
warehouseId: 19001, palletType: '塑料托盘', capacityCbm: 1.5, weightKg: 10, color: '黑色', remark: '标准托盘',
2026-08-04 21:01:39 +08:00
})
})
2026-08-05 17:10:10 +08:00
it('groups the pallet editor into clear ownership and specification sections', async () => {
const wrapper = await mountView()
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增托盘')
await createButton?.trigger('click')
await flushPromises()
expect(document.body.querySelector('.pallet-editor-header')?.textContent).toContain('新增托盘')
expect(document.body.querySelector('.pallet-editor-header')?.textContent).toContain('录入归属仓库与基础规格')
const editorDialog = document.body.querySelector('.pallet-editor-drawer[role="dialog"]')
const labelledBy = editorDialog?.getAttribute('aria-labelledby')
expect(labelledBy).toBeTruthy()
expect(document.getElementById(labelledBy || '')?.textContent).toBe('新增托盘')
expect(document.body.querySelectorAll('.editor-section')).toHaveLength(2)
expect(document.body.querySelector('#pallet-ownership-heading')?.textContent).toBe('归属仓库')
expect(document.body.querySelector('#pallet-spec-heading')?.textContent).toBe('托盘规格')
expect(document.body.querySelector('.pallet-editor-footer')?.textContent).toContain('为必填项')
})
2026-08-04 21:01:39 +08:00
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')
})
2026-08-06 13:56:04 +08:00
it('keeps platform warehouse and pallet queries independent from customer names', async () => {
2026-08-04 21:01:39 +08:00
const wrapper = await mountView()
2026-08-06 13:56:04 +08:00
expect(wrapper.text()).not.toContain('客户列表暂时无法加载')
expect(wrapper.text()).not.toContain('所属企业')
2026-08-06 14:55:17 +08:00
expect(listAllPlatformWarehouses).toHaveBeenCalledWith()
2026-08-04 21:01:39 +08:00
expect(listPlatformPallets).toHaveBeenCalledWith({
page: 1, size: 20,
})
})
it('shows only the pallet fields exposed by the backend contract', async () => {
2026-08-06 13:56:04 +08:00
const publicPallet = { ...pallet, tenantId: '0' }
vi.mocked(listPlatformPallets).mockResolvedValue({ records: [publicPallet], page: 1, size: 20, total: 1, pages: 1 })
vi.mocked(getPlatformPallet).mockResolvedValue(publicPallet)
2026-08-04 21:01:39 +08:00
const wrapper = await mountView()
await loadRows(wrapper)
2026-08-06 13:56:04 +08:00
expect(wrapper.findComponent(DataTable).props('data')).toEqual([publicPallet])
2026-08-04 21:01:39 +08:00
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()
2026-08-06 13:56:04 +08:00
expect(getPlatformPallet).toHaveBeenCalledWith(0, 19001, '20001')
expect(document.body.textContent).toContain('资源作用域')
expect(document.body.textContent).toContain('平台公共资源')
expect(document.body.textContent).not.toContain('所属企业')
2026-08-04 21:01:39 +08:00
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('最大承重')
})
})