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() 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: '', }) }) })