Files
admin-web/src/modules/warehouse-resources/locations/pages/LocationsView.test.ts
T
2026-08-06 20:14:56 +08:00

340 lines
13 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 {
createPlatformLocation,
getPlatformLocation,
listPlatformLocations,
type PlatformLocationResource,
} from '../api/platformLocations'
import { listAllPlatformWarehouses, type PlatformWarehouseResource } from '../../../warehouses/api/platformWarehouses'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import DataTable from '@/shared/components/DataTable.vue'
import LocationsView from './LocationsView.vue'
vi.mock('../../../warehouses/api/platformWarehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
vi.mock('../api/platformLocations', () => ({
listPlatformLocations: vi.fn(),
getPlatformLocation: vi.fn(),
createPlatformLocation: vi.fn(),
updatePlatformLocation: vi.fn(),
changePlatformLocationStatus: vi.fn(),
deletePlatformLocation: 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 tenantId = '0'
const warehouseId = '9223372036854775805'
const warehouse: PlatformWarehouseResource = {
id: warehouseId,
tenantId,
warehouseCode: 'CN-YW-01',
warehouseName: '义乌收货仓',
warehouseType: 'DOMESTIC',
countryCode: 'CN',
timezone: 'Asia/Shanghai',
contactName: null,
contactPhone: null,
contactEmail: null,
status: 'ACTIVE',
remark: null,
}
const fbaWarehouse: PlatformWarehouseResource = {
...warehouse,
id: '9223372036854775802',
warehouseCode: 'FBA-ONT8',
warehouseName: 'ONT8 FBA 仓',
warehouseType: 'FBA',
}
const overseasWarehouse: PlatformWarehouseResource = {
...warehouse,
id: '9223372036854775801',
warehouseCode: 'US-LAX-01',
warehouseName: '洛杉矶海外仓',
warehouseType: 'OVERSEAS',
}
const availableLocation: PlatformLocationResource = {
id: '9223372036854775804',
tenantId,
warehouseId,
zoneCode: 'A',
zoneName: '常规区',
locationCode: 'A1-01',
locationName: 'A1 区 01 位',
locationType: 'SHELF',
maxWeightKg: '500.000',
sharedOverflow: false,
status: 'AVAILABLE',
remark: '靠近入库口',
version: 3,
updatedAt: '2026-08-04T10:20:00',
}
const occupiedLocation: PlatformLocationResource = {
...availableLocation,
id: '9223372036854775803',
locationCode: 'A1-02',
locationName: 'A1 区 02 位',
status: 'OCCUPIED',
version: 5,
}
async function mountView() {
vi.stubGlobal('localStorage', createMemoryStorage())
vi.stubGlobal('sessionStorage', createMemoryStorage())
const pinia = createPinia()
setActivePinia(pinia)
const auth = useAuthStore()
auth.token = 'token'
auth.permissions = [
'platform:location:list',
'platform:location:create',
'platform:location:edit',
'platform:location:status',
'platform:location:delete',
]
useAccessPreviewStore().initializeAfterLogin()
const wrapper = mount(LocationsView, {
attachTo: document.body,
global: { plugins: [pinia, ElementPlus] },
})
await flushPromises()
return wrapper
}
async function selectScope(wrapper: VueWrapper) {
const toolbar = wrapper.findComponent(QueryToolbar)
toolbar.vm.$emit('update:modelValue', { ...(toolbar.props('modelValue') as QueryModel), warehouseId })
await flushPromises()
const searchButton = toolbar.findAll('button').find((button) => button.text().trim() === '查询')
await searchButton!.trigger('click')
await flushPromises()
}
enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('LocationsView', () => {
beforeEach(() => {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
vi.mocked(listAllPlatformWarehouses).mockReset().mockResolvedValue([warehouse, fbaWarehouse, overseasWarehouse])
vi.mocked(listPlatformLocations).mockReset().mockResolvedValue({
records: [availableLocation, occupiedLocation], page: 1, size: 20, total: 2, pages: 1,
})
vi.mocked(getPlatformLocation).mockReset().mockResolvedValue({
location: availableLocation, warehouseCode: warehouse.warehouseCode, warehouseName: warehouse.warehouseName,
})
vi.mocked(createPlatformLocation).mockReset()
})
it('loads only domestic warehouse candidates for querying and writes', async () => {
const wrapper = await mountView()
const toolbar = wrapper.findComponent(QueryToolbar)
const fields = toolbar.props('fields') as QueryField[]
expect(wrapper.find('[aria-label="所属企业"]').exists()).toBe(false)
expect(fields[0]?.placeholder).toBe('全部仓库')
expect(fields[0]?.options).toEqual([
{ value: warehouseId, label: '义乌收货仓(CN-YW-01)' },
])
expect(fields.map(({ key, width }) => [key, width])).toEqual([
['warehouseId', 300], ['zoneCode', 210],
['locationCode', 220], ['locationType', 170], ['status', 160],
])
expect(listAllPlatformWarehouses).toHaveBeenCalledWith({ warehouseType: 'DOMESTIC' })
expect(listPlatformLocations).toHaveBeenCalledWith({
page: 1,
size: 20,
zoneCode: '',
locationCode: '',
locationType: '',
status: '',
})
const searchButton = toolbar.findAll('button').find((button) => button.text().trim() === '查询')
await searchButton!.trigger('click')
await flushPromises()
expect(listPlatformLocations).toHaveBeenCalledTimes(2)
expect(wrapper.text()).not.toContain('请选择客户')
expect(wrapper.text()).not.toContain('请选择所属企业')
expect(wrapper.text()).not.toContain('租户')
expect(wrapper.text()).not.toContain('企业 ID')
})
it('filters by the selected warehouse without exposing or sending a customer id', async () => {
const wrapper = await mountView()
await selectScope(wrapper)
const tableToolbar = wrapper.find('.table-panel .data-table__toolbar')
expect(tableToolbar.exists()).toBe(true)
expect(tableToolbar.text()).toContain('库位列表')
expect(tableToolbar.text()).toContain('共 2 个库位')
expect(tableToolbar.text()).toContain('新增库位')
expect(wrapper.findComponent(QueryToolbar).text()).not.toContain('新增库位')
expect(listAllPlatformWarehouses).toHaveBeenCalledWith({ warehouseType: 'DOMESTIC' })
expect(listPlatformLocations).toHaveBeenLastCalledWith({
page: 1,
size: 20,
warehouseId,
zoneCode: '',
locationCode: '',
locationType: '',
status: '',
})
expect(wrapper.text()).toContain('义乌收货仓')
expect(wrapper.text()).toContain('A1-01')
expect(wrapper.findAll('thead th').map((item) => item.text().trim())).toEqual([
'操作', '库位编码', '所属仓库', '库区', '库位类型', '最大承重(kg)', '状态', '备注',
])
})
it('opens creation without a list filter and requires the warehouse inside the form', async () => {
const wrapper = await mountView()
const createButton = wrapper.find('.table-panel .data-table__toolbar').findAll('button').find((button) => button.text().trim() === '新增库位')
expect(createButton?.attributes('disabled')).toBeUndefined()
await createButton!.trigger('click')
await flushPromises()
expect(document.body.textContent).toContain('新增库位')
expect(document.body.textContent).toContain('所属仓库')
expect(document.body.textContent).toContain('请选择国内仓')
})
it('does not use an FBA warehouse filter as the creation scope', async () => {
const wrapper = await mountView()
const toolbar = wrapper.findComponent(QueryToolbar)
toolbar.vm.$emit('update:modelValue', { ...(toolbar.props('modelValue') as QueryModel), warehouseId: fbaWarehouse.id })
await flushPromises()
const createButton = wrapper.find('.table-panel .data-table__toolbar').findAll('button').find((button) => button.text().trim() === '新增库位')
await createButton!.trigger('click')
await flushPromises()
const saveButton = [...document.body.querySelectorAll('button')].find((button) => button.textContent?.trim() === '保存库位')
saveButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
await flushPromises()
expect(createPlatformLocation).not.toHaveBeenCalled()
})
it('keeps the operation column first and hides edit actions for occupied resources', async () => {
const wrapper = await mountView()
await selectScope(wrapper)
const headings = wrapper.findAll('thead th').map((item) => item.text().trim())
expect(headings[0]).toBe('操作')
const bodyRows = wrapper.findAll('tbody tr')
expect(bodyRows[0]?.text()).toContain('编辑')
expect(bodyRows[1]?.text()).toContain('详情')
expect(bodyRows[1]?.text()).not.toContain('编辑')
expect(bodyRows[1]?.text()).not.toContain('更多')
})
it('does not offer editing for a location outside a domestic warehouse', async () => {
vi.mocked(listPlatformLocations).mockResolvedValue({
records: [{ ...availableLocation, warehouseId: fbaWarehouse.id }, availableLocation], page: 1, size: 20, total: 2, pages: 1,
})
const wrapper = await mountView()
const bodyRows = wrapper.findAll('tbody tr')
expect(bodyRows[0]?.text()).toContain('详情')
expect(bodyRows[0]?.text()).not.toContain('编辑')
expect(bodyRows[1]?.text()).toContain('编辑')
})
it('opens an authenticated detail view with warehouse context', async () => {
const wrapper = await mountView()
const detailButton = wrapper.find('.el-table__body-wrapper tbody tr button')
expect(detailButton.text()).toBe('详情')
await detailButton.trigger('click')
await flushPromises()
expect(getPlatformLocation).toHaveBeenCalledWith(warehouseId, availableLocation.id)
expect(document.body.textContent).toContain('库位详情')
expect(document.body.textContent).toContain('义乌收货仓(CN-YW-01)')
expect(document.body.textContent).toContain('资源作用域平台公共资源')
expect(document.body.textContent).not.toContain('所属企业')
expect(document.body.textContent).not.toContain('企业 ID')
expect(document.body.textContent).toContain('库位名称A1 区 01 位')
expect(document.body.textContent).toContain('v3')
})
it('presents occupied locations as read-only business resources', async () => {
vi.mocked(getPlatformLocation).mockResolvedValueOnce({
location: occupiedLocation,
warehouseCode: warehouse.warehouseCode,
warehouseName: warehouse.warehouseName,
})
const wrapper = await mountView()
const occupiedRow = wrapper.findAll('tbody tr')[1]
await occupiedRow!.find('button').trigger('click')
await flushPromises()
expect(document.body.textContent).toContain('状态已占用')
expect(document.body.textContent).toContain('当前库位正在被业务使用,只允许查看。')
})
it('clears the warehouse on reset and immediately restores the full query', async () => {
const wrapper = await mountView()
await selectScope(wrapper)
const toolbar = wrapper.findComponent(QueryToolbar)
const resetButton = toolbar.findAll('button').find((button) => button.text().trim() === '重置')
await resetButton!.trigger('click')
await flushPromises()
const model = wrapper.findComponent(QueryToolbar).props('modelValue') as QueryModel
expect(model.warehouseId).toBe('')
expect(listPlatformLocations).toHaveBeenLastCalledWith({
page: 1,
size: 20,
zoneCode: '',
locationCode: '',
locationType: '',
status: '',
})
expect(wrapper.text()).not.toContain('请选择客户')
})
it('paginates the all-customer list without adding customer or warehouse IDs', async () => {
vi.mocked(listPlatformLocations).mockResolvedValue({
records: [availableLocation, occupiedLocation], page: 1, size: 20, total: 40, pages: 2,
})
const wrapper = await mountView()
wrapper.findComponent(DataTable).vm.$emit('update:page', 2)
await flushPromises()
expect(listPlatformLocations).toHaveBeenLastCalledWith({
page: 2,
size: 20,
zoneCode: '',
locationCode: '',
locationType: '',
status: '',
})
})
})