2026-07-31 13:43:16 +08:00
|
|
|
import ElementPlus from 'element-plus'
|
|
|
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
|
|
|
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
|
|
|
|
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
|
import {
|
|
|
|
|
createPlatformWarehouse,
|
|
|
|
|
getPlatformWarehouse,
|
|
|
|
|
listPlatformWarehouses,
|
|
|
|
|
updatePlatformWarehouse,
|
|
|
|
|
type PlatformWarehouseDetailResource,
|
|
|
|
|
type PlatformWarehouseResource,
|
|
|
|
|
} from '@/api/platformWarehouses'
|
|
|
|
|
import { useAccessPreviewStore } from '@/stores/accessPreview'
|
2026-07-31 15:34:35 +08:00
|
|
|
import { useAuthStore } from '@/stores/auth'
|
2026-07-31 13:43:16 +08:00
|
|
|
import WarehousesView from './WarehousesView.vue'
|
|
|
|
|
|
|
|
|
|
vi.mock('@/api/platformWarehouses', () => ({
|
|
|
|
|
listPlatformWarehouses: vi.fn(),
|
|
|
|
|
getPlatformWarehouse: vi.fn(),
|
|
|
|
|
createPlatformWarehouse: vi.fn(),
|
|
|
|
|
updatePlatformWarehouse: vi.fn(),
|
|
|
|
|
changePlatformWarehouseStatus: vi.fn(),
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
class ResizeObserverStub implements ResizeObserver {
|
|
|
|
|
observe() {}
|
|
|
|
|
unobserve() {}
|
|
|
|
|
disconnect() {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createMemoryStorage(initial: Record<string, string> = {}): Storage {
|
|
|
|
|
const values = new Map(Object.entries(initial))
|
|
|
|
|
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 = {
|
|
|
|
|
id: '19001',
|
|
|
|
|
tenantId: 10001,
|
|
|
|
|
warehouseCode: 'CN-TEST-01',
|
|
|
|
|
warehouseName: '测试环境收货仓',
|
|
|
|
|
warehouseType: 'DOMESTIC',
|
|
|
|
|
countryCode: 'CN',
|
|
|
|
|
timezone: 'Asia/Shanghai',
|
|
|
|
|
contactName: '真实联系人',
|
|
|
|
|
contactPhone: '0579-12345678',
|
|
|
|
|
contactEmail: 'warehouse@example.com',
|
|
|
|
|
status: 'ACTIVE',
|
|
|
|
|
remark: '测试环境数据',
|
|
|
|
|
updatedAt: '2026-07-31T10:20:00',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const detail: PlatformWarehouseDetailResource = {
|
|
|
|
|
warehouse,
|
|
|
|
|
addresses: [{
|
|
|
|
|
id: '19002', tenantId: 10001, warehouseId: '19001', addressType: 'OPERATING', countryCode: 'CN',
|
|
|
|
|
stateProvince: '浙江省', city: '义乌市', district: '北苑街道', postalCode: '322000',
|
|
|
|
|
addressLine1: '真实物流园 1 号库', addressLine2: 'A 栋', latitude: '29.3', longitude: '120.0',
|
|
|
|
|
}],
|
|
|
|
|
capabilities: [
|
|
|
|
|
{ id: '19003', tenantId: 10001, warehouseId: '19001', capabilityCode: 'RECEIVING', capabilityName: '收货服务', enabled: true, cutoffTime: '17:00:00', description: '周一至周六 08:30-18:00' },
|
|
|
|
|
{ id: '19004', tenantId: 10001, warehouseId: '19001', capabilityCode: 'STORAGE', capabilityName: '仓储', enabled: true, cutoffTime: null, description: '托盘仓储' },
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 12:17:33 +08:00
|
|
|
async function mountView() {
|
2026-07-31 13:43:16 +08:00
|
|
|
vi.stubGlobal('localStorage', createMemoryStorage())
|
2026-08-01 12:17:33 +08:00
|
|
|
vi.stubGlobal('sessionStorage', createMemoryStorage())
|
2026-07-31 13:43:16 +08:00
|
|
|
const pinia = createPinia()
|
|
|
|
|
setActivePinia(pinia)
|
2026-07-31 15:34:35 +08:00
|
|
|
const auth = useAuthStore()
|
|
|
|
|
auth.token = 'token'
|
|
|
|
|
auth.permissions = ['platform:warehouse:list', 'platform:warehouse:create', 'platform:warehouse:edit', 'platform:warehouse:status']
|
|
|
|
|
useAccessPreviewStore().initializeAfterLogin()
|
2026-07-31 13:43:16 +08:00
|
|
|
const wrapper = mount(WarehousesView, {
|
|
|
|
|
attachTo: document.body,
|
|
|
|
|
global: { plugins: [pinia, ElementPlus] },
|
|
|
|
|
})
|
|
|
|
|
await flushPromises()
|
|
|
|
|
return wrapper
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enableAutoUnmount(afterEach)
|
|
|
|
|
afterAll(() => vi.unstubAllGlobals())
|
|
|
|
|
|
|
|
|
|
describe('WarehousesView real API integration', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
|
|
|
|
|
document.body.innerHTML = ''
|
|
|
|
|
vi.mocked(listPlatformWarehouses).mockReset().mockResolvedValue({ records: [warehouse], page: 1, size: 200, total: 1, pages: 1 })
|
|
|
|
|
vi.mocked(getPlatformWarehouse).mockReset().mockResolvedValue(detail)
|
|
|
|
|
vi.mocked(createPlatformWarehouse).mockReset().mockResolvedValue('19005')
|
|
|
|
|
vi.mocked(updatePlatformWarehouse).mockReset().mockResolvedValue(null)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-01 12:17:33 +08:00
|
|
|
it('automatically loads the fixed tenant and renders address and receiving capability from details', async () => {
|
2026-07-31 13:43:16 +08:00
|
|
|
const wrapper = await mountView()
|
|
|
|
|
|
|
|
|
|
expect(listPlatformWarehouses).toHaveBeenCalledWith(10001, 1, 200)
|
|
|
|
|
expect(getPlatformWarehouse).toHaveBeenCalledWith(10001, '19001')
|
|
|
|
|
expect(wrapper.text()).toContain('测试环境收货仓')
|
|
|
|
|
expect(wrapper.text()).toContain('北苑街道 真实物流园 1 号库 A 栋')
|
|
|
|
|
expect(wrapper.text()).toContain('周一至周六 08:30-18:00')
|
2026-08-01 12:17:33 +08:00
|
|
|
expect(wrapper.text()).not.toContain('全部收货仓')
|
|
|
|
|
expect(wrapper.find('.query-toolbar').exists()).toBe(true)
|
|
|
|
|
expect(wrapper.find('.data-table').exists()).toBe(true)
|
2026-07-31 13:43:16 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-01 12:17:33 +08:00
|
|
|
it('updates filters locally and only reloads after search', async () => {
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
const keywordInput = wrapper.find('input[placeholder="仓库名称、编码、城市或地址"]')
|
2026-07-31 13:43:16 +08:00
|
|
|
|
2026-08-01 12:17:33 +08:00
|
|
|
await keywordInput.setValue('义乌')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
expect(listPlatformWarehouses).toHaveBeenCalledTimes(1)
|
|
|
|
|
|
|
|
|
|
const searchButton = wrapper.findAll('button').find((button) => button.text().trim() === '查询')
|
|
|
|
|
expect(searchButton).toBeDefined()
|
|
|
|
|
await searchButton?.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(listPlatformWarehouses).toHaveBeenCalledTimes(2)
|
|
|
|
|
expect(wrapper.text()).toContain('测试环境收货仓')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not render a tenant selector during the single-tenant phase', async () => {
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
|
|
|
|
|
expect(wrapper.find('[aria-label="目标租户 ID"]').exists()).toBe(false)
|
|
|
|
|
expect(wrapper.text()).not.toContain('查询仓库')
|
|
|
|
|
expect(wrapper.text()).toContain('租户 10001')
|
2026-07-31 13:43:16 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('shows a recoverable real API error without falling back to demo warehouses', async () => {
|
|
|
|
|
vi.mocked(listPlatformWarehouses).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('演示物流园')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('preserves backend-only address, email, capability and cutoff fields when updating', async () => {
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
const editButton = wrapper.findAll('button').find((button) => button.text().trim() === '编辑')
|
|
|
|
|
expect(editButton).toBeDefined()
|
|
|
|
|
await editButton?.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
const saveButton = wrapper.findAll('button').find((button) => button.text().trim() === '保存仓库')
|
|
|
|
|
expect(saveButton).toBeDefined()
|
|
|
|
|
await saveButton?.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(updatePlatformWarehouse).toHaveBeenCalledOnce()
|
|
|
|
|
const [, , payload] = vi.mocked(updatePlatformWarehouse).mock.calls[0]
|
|
|
|
|
expect(payload.contactEmail).toBe('warehouse@example.com')
|
|
|
|
|
expect(payload.address).toMatchObject({
|
|
|
|
|
district: '北苑街道', postalCode: '322000', addressLine1: '真实物流园 1 号库', addressLine2: 'A 栋',
|
|
|
|
|
latitude: '29.3', longitude: '120.0',
|
|
|
|
|
})
|
|
|
|
|
expect(payload.capabilities).toContainEqual(expect.objectContaining({
|
|
|
|
|
capabilityCode: 'RECEIVING', cutoffTime: '17:00:00', description: '周一至周六 08:30-18:00',
|
|
|
|
|
}))
|
|
|
|
|
expect(payload.capabilities).toContainEqual(expect.objectContaining({ capabilityCode: 'STORAGE', description: '托盘仓储' }))
|
|
|
|
|
})
|
2026-08-01 12:17:33 +08:00
|
|
|
|
|
|
|
|
it('shows field-level required errors and does not submit an empty warehouse', async () => {
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增仓库')
|
|
|
|
|
expect(createButton).toBeDefined()
|
|
|
|
|
await createButton?.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(document.querySelector('input[placeholder="例如 广东省"]')).not.toBeNull()
|
|
|
|
|
expect(document.querySelector('input[placeholder="请输入仓库联系人姓名"]')).not.toBeNull()
|
|
|
|
|
expect(document.querySelector('textarea[placeholder="请输入街道、园区、楼栋及库位信息"]')).not.toBeNull()
|
|
|
|
|
|
|
|
|
|
await wrapper.find('input[placeholder="例如 CN-NB-01"]').trigger('blur')
|
|
|
|
|
await wrapper.vm.$nextTick()
|
|
|
|
|
expect(wrapper.find('#warehouse-code-error').text()).toBe('请输入仓库编码')
|
|
|
|
|
|
|
|
|
|
const saveButton = wrapper.findAll('button').find((button) => button.text().trim() === '保存仓库')
|
|
|
|
|
expect(saveButton).toBeDefined()
|
|
|
|
|
await saveButton?.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(createPlatformWarehouse).not.toHaveBeenCalled()
|
|
|
|
|
expect(wrapper.text()).toContain('请输入仓库编码')
|
|
|
|
|
expect(wrapper.text()).toContain('请输入仓库名称')
|
|
|
|
|
expect(wrapper.text()).toContain('请输入城市')
|
|
|
|
|
expect(wrapper.text()).toContain('请输入详细地址')
|
|
|
|
|
})
|
2026-07-31 13:43:16 +08:00
|
|
|
})
|