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')
|
2026-08-03 18:52:30 +08:00
|
|
|
const routerSource = readFileSync(new URL('../routes.ts', import.meta.url), 'utf8')
|
|
|
|
|
const editorShellSource = readFileSync(new URL('../components/FirstLegOrderEditorShell.vue', import.meta.url), 'utf8')
|
2026-08-06 13:56:13 +08:00
|
|
|
const progressSource = readFileSync(new URL('../components/OrderProgressPanel.vue', import.meta.url), 'utf8')
|
2026-07-30 17:59:42 +08:00
|
|
|
|
|
|
|
|
test('routes the confirmed first-leg path to the implemented order view', () => {
|
2026-08-03 18:52:30 +08:00
|
|
|
assert.match(routerSource, /path: 'orders\/first-leg'[\s\S]*?component: \(\) => import\('\.\/views\/FirstLegOrdersView\.vue'\)[\s\S]*?release: 'active'/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-03 17:44:42 +08:00
|
|
|
test('opens order creation as a dedicated protected page while keeping draft editing in a drawer', () => {
|
2026-08-03 18:52:30 +08:00
|
|
|
assert.match(routerSource, /path: 'orders\/first-leg\/create'[\s\S]*?name: 'first-leg-order-create'[\s\S]*?release: 'active'[\s\S]*?permission: 'order:first-leg:create'/)
|
2026-08-05 15:55:02 +08:00
|
|
|
assert.match(routerSource, /name: 'first-leg-order-create'[\s\S]*?props: \{ createPage: true \}/)
|
2026-08-03 17:44:42 +08:00
|
|
|
assert.match(source, /router\.push\(\{ name: 'first-leg-order-create' \}\)/)
|
2026-08-05 15:55:02 +08:00
|
|
|
assert.match(source, /defineProps<\{ createPage\?: boolean \}>/)
|
|
|
|
|
assert.match(source, /const isCreatePage = computed\(\(\) => props\.createPage\)/)
|
2026-08-03 17:44:42 +08:00
|
|
|
assert.match(source, /<FirstLegOrderEditorShell[^>]*:page="isCreatePage"/)
|
|
|
|
|
assert.match(editorShellSource, /<section v-if="page" class="first-leg-editor-page">/)
|
|
|
|
|
assert.match(editorShellSource, /<el-drawer[\s\S]*v-else/)
|
|
|
|
|
assert.match(editorShellSource, /返回订单列表/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 17:59:42 +08:00
|
|
|
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\(\[/)
|
2026-08-06 15:32:07 +08:00
|
|
|
assert.match(source, /const orderPageRequest = isCreatePage\.value\s*\? store\.loadWarehouseType\('DOMESTIC', true\)\s*:\s*store\.load\(\)/)
|
|
|
|
|
assert.match(source, /Promise\.allSettled\(\[\s*orderPageRequest,\s*companyRequest/)
|
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|取消订单/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-06 18:02:02 +08:00
|
|
|
test('keeps create-page support errors scoped to create-page requests', () => {
|
|
|
|
|
assert.match(source, /if \(!isCreatePage\.value && store\.draftLoadError\)/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 17:59:42 +08:00
|
|
|
test('blocks order submission before company approval while preserving draft actions', () => {
|
2026-08-04 10:31:53 +08:00
|
|
|
assert.match(source, /companySubmissionBlocked/)
|
|
|
|
|
assert.match(source, /companyAuditLoaded\.value = true/)
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.match(source, /企业资料待审核,当前仅可保存订单草稿/)
|
|
|
|
|
assert.match(source, /暂时无法提交订单/)
|
|
|
|
|
assert.match(source, /router\.push\('\/company\/profile'\)/)
|
|
|
|
|
assert.match(source, /auth\.hasPermission\('order:first-leg:submit'\)/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-04 10:31:53 +08:00
|
|
|
test('defers approval checks to the server when the member cannot read the company profile', () => {
|
|
|
|
|
assert.match(source, /const canViewCompanyProfile = computed/)
|
|
|
|
|
assert.match(source, /const companySubmissionBlocked = computed\(\(\) => companyAuditLoaded\.value/)
|
|
|
|
|
assert.match(source, /const knownCompanyAuditStatus = companyAuditLoaded\.value \? tenantStore\.company\.auditStatus : undefined/)
|
|
|
|
|
assert.match(source, /store\.submit\([^\n]*knownCompanyAuditStatus\)/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 17:59:42 +08:00
|
|
|
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>/)
|
2026-08-06 13:56:13 +08:00
|
|
|
assert.match(source, /<QueryToolbar v-model="filters" class="first-leg-query-toolbar" :fields="queryFields" ariaLabel="订单查询" @search="searchOrders" \/>/)
|
2026-08-01 12:17:33 +08:00
|
|
|
assert.match(source, /async function searchOrders\(model: QueryModel\)/)
|
2026-08-06 13:56:13 +08:00
|
|
|
assert.match(source, /await store\.load\(\{/)
|
2026-08-01 12:17:33 +08:00
|
|
|
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-08-06 13:56:13 +08:00
|
|
|
test('shows one unified progress line, C02 business summaries, and optional customer-visible nodes', () => {
|
|
|
|
|
assert.match(source, /import OrderProgressPanel/)
|
|
|
|
|
assert.match(source, /<OrderProgressPanel :order="detailOrder" \/>/)
|
|
|
|
|
assert.match(source, /<h3>关键业务摘要<\/h3>/)
|
|
|
|
|
assert.match(source, /progressSummaryItems/)
|
|
|
|
|
assert.match(source, /summary\.preloadStatus/)
|
|
|
|
|
assert.match(source, /summary\.expectedPickCount/)
|
|
|
|
|
assert.match(source, /summary\.deliveryTrackingNo/)
|
|
|
|
|
assert.match(progressSource, /FIRST_LEG_PROGRESS_STAGES/)
|
|
|
|
|
assert.match(progressSource, /从订单提交到派送完成/)
|
|
|
|
|
assert.match(progressSource, /item\.kind === 'CUSTOM_NODE'/)
|
|
|
|
|
assert.match(progressSource, /业务节点/)
|
|
|
|
|
assert.match(progressSource, /暂无进度记录/)
|
|
|
|
|
assert.doesNotMatch(progressSource, /客户确认|差异处理中|派送中/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('uses only fields present on C02 rows before loading full detail', () => {
|
|
|
|
|
assert.match(source, /getOrderCargoSummary/)
|
|
|
|
|
assert.match(source, /目的地 \/ 渠道/)
|
|
|
|
|
assert.match(source, /运输方式待更新|渠道待更新/)
|
|
|
|
|
assert.doesNotMatch(source, /key: 'operator'/)
|
|
|
|
|
assert.doesNotMatch(source, /key: 'transportMode', type: 'select'/)
|
|
|
|
|
assert.match(source, /store\.draftLoadError/)
|
|
|
|
|
assert.match(source, /mainStatus: statusValue && statusValue !== 'DRAFT' \? statusValue : undefined/)
|
|
|
|
|
assert.match(source, /draftOnly: statusValue === 'DRAFT'/)
|
|
|
|
|
assert.match(source, /createdFrom: range\[0\] \? `\$\{range\[0\]\}T00:00:00`/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('keeps the seven-stage progress and business summary usable on mobile', () => {
|
|
|
|
|
assert.match(progressSource, /grid-template-columns: repeat\(7, 82px\)/)
|
|
|
|
|
assert.match(progressSource, /overflow-x: auto/)
|
|
|
|
|
assert.match(source, /\.fulfillment-summary \{ grid-template-columns: 1fr; \}/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-03 10:51:53 +08:00
|
|
|
test('keeps the first viewport compact and moves create into the table toolbar', () => {
|
|
|
|
|
assert.match(source, /class="order-table-toolbar"/)
|
|
|
|
|
assert.match(source, /<strong>订单列表<\/strong><span>共 \{\{ filteredOrders\.length \}\} 条<\/span>/)
|
2026-08-03 17:44:42 +08:00
|
|
|
assert.match(source, /class="order-table-toolbar"[\s\S]*order:first-leg:create[\s\S]*创建订单[\s\S]*class="order-eligibility is-pending"/)
|
2026-08-03 10:51:53 +08:00
|
|
|
assert.doesNotMatch(source, /<QueryToolbar[\s\S]*?<template #actions>/)
|
2026-08-03 17:44:42 +08:00
|
|
|
assert.match(source, /client-main:has\(> \.first-leg-page:not\(\.is-create-route\)\) > \.client-page-heading > div\) \{ position: absolute; width: 1px; height: 1px;/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('keeps desktop query actions on the first row and only shows the qualification notice before approval', () => {
|
|
|
|
|
assert.match(source, /\.first-leg-query-toolbar \{ flex-wrap: nowrap; \}/)
|
|
|
|
|
assert.match(source, /query-toolbar__filters\) \{ display: grid; min-width: 0; flex: 1 1 auto;/)
|
|
|
|
|
assert.match(source, /query-toolbar__actions\) \{ align-self: flex-start; flex-wrap: nowrap; margin-left: auto;/)
|
2026-08-04 10:31:53 +08:00
|
|
|
assert.match(source, /v-if="companySubmissionBlocked && !supportingDataErrors\.some/)
|
2026-08-03 17:44:42 +08:00
|
|
|
assert.doesNotMatch(source, /企业资料已审核,可正常提交订单/)
|
|
|
|
|
assert.doesNotMatch(source, /order-eligibility\.is-approved/)
|
2026-08-03 10:51:53 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-06 15:32:07 +08:00
|
|
|
test('does not let order-list loading block the create form', () => {
|
|
|
|
|
assert.match(source, /v-loading="isCreatePage && supportingDataLoading"/)
|
|
|
|
|
assert.match(source, /const pageDataLoading = computed\(\(\) => supportingDataLoading\.value \|\| \(!isCreatePage\.value && store\.loading\)\)/)
|
|
|
|
|
})
|
|
|
|
|
|
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', () => {
|
2026-08-04 11:14:29 +08:00
|
|
|
assert.match(source, /store\.warehouses\.filter\(\(warehouse\) => warehouse\.warehouseType === 'DOMESTIC'\)/)
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.match(source, /availableWarehouses\.value\.length === 0/)
|
2026-08-04 12:01:05 +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/)
|
2026-08-03 17:44:42 +08:00
|
|
|
assert.match(source, /class="selected-warehouse" aria-label="所选收货仓详情"/)
|
|
|
|
|
assert.doesNotMatch(source, /class="selected-warehouse"[^>]*>[\s\S]*?<strong>\{\{ selectedWarehouse\.name \}\}<\/strong>/)
|
|
|
|
|
assert.match(source, /first-leg-editor \.el-drawer__header\) \{[^}]*margin-bottom: 0;[^}]*border-bottom: 1px solid var\(--border\)/)
|
|
|
|
|
assert.match(source, /first-leg-editor \.el-drawer__body\) \{[^}]*min-height: 0;[^}]*padding: 18px 22px 32px;[^}]*overscroll-behavior: contain/)
|
|
|
|
|
assert.match(source, /first-leg-editor \.el-drawer__footer\) \{[^}]*border-top: 1px solid var\(--border\);[^}]*background: #fff/)
|
2026-07-30 17:59:42 +08:00
|
|
|
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
|
|
|
})
|
|
|
|
|
|
2026-08-04 11:14:29 +08:00
|
|
|
test('selects platform destinations by type and caches private addresses per user', () => {
|
|
|
|
|
assert.match(source, /warehouse\.warehouseType === form\.destinationType/)
|
2026-08-06 14:55:13 +08:00
|
|
|
assert.match(source, /await store\.loadWarehouseType\(type\)/)
|
|
|
|
|
assert.match(source, /await loadDestinationWarehouses\(form\.destinationType\)/)
|
|
|
|
|
assert.match(source, /:loading="destinationWarehousesLoading"/)
|
2026-08-04 11:14:29 +08:00
|
|
|
assert.match(source, /v-model="form\.destinationWarehouseId"/)
|
|
|
|
|
assert.match(source, /form\.amazonWarehouseCode = form\.destinationType === 'FBA' \? warehouse\.code : ''/)
|
|
|
|
|
assert.match(source, /cc:first-leg:private-addresses:\$\{auth\.user\?\.id/)
|
|
|
|
|
assert.match(source, /localStorage\.setItem\(privateAddressStorageKey\(\)/)
|
|
|
|
|
assert.match(source, /私人地址不能引用平台公共仓库|form\.destinationWarehouseId = ''/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-06 18:02:02 +08:00
|
|
|
test('lets the create-page instance own destination loading and exposes a recoverable error state', () => {
|
|
|
|
|
assert.match(source, /await router\.push\(\{ name: 'first-leg-order-create' \}\)\s*\n\}/)
|
|
|
|
|
assert.doesNotMatch(source, /await router\.push\(\{ name: 'first-leg-order-create' \}\)[\s\S]*?await loadDestinationWarehouses\(form\.destinationType\)/)
|
|
|
|
|
assert.match(source, /if \(destinationResult\.status === 'rejected'\) supportingDataErrors\.value\.push\(`目的仓加载失败:\$\{errorMessage\(destinationResult\.reason\)\}`\)/)
|
|
|
|
|
assert.match(source, /下单配套数据加载失败/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('shows a loading state before the list can create an order', () => {
|
|
|
|
|
assert.match(source, /<el-button v-if="auth\.hasPermission\('order:first-leg:create'\)" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading \|\| availableWarehouses\.length === 0" @click="openCreate"><FilePlus2/)
|
|
|
|
|
assert.match(source, /pageDataLoading \? '加载收货仓中' : \(availableWarehouses\.length \? '创建第一笔订单'/)
|
2026-08-06 15:32:07 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-04 12:58:04 +08:00
|
|
|
test('auto-fills trusted warehouse and company data instead of asking customers to repeat it', () => {
|
|
|
|
|
assert.match(source, /async function selectDestinationWarehouse\(id: string\)/)
|
|
|
|
|
assert.match(source, /await store\.loadWarehouseDetail\(warehouse\.id\)/)
|
|
|
|
|
assert.match(source, /availableWarehouses\.value\.length === 1/)
|
|
|
|
|
assert.match(source, /availableDestinationWarehouses\.value\.length === 1/)
|
|
|
|
|
assert.match(source, /function createPickupPartySnapshot\(\)/)
|
|
|
|
|
assert.match(source, /contactName: tenantStore\.company\.contactName/)
|
|
|
|
|
assert.match(source, /selectedDestinationWarehouse\.address/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 17:59:42 +08:00
|
|
|
test('collects the backend-required first-leg fields', () => {
|
2026-08-03 15:50:24 +08:00
|
|
|
for (const model of ['destinationCountryCode', 'destinationWarehouseName', 'channelCode', '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-03 15:50:24 +08:00
|
|
|
assert.match(source, /form\.channelName = channel\?\.name/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('distills order creation into the confirmed prototype sections without fake pricing', () => {
|
2026-08-04 12:58:04 +08:00
|
|
|
assert.match(source, /<h3>目的地信息<\/h3>/)
|
|
|
|
|
assert.match(source, /<h3>发货信息<\/h3>/)
|
2026-08-03 15:50:24 +08:00
|
|
|
assert.match(source, /<h3>货物明细<\/h3>/)
|
2026-08-04 12:58:04 +08:00
|
|
|
assert.ok(source.indexOf('<h3>发货信息</h3>') < source.indexOf('<h3>目的地信息</h3>'))
|
|
|
|
|
assert.ok(source.indexOf('<h3>目的地信息</h3>') < source.indexOf('<h3>货物明细</h3>'))
|
2026-08-03 15:50:24 +08:00
|
|
|
assert.match(source, /class="segmented-options"/)
|
2026-08-04 12:01:05 +08:00
|
|
|
assert.match(source, /class="prototype-form-row prototype-form-row--tabs"/)
|
2026-08-03 15:50:24 +08:00
|
|
|
assert.match(source, /class="cargo-primary-grid"/)
|
|
|
|
|
assert.match(source, /title="更多申报信息"/)
|
|
|
|
|
assert.doesNotMatch(source, /费用预估|查看最新报价|cost-estimate/)
|
|
|
|
|
assert.doesNotMatch(source, /v-model="form\.(?:businessType|origin|destination|priority|tradeTerm)"/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-01 12:17:33 +08:00
|
|
|
test('shows field-level validation and concise guidance for required order inputs', () => {
|
2026-08-03 17:44:42 +08:00
|
|
|
assert.match(source, /ref="orderFormRef"[^>]*:model="form" :rules="orderFormRules"/)
|
2026-08-01 12:17:33 +08:00
|
|
|
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-08-04 12:01:05 +08:00
|
|
|
test('collects snapshots only for pickup and private destinations', () => {
|
|
|
|
|
assert.doesNotMatch(source, /firstLegPartySource|v-model="form\.(?:shipper|consignee)PartyId"/)
|
|
|
|
|
assert.match(source, /form\.pickupMethod === 'PICKUP'/)
|
|
|
|
|
assert.match(source, /form\.destinationType === 'PRIVATE'/)
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.match(source, /form\.shipperPartySnapshot\.contactName/)
|
|
|
|
|
assert.match(source, /form\.consigneePartySnapshot\.address/)
|
2026-08-04 12:01:05 +08:00
|
|
|
assert.match(source, /syncPrivateDestinationName/)
|
|
|
|
|
assert.match(source, /changePickupMethod/)
|
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
|
|
|
|
2026-08-04 21:01:34 +08:00
|
|
|
test('loads merchant inbound billing as an isolated read-only detail section', () => {
|
|
|
|
|
assert.match(source, /import InboundBillingPanel/)
|
|
|
|
|
assert.match(source, /import \{ getMerchantInboundBilling \}/)
|
|
|
|
|
assert.match(source, /void loadInboundBilling\(order\)/)
|
|
|
|
|
assert.match(source, /<InboundBillingPanel/)
|
|
|
|
|
assert.match(source, /@retry="retryInboundBilling"/)
|
|
|
|
|
assert.match(source, /error instanceof ApiError && error\.code === 'A0404'/)
|
|
|
|
|
assert.match(source, /order\.status === 'DRAFT'/)
|
|
|
|
|
assert.match(source, /订单提交后可查看入库结果/)
|
|
|
|
|
assert.doesNotMatch(source, /confirmMerchantInbound|adjustMerchantInbound|sendMerchantInbound/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('keeps platform enterprise scope and tenant terminology out of the customer order UI', () => {
|
|
|
|
|
const templateSource = source.slice(source.indexOf('<template>'), source.lastIndexOf('</template>'))
|
|
|
|
|
assert.doesNotMatch(templateSource, /租户|tenantId|所属企业|企业筛选/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('discards stale inbound billing responses when the drawer closes or the user changes orders', () => {
|
|
|
|
|
assert.match(source, /let inboundBillingRequestSequence = 0/)
|
|
|
|
|
assert.match(source, /const sequence = \+\+inboundBillingRequestSequence/)
|
|
|
|
|
assert.match(source, /sequence === inboundBillingRequestSequence[\s\S]*detailOpen\.value[\s\S]*detailId\.value === orderId/)
|
|
|
|
|
assert.match(source, /watch\(detailOpen,[\s\S]*resetInboundBillingState\(\)/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 21:23:06 +08:00
|
|
|
test('protects unsaved drafts and exposes recoverable load failures', () => {
|
2026-08-05 15:55:02 +08:00
|
|
|
assert.match(source, /useUnsavedWorkRegistration\(/)
|
|
|
|
|
assert.match(source, /routeKey: unsavedRouteKey/)
|
|
|
|
|
assert.doesNotMatch(source, /onBeforeRouteLeave|beforeunload/)
|
2026-07-30 21:23:06 +08:00
|
|
|
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, /部分订单配套数据加载失败/)
|
2026-08-04 12:01:05 +08:00
|
|
|
assert.doesNotMatch(source, /往来单位加载失败/)
|
2026-07-31 15:34:22 +08:00
|
|
|
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, /原平台收货仓当前不可用,请重新选择可用收货仓/)
|
2026-08-04 11:14:29 +08:00
|
|
|
assert.match(source, /原目的仓当前不可用,请重新选择平台维护的目的仓/)
|
2026-07-30 21:23:06 +08:00
|
|
|
})
|