340 lines
13 KiB
TypeScript
340 lines
13 KiB
TypeScript
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: '',
|
||
})
|
||
})
|
||
})
|