2026-07-30 17:59:42 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
|
|
|
|
|
const source = readFileSync(new URL('./FirstLegOrdersView.vue', import.meta.url), 'utf8')
|
|
|
|
|
const routerSource = readFileSync(new URL('../router/index.ts', import.meta.url), 'utf8')
|
|
|
|
|
|
|
|
|
|
test('routes the confirmed first-leg path to the implemented order view', () => {
|
|
|
|
|
assert.match(routerSource, /path: 'orders\/first-leg'.*FirstLegOrdersView\.vue/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('exposes real list, draft, submit, and detail workflow entries without cancellation', () => {
|
|
|
|
|
assert.match(source, /function openCreate\(\)/)
|
|
|
|
|
assert.match(source, /async function saveDraft\(\)/)
|
|
|
|
|
assert.match(source, /function submitOrder\(\)/)
|
|
|
|
|
assert.match(source, /async function openDetail\(order: FirstLegOrder\)/)
|
2026-07-31 15:34:22 +08:00
|
|
|
assert.match(source, /Promise\.allSettled\(\[/)
|
|
|
|
|
assert.match(source, /store\.load\(\),\s+firstLegPartySource\.load\(\)/)
|
2026-07-30 21:23:06 +08:00
|
|
|
assert.match(source, /tenantStore\.loadCompanyContext\(\)/)
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.match(source, /await store\.loadDetail\(order\.id\)/)
|
|
|
|
|
assert.doesNotMatch(source, /cancelOrder|取消订单/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('blocks order submission before company approval while preserving draft actions', () => {
|
|
|
|
|
assert.match(source, /companyApproved/)
|
|
|
|
|
assert.match(source, /企业资料待审核,当前仅可保存订单草稿/)
|
|
|
|
|
assert.match(source, /暂时无法提交订单/)
|
|
|
|
|
assert.match(source, /router\.push\('\/company\/profile'\)/)
|
|
|
|
|
assert.match(source, /auth\.hasPermission\('order:first-leg:submit'\)/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('renders dynamic cargo lines and responsive drawers', () => {
|
|
|
|
|
assert.match(source, /v-for="\(item, index\) in form\.items"/)
|
|
|
|
|
assert.match(source, /function addCargoItem\(\)/)
|
|
|
|
|
assert.match(source, /function removeCargoItem\(index: number\)/)
|
|
|
|
|
assert.match(source, /:global\(\.first-leg-editor\).*width: 100% !important/)
|
|
|
|
|
assert.match(source, /class="mobile-order-list"/)
|
|
|
|
|
assert.match(source, /\.desktop-order-table \{ display: none; \}/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('keeps primary mobile order actions touch sized', () => {
|
|
|
|
|
assert.match(source, /\.editor-actions :deep\(\.el-button\) \{ min-height: 44px;/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-01 12:17:33 +08:00
|
|
|
test('uses the shared query toolbar and data table without metric cards', () => {
|
|
|
|
|
assert.match(source, /import QueryToolbar, \{ type QueryField, type QueryModel \}/)
|
|
|
|
|
assert.match(source, /import DataTable, \{ type DataTableColumn \}/)
|
|
|
|
|
assert.match(source, /const filters = ref<QueryModel>/)
|
|
|
|
|
assert.match(source, /<QueryToolbar v-model="filters" :fields="queryFields" ariaLabel="头程订单查询" @search="searchOrders">/)
|
|
|
|
|
assert.match(source, /async function searchOrders\(model: QueryModel\)/)
|
|
|
|
|
assert.match(source, /await store\.load\(\)/)
|
|
|
|
|
assert.match(source, /<DataTable v-model:page="tablePage" v-model:page-size="tablePageSize" :data="pagedOrders" :columns="orderColumns"/)
|
|
|
|
|
assert.match(source, /\{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' \}/)
|
|
|
|
|
assert.doesNotMatch(source, /order-metrics|订单概览/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 17:59:42 +08:00
|
|
|
test('does not expose arbitrary status mutation controls to customers', () => {
|
|
|
|
|
assert.doesNotMatch(source, /setStatusForPreview/)
|
|
|
|
|
assert.doesNotMatch(source, /状态更新|更新状态/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('requires an enabled platform receiving warehouse in the order form', () => {
|
|
|
|
|
assert.match(source, /computed\(\(\) => store\.warehouses\)/)
|
|
|
|
|
assert.match(source, /availableWarehouses\.value\.length === 0/)
|
2026-08-01 12:17:33 +08:00
|
|
|
assert.match(source, /label="平台收货仓"[^>]*required/)
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.match(source, /v-model="form\.receivingWarehouseId"/)
|
|
|
|
|
assert.match(source, /selectedWarehouse\.businessHours/)
|
|
|
|
|
assert.match(source, /detailOrder\.receivingWarehouseSnapshot\?\.name/)
|
|
|
|
|
assert.match(source, /detailOrder\.receivingWarehouseSnapshot\.code/)
|
2026-07-30 21:23:06 +08:00
|
|
|
assert.match(source, /loadSelectedWarehouse\(detailedOrder\.receivingWarehouseId\)/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('collects the backend-required first-leg fields', () => {
|
2026-08-01 12:17:33 +08:00
|
|
|
for (const model of ['destinationCountryCode', 'destinationWarehouseName', 'channelCode', 'channelName', 'pickupMethod', 'taxMode', 'destinationType', 'packageType', 'upc']) {
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.match(source, new RegExp(`v-model="(?:form|item)\\.${model}"`))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-01 12:17:33 +08:00
|
|
|
test('shows field-level validation and concise guidance for required order inputs', () => {
|
|
|
|
|
assert.match(source, /ref="orderFormRef" :model="form" :rules="orderFormRules"/)
|
|
|
|
|
assert.match(source, /prop="shipperPartySnapshot\.contactName" required/)
|
|
|
|
|
assert.match(source, /prop="consigneePartySnapshot\.address" required/)
|
|
|
|
|
assert.match(source, /:prop="`items\.\$\{index\}\.cargoNameCn`" required/)
|
|
|
|
|
assert.match(source, /:prop="`items\.\$\{index\}\.grossWeightKg`" required/)
|
|
|
|
|
assert.match(source, /label="UPC".*required.*v-model="item\.upc"/)
|
|
|
|
|
assert.match(source, /placeholder="例如:蓝牙音箱"/)
|
|
|
|
|
assert.match(source, /\.order-form :deep\(\.el-form-item__error\).*position: static/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 17:59:42 +08:00
|
|
|
test('selects enabled counterparties and stores editable order snapshots', () => {
|
|
|
|
|
assert.match(source, /firstLegPartySource\.listAvailable\('SHIPPER'\)/)
|
|
|
|
|
assert.match(source, /firstLegPartySource\.findAvailable\(id, role\)/)
|
|
|
|
|
assert.match(source, /firstLegPartySource\.load\(\)/)
|
|
|
|
|
assert.match(source, /v-model="form\.shipperPartyId"/)
|
|
|
|
|
assert.match(source, /v-model="form\.consigneePartyId"/)
|
|
|
|
|
assert.match(source, /form\.shipperPartySnapshot\.contactName/)
|
|
|
|
|
assert.match(source, /form\.consigneePartySnapshot\.address/)
|
|
|
|
|
assert.match(source, /只影响本订单快照/)
|
2026-07-30 21:23:06 +08:00
|
|
|
assert.match(source, /form\.shipperPartySnapshot\.name\} · 历史快照/)
|
|
|
|
|
assert.match(source, /form\.consigneePartySnapshot\.name\} · 历史快照/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('shows fulfillment courier information as read-only client detail', () => {
|
|
|
|
|
assert.match(source, /履约快递信息/)
|
|
|
|
|
assert.match(source, /detailOrder\.expressCompanyName/)
|
|
|
|
|
assert.match(source, /detailOrder\.trackingNumber/)
|
|
|
|
|
assert.match(source, /客户端仅只读展示/)
|
|
|
|
|
assert.doesNotMatch(source, /v-model="form\.expressCompanyName"/)
|
|
|
|
|
assert.doesNotMatch(source, /v-model="form\.trackingNumber"/)
|
|
|
|
|
})
|
2026-07-30 21:23:06 +08:00
|
|
|
|
|
|
|
|
test('protects unsaved drafts and exposes recoverable load failures', () => {
|
|
|
|
|
assert.match(source, /onBeforeRouteLeave/)
|
|
|
|
|
assert.match(source, /beforeunload/)
|
|
|
|
|
assert.match(source, /confirmDiscardChanges/)
|
|
|
|
|
assert.match(source, /:before-close="requestCloseEditor"/)
|
|
|
|
|
assert.match(source, /store\.loadError/)
|
|
|
|
|
assert.match(source, /订单数据加载失败/)
|
|
|
|
|
assert.match(source, /订单详情加载失败/)
|
2026-07-31 15:34:22 +08:00
|
|
|
assert.match(source, /部分订单配套数据加载失败/)
|
|
|
|
|
assert.match(source, /往来单位加载失败/)
|
|
|
|
|
assert.match(source, /企业资料加载失败/)
|
2026-07-30 21:23:06 +08:00
|
|
|
assert.match(source, /@click="loadPageData"/)
|
|
|
|
|
assert.match(source, /@click="retryDetail"/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('copies reactive order drafts into plain request data', () => {
|
|
|
|
|
assert.match(source, /function cloneDraft\(draft: FirstLegOrderDraft\)/)
|
|
|
|
|
assert.match(source, /items: draft\.items\.map\(\(item\) => \(\{ \.\.\.item \}\)\)/)
|
|
|
|
|
assert.match(source, /store\.saveDraft\(cloneDraft\(form\)/)
|
|
|
|
|
assert.match(source, /store\.submit\(cloneDraft\(form\)/)
|
|
|
|
|
assert.doesNotMatch(source, /structuredClone\((?:form|order\.)/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('loads complete order detail before editing and rejects an unavailable historical warehouse', () => {
|
|
|
|
|
assert.match(source, /async function openEdit\(order: FirstLegOrder\)/)
|
|
|
|
|
assert.match(source, /const detailedOrder = await store\.loadDetail\(order\.id\)/)
|
|
|
|
|
assert.match(source, /draftFromOrder\(detailedOrder\)/)
|
|
|
|
|
assert.match(source, /draft\.receivingWarehouseId = ''/)
|
|
|
|
|
assert.match(source, /原平台收货仓当前不可用,请重新选择可用收货仓/)
|
|
|
|
|
})
|