262 lines
15 KiB
TypeScript
262 lines
15 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { readFileSync } from 'node:fs'
|
||
import { resolve } from 'node:path'
|
||
|
||
const source = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/components/LoadingListsView.vue'), 'utf8')
|
||
const detailSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/components/LoadingListDetailDrawer.vue'), 'utf8')
|
||
const preloadDetailSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/pages/PreloadPlanDetailView.vue'), 'utf8')
|
||
const confirmedDetailSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/pages/ConfirmedLoadingListDetailView.vue'), 'utf8')
|
||
const routesSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/routes.ts'), 'utf8')
|
||
|
||
describe('LoadingListsView', () => {
|
||
it('filters both list modes by an active name-only customer selector', () => {
|
||
expect(source).toContain("listPlatformTenants({ page: 1, size: 200, status: 'ACTIVE' })")
|
||
expect(source).toContain("label: customer.tenantName")
|
||
expect(source).toContain("customerId: String(query.customerId || '').trim() || undefined")
|
||
expect(source).not.toContain('customer.tenantCode')
|
||
})
|
||
it('keeps the operation column first and fixed left in both lists', () => {
|
||
const actionColumns = [...source.matchAll(/key: 'actions', label: '操作', fixed: 'left'/g)]
|
||
expect(actionColumns).toHaveLength(2)
|
||
expect(source.indexOf("key: 'actions'")).toBeLessThan(source.indexOf("key: 'preloadNo'"))
|
||
expect(source.indexOf("key: 'actions'", source.indexOf("key: 'actions'") + 1)).toBeLessThan(source.indexOf("key: 'loadingListNo'"))
|
||
})
|
||
|
||
it('queries all warehouses without exposing a warehouse selector', () => {
|
||
expect(source).not.toContain('listAllPlatformWarehouses')
|
||
expect(source).not.toContain("key: 'warehouseId'")
|
||
expect(source).not.toContain('loadingWarehouses')
|
||
expect(source).not.toContain('warehouseId: warehouseId || undefined')
|
||
expect(source).toContain('query: { warehouseId: String(row.warehouseId || \'\') }')
|
||
expect(source).not.toContain("ElMessage.warning('请先选择仓库')")
|
||
expect(source).not.toContain("'请先选择仓库后查询'")
|
||
expect(preloadDetailSource).toContain('MISSING_WAREHOUSE_CONTEXT')
|
||
expect(confirmedDetailSource).toContain('MISSING_WAREHOUSE_CONTEXT')
|
||
})
|
||
|
||
it('uses the shared query and table components while keeping details on route pages', () => {
|
||
expect(source).toContain('QueryToolbar')
|
||
expect(source).toContain('DataTable')
|
||
expect(source).toContain('LoadErrorState')
|
||
expect(source).toContain('ApiErrorMeta')
|
||
expect(preloadDetailSource).toContain('AppDialog')
|
||
expect(confirmedDetailSource).not.toContain('AppDialog')
|
||
expect(detailSource).not.toContain('AppDrawer')
|
||
expect(detailSource).not.toContain('ContainerFeesPanel')
|
||
})
|
||
|
||
it('distinguishes the generated cabinet reference from the physical container number', () => {
|
||
expect(source).toContain("label: '柜次号'")
|
||
expect(source).toContain("label: '集装箱号'")
|
||
expect(source).toContain('装柜清单号/柜次号/集装箱号')
|
||
expect(detailSource).toContain('<span>柜次号</span>')
|
||
expect(detailSource).toContain('<span>集装箱号</span>')
|
||
expect(source).not.toContain("label: '柜号'")
|
||
expect(detailSource).not.toContain('<span>柜号</span>')
|
||
expect(source).toContain("key: 'cabinetNo', prop: 'cabinetNo'")
|
||
expect(source).not.toContain('internalContainerNo')
|
||
})
|
||
|
||
it('keeps preload routes while completed lists move into domestic container detail', () => {
|
||
expect(source).toContain("name: 'preload-plan-detail'")
|
||
expect(source).toContain("name: 'loading-list-detail'")
|
||
expect(source).not.toContain('confirmed-inline-detail')
|
||
expect(preloadDetailSource).toContain('返回列表')
|
||
expect(preloadDetailSource).toContain('router.back()')
|
||
expect(confirmedDetailSource).toContain('返回列表')
|
||
expect(confirmedDetailSource).toContain('router.back()')
|
||
expect(confirmedDetailSource).toContain('@media (min-width: 1920px) { .confirmed-loading-detail-page { width: 100%; max-width: none; gap: 12px; } }')
|
||
expect(routesSource).not.toContain("path: 'loading-lists/:planId'")
|
||
expect(routesSource).not.toContain("name: 'loading-list-detail'")
|
||
})
|
||
|
||
it('keeps the real transport edit contract in a 620px centered modal on the detail page', () => {
|
||
expect(source).not.toContain('class="loading-transport-dialog"')
|
||
expect(preloadDetailSource).toContain('width="min(620px, calc(100vw - 28px))"')
|
||
expect(preloadDetailSource).not.toContain('备注信息(仅自己可见)')
|
||
expect(preloadDetailSource).not.toContain('备注信息(客户可见)')
|
||
expect(preloadDetailSource).not.toContain('备注信息(仓库可见)')
|
||
})
|
||
|
||
it('only exposes transport editing for LOADING preload details', () => {
|
||
expect(detailSource).toContain("props.mode === 'preload' && props.canEdit && ['EDITING', 'LOADING'].includes(summary.value?.status || '')")
|
||
expect(detailSource).toContain('const itemColumns = computed(() => canEditItemRemark.value ? [actionColumn, ...baseItemColumns] : baseItemColumns)')
|
||
expect(detailSource).not.toContain('报关资料')
|
||
expect(detailSource).not.toContain('完成报关')
|
||
expect(preloadDetailSource).toContain('expectedVersion: detail.value.summary.version')
|
||
})
|
||
|
||
it('renders business labels instead of raw loading-plan status codes', () => {
|
||
expect(source).toContain(':status="loadingListStatusLabel(row.status)"')
|
||
expect(detailSource).toContain(':status="loadingListStatusLabel(summary.status)"')
|
||
expect(detailSource).not.toContain(':status="summary.status"')
|
||
expect(source).not.toContain('仅装柜中允许修改')
|
||
})
|
||
|
||
it('keeps transport editing as a detail-page action', () => {
|
||
expect(source).not.toContain('openEditFromRow(row)')
|
||
expect(source).not.toContain('编辑运输信息')
|
||
expect(detailSource).toContain("summary.containerNo || '待补录'")
|
||
expect(detailSource).toContain('v-if="canEditTransport"')
|
||
expect(preloadDetailSource).toContain("isContainerCorrection.value ? '纠正集装箱号 / 航次' : '补录集装箱号 / 航次'")
|
||
})
|
||
|
||
it('refreshes the detail version on A0409 while preserving transport input', () => {
|
||
expect(preloadDetailSource).toContain("details.code === 'A0409'")
|
||
expect(preloadDetailSource).toContain('await loadDetail()')
|
||
expect(preloadDetailSource).toContain('当前输入已保留,请核对后重新保存')
|
||
})
|
||
|
||
it('opens a validated plan and warehouse deep link without mixing list modes', () => {
|
||
expect(source).toContain('useRoute()')
|
||
expect(source).toContain('route.query.planId')
|
||
expect(source).toContain('route.query.warehouseId')
|
||
expect(source).toContain("router.replace({ name: 'loading-list-detail'")
|
||
expect(confirmedDetailSource).toContain("result.summary.status !== 'COMPLETED'")
|
||
expect(confirmedDetailSource).toContain('MODE_STATUS_MISMATCH')
|
||
expect(confirmedDetailSource).toContain('watch([planId, warehouseId], () => void loadDetail())')
|
||
expect(confirmedDetailSource).toContain('if (sequence !== loadSequence) return')
|
||
expect(preloadDetailSource).toContain('watch([planId, warehouseId], () => {')
|
||
})
|
||
|
||
it('uses backend readiness for whole-plan confirmation and translates blocking reasons', () => {
|
||
expect(detailSource).toContain(':disabled="!preloadSummary?.readiness.canConfirm || !pdaAssigneeSelected"')
|
||
expect(detailSource).toContain("OCEAN_BOOKING_NOT_RELEASED: '关联订舱尚未放舱'")
|
||
expect(detailSource).toContain("PRELOAD_ALREADY_CONFIRMED: '整份预装计划已经确认'")
|
||
expect(detailSource).toContain("confirmationStatus !== 'CONFIRMED' && !preloadSummary.readiness.canConfirm")
|
||
expect(detailSource).toContain('confirmationStatusLabels[preloadSummary.readiness.confirmationStatus]')
|
||
expect(detailSource).toContain("pdaTaskStatusLabels")
|
||
expect(preloadDetailSource).toContain('@confirm-preload="openConfirmPreload"')
|
||
})
|
||
|
||
it('passes translated status text directly to the badge', () => {
|
||
expect(source).toContain('<StatusBadge :status="loadingListStatusLabel(row.status)" />')
|
||
expect(source).not.toContain('<StatusBadge :status="row.status" />')
|
||
expect(detailSource).toContain('<StatusBadge :status="loadingListStatusLabel(summary.status)" />')
|
||
})
|
||
|
||
it('uses text row actions in the integrated list while retaining detail table styles', () => {
|
||
expect(source).toContain(":class=\"{ 'density-action': isPreload }\"")
|
||
expect(source).toContain(':type="isPreload ? undefined : \'primary\'" :plain="!isPreload"')
|
||
expect(source).not.toContain('<el-button v-if="canView" link')
|
||
expect(source).toContain("key: 'actions', label: '操作', fixed: 'left', width: 110")
|
||
expect(source).toContain('flex-wrap: wrap')
|
||
expect(source).toContain('@media (max-width: 720px)')
|
||
expect(source).toContain('.row-actions { gap: 4px; }')
|
||
expect(source).toContain('row-actions density-actions admin-text-actions')
|
||
expect(source).toContain('appearance="integrated" :stripe="false"')
|
||
expect(detailSource).toMatch(/:columns="itemColumns"\s+stripe/)
|
||
})
|
||
|
||
it('shows every prototype detail field that is backed by the loading item contract', () => {
|
||
expect(detailSource.indexOf("key: 'actions'")).toBeLessThan(detailSource.indexOf("key: 'sequence'"))
|
||
expect(detailSource).toContain("key: 'actions', label: '操作', fixed: 'left'")
|
||
expect(detailSource.indexOf("key: 'orderNo'")).toBeLessThan(detailSource.indexOf("key: 'containerNo'"))
|
||
expect(detailSource.indexOf("key: 'containerNo'")).toBeLessThan(detailSource.indexOf("key: 'skuCode'"))
|
||
const fields = [
|
||
'taxRefundFlag',
|
||
'chineseName',
|
||
'englishName',
|
||
'netWeightKg',
|
||
'packageCount',
|
||
'palletCount',
|
||
'volumeCbm',
|
||
'grossWeightKg',
|
||
'dimensionalWeightKg',
|
||
'productQuantity',
|
||
'unitsPerPackage',
|
||
'fbaTrackingNo',
|
||
'shipmentNo',
|
||
'shippingMark',
|
||
'customerRemark',
|
||
'internalRemark',
|
||
'warehouseRemark',
|
||
'customerServiceRemark',
|
||
]
|
||
let previous = detailSource.indexOf("key: 'customerName'")
|
||
for (const field of fields) {
|
||
const current = detailSource.indexOf(`key: '${field}'`, previous + 1)
|
||
expect(current, field).toBeGreaterThan(previous)
|
||
previous = current
|
||
}
|
||
expect(detailSource).not.toContain("key: 'goodsImageUrl'")
|
||
expect(detailSource).not.toContain("label: '退税资料'")
|
||
})
|
||
|
||
it('keeps customer remarks read-only while exposing three editable collaboration remarks', () => {
|
||
expect(detailSource).toContain('来源:客户下单')
|
||
expect(detailSource).toContain('来源:订单内部备注')
|
||
expect(detailSource).not.toContain('仓库备注将随装柜完成资料下发海外仓')
|
||
expect(detailSource).toContain("emit('editRemarks', row as LoadingItem)")
|
||
expect(detailSource).toContain("props.mode === 'preload' && props.canEdit && ['EDITING', 'LOADING'].includes(summary.value?.status || '')")
|
||
expect(detailSource).not.toContain("emit('editCustomerRemark'")
|
||
expect(detailSource).toContain("key: 'customerServiceRemark'")
|
||
})
|
||
|
||
it('uses one white detail surface, a readable four-column summary and dense item table', () => {
|
||
expect(detailSource).toContain('class="detail-overview"')
|
||
expect(detailSource).not.toContain('class="detail-overview surface-panel"')
|
||
expect(detailSource).toContain('class="detail-page-header"')
|
||
expect(detailSource).toContain("@click=\"emit('back')\"")
|
||
expect(detailSource).toContain('class="detail-page-header__actions"')
|
||
expect(detailSource).toContain('grid-template-columns: repeat(4, minmax(0, 1fr))')
|
||
expect(detailSource).toContain('min-height: 26px')
|
||
expect(detailSource).toContain('> div > span:first-child::after { content: ":"; }')
|
||
expect(detailSource).not.toContain('> div > span::after { content: ":"; }')
|
||
expect(detailSource).toContain('background: var(--admin-surface, #fff)')
|
||
expect(detailSource).toContain('overflow-wrap: anywhere')
|
||
expect(detailSource).toContain("className: 'identifier-column'")
|
||
expect(detailSource).toContain('showOverflowTooltip: true')
|
||
expect(detailSource).toContain('white-space: nowrap')
|
||
expect(detailSource).toContain('<template #toolbar-start>')
|
||
expect(detailSource).not.toContain('<span v-else aria-label="只读">—</span>')
|
||
})
|
||
|
||
it('merges the page header and summary while spacing independent business sections', () => {
|
||
expect(detailSource).toContain('<section v-if="detail && summary" class="detail-summary"')
|
||
expect(detailSource).not.toContain('class="detail-summary surface-panel"')
|
||
expect(detailSource).toContain('.detail-layout :deep(.detail-page-layout__content) { display: grid; min-width: 0; gap: 20px; }')
|
||
expect(detailSource).toContain('<section class="detail-section">')
|
||
expect(detailSource).not.toContain('class="detail-section surface-panel"')
|
||
expect(detailSource).not.toContain('class="history-section surface-panel"')
|
||
expect(detailSource).toContain('padding: var(--admin-space-6, 16px) 0 0')
|
||
expect(detailSource).toContain("fixed: 'left', width: 132")
|
||
})
|
||
|
||
it('does not expose unknown history action codes', () => {
|
||
expect(detailSource).toContain("CONFIRM_PRELOAD: '确认预装'")
|
||
expect(detailSource).toContain("[action] || '状态更新'")
|
||
expect(detailSource).not.toContain('[action] || action')
|
||
})
|
||
it('uses safe labels when customer or operator names are missing', () => {
|
||
expect(source).toContain("item.customerName || '客户信息缺失'")
|
||
expect(source).not.toContain('item.customerName || item.accountId')
|
||
expect(detailSource).toContain("item.customerName || '客户信息缺失'")
|
||
expect(detailSource).toContain("row.operatorName || '操作人信息缺失'")
|
||
expect(detailSource).not.toContain('item.customerName || item.accountId')
|
||
expect(detailSource).not.toContain('row.operatorName || row.operatorId')
|
||
})
|
||
|
||
it('does not retain the retired loading-list customs entry', () => {
|
||
expect(confirmedDetailSource).not.toContain('LoadingDeclaration')
|
||
expect(confirmedDetailSource).not.toContain('customsDocument')
|
||
expect(detailSource).not.toContain('customsDocument')
|
||
expect(detailSource).not.toContain('报关资料')
|
||
})
|
||
|
||
it('filters pure status no-ops and counts identity-distinct visible history events', () => {
|
||
for (const marker of ['visibleHistories', 'STATUS_UPDATE', 'operationId', 'PUSH_PRELOAD']) expect(detailSource).toContain(marker)
|
||
expect(detailSource).toContain('visibleHistories.length }} 条')
|
||
expect(detailSource).toContain(':data="visibleHistories"')
|
||
expect(detailSource).not.toContain('detail.histories.length }} 条')
|
||
expect(detailSource).not.toContain('v-for="history in detail.histories"')
|
||
expect(detailSource).toContain('seen.has(eventKey)')
|
||
expect(detailSource).toContain('history.operationId')
|
||
const sameWording = [
|
||
{ historyId: '4', operationId: '', actionType: 'ADJUST_ITEM', operationReason: '相同文案' },
|
||
{ historyId: '5', operationId: '', actionType: 'ADJUST_ITEM', operationReason: '相同文案' },
|
||
]
|
||
expect(new Set(sameWording.map((history) => history.historyId)).size).toBe(2)
|
||
})
|
||
})
|