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 sharedLoaderSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/useLoadingListDetail.ts'), '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(sharedLoaderSource).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('柜次号') expect(detailSource).toContain('集装箱号') expect(source).not.toContain("label: '柜号'") expect(detailSource).not.toContain('柜号') 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(detailSource).toContain("backLabel || '返回列表'") 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).toContain("path: 'loading-lists/:planId'") expect(routesSource).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("await router.replace({ name: 'loading-list-detail'") expect(sharedLoaderSource).toContain("result.summary.status !== 'COMPLETED'") expect(sharedLoaderSource).toContain('MODE_STATUS_MISMATCH') expect(confirmedDetailSource).toContain('watch([planId, warehouseId], () => void loadDetail())') expect(sharedLoaderSource).toContain('if (request !== sequence) return null') 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('') expect(source).not.toContain('') expect(detailSource).toContain('') }) 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(' { 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("row.customerRemark || '—'") expect(detailSource).toContain("row.internalRemark || '—'") 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('