206 lines
7.1 KiB
TypeScript
206 lines
7.1 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { readFileSync } from 'node:fs'
|
|
import { parse } from '@vue/compiler-sfc'
|
|
import ts from 'typescript'
|
|
import { ref, nextTick } from 'vue'
|
|
import * as stops from '../src/utils/pl-plan-stop-form'
|
|
import * as persistence from '../src/views/m/pl/plan/mobilePlanPersistence'
|
|
|
|
// Evaluate current working-tree handlers, with only external APIs/UI services stubbed.
|
|
const source = parse(
|
|
readFileSync(
|
|
new URL('../src/components/business/pl-plan-form-dialog/index.vue', import.meta.url),
|
|
'utf8'
|
|
)
|
|
).descriptor.scriptSetup!.content
|
|
const tree = ts.createSourceFile('form.ts', source, ts.ScriptTarget.Latest, true)
|
|
const names = [
|
|
'loadPlanForEdit',
|
|
'loadPlanForCopy',
|
|
'applyStopsFromPlan',
|
|
'enrichStopFormsDisplayFromPlanProducts',
|
|
'bootstrapStopsFromPlanProducts',
|
|
'mirrorPlanProductsFromLoad',
|
|
'mapPlanProductToFormRow',
|
|
'mapPlanProductToCopyFormRow',
|
|
'mapProductsToSubmitPayload',
|
|
'buildPlanCommonPayload',
|
|
'handleSubmit'
|
|
]
|
|
const code = names
|
|
.map((name) => {
|
|
const node = tree.statements.find(
|
|
(node) => ts.isFunctionDeclaration(node) && node.name?.text === name
|
|
)
|
|
assert.ok(node, name)
|
|
return node.getText(tree)
|
|
})
|
|
.join('\n')
|
|
const clone = (value: any) => JSON.parse(JSON.stringify(value))
|
|
const line = {
|
|
productId: 'sku',
|
|
productNo: 'SKU',
|
|
productName: '产品',
|
|
unit: '吨',
|
|
packageSpec: '桶',
|
|
model: 'M',
|
|
planQty: 5,
|
|
quantity: 5
|
|
}
|
|
function harness(mobile: boolean) {
|
|
const noop = () => {},
|
|
yes = () => true,
|
|
identity = async (rows: any[]) => rows
|
|
let saved: any = {
|
|
id: 'plan',
|
|
mobileProductMetaVersion: 1,
|
|
products: [{ ...line, id: 'pp' }],
|
|
load: { id: 'load', products: [{ ...line, id: 'lp', planProductId: 'pp' }] },
|
|
deliveries: [{ id: 'delivery', seq: 1, products: [{ ...line, id: 'dp', planProductId: 'pp' }] }]
|
|
}
|
|
const writes: any[] = [],
|
|
events: any[] = []
|
|
const b: any = {
|
|
...stops,
|
|
...persistence,
|
|
nextTick,
|
|
isPageVariant: ref(mobile),
|
|
props: { planId: 'plan' },
|
|
isEdit: ref(true),
|
|
pageReady: ref(true),
|
|
pageLoadGeneration: 1,
|
|
assertPageLoadCurrent: noop,
|
|
isPageLoadCurrent: yes,
|
|
isPageSubmitCurrent: yes,
|
|
suppressAddressMutex: ref(false),
|
|
suppressStopProductSync: ref(false),
|
|
stopFormsReady: ref(false),
|
|
formData: { products: [] },
|
|
loadForm: ref(stops.createEmptyLoadForm()),
|
|
deliveryForms: ref([]),
|
|
resetForm: noop,
|
|
validateLoadedPagePlan: noop,
|
|
formatDate: () => '',
|
|
todayYmd: () => '2026-10-27',
|
|
resolveCurrentUserId: () => 'u',
|
|
resolveCurrentUserName: () => '',
|
|
syncCommittedPartyIds: noop,
|
|
loadedTransportStatus: ref(''),
|
|
loadedFollowStatus: ref(''),
|
|
loadedPlanSnapshot: ref(null),
|
|
enrichPlanProductsHazardFromCrm: identity,
|
|
enrichPlanProductLinesAlias: identity,
|
|
freightCalc: { resetFreightCalc: noop },
|
|
restoreFreightFromTransport: noop,
|
|
selectedDriverId: ref(''),
|
|
selectedEscortId: ref(''),
|
|
vehicleBundle: ref(null),
|
|
syncSelectedCustomerOptions: noop,
|
|
reapplyStopDibangByVehiclePlate: noop,
|
|
prepareLineAliasAndBrandForSubmit: noop,
|
|
resolveLineProductName: (name: string) => name,
|
|
buildTransportBlock: () => undefined,
|
|
mobileMetadata: { ready: yes },
|
|
mobileSavedPlanId: ref(''),
|
|
mobileCreateUncertain: ref(false),
|
|
submitLoading: ref(false),
|
|
basicFormRef: ref({ validate: async () => true }),
|
|
validateBusinessRules: yes,
|
|
prefetchStopContractProducts: noop,
|
|
validateSingleContractPerStop: yes,
|
|
validateContractPackageSpecs: yes,
|
|
validateStopContractsRequired: yes,
|
|
pickBxData: (x: any) => x,
|
|
showPlanSaveWarnings: noop,
|
|
ElMessage: { warning: noop, error: noop, success: noop },
|
|
innerVisible: ref(true),
|
|
emit: (...args: any[]) => events.push(args),
|
|
planApi: {
|
|
get: async () => clone(saved),
|
|
update: async (_id: string, payload: any) => {
|
|
writes.push(clone(payload))
|
|
// Server returns generated identities for genuinely new rows.
|
|
saved = { ...clone(payload), id: 'plan', mobileProductMetaVersion: 1 }
|
|
saved.products.forEach((row: any, i: number) => {
|
|
row.id ||= `pp-new-${i}`
|
|
})
|
|
const resolve = (row: any) =>
|
|
saved.products.find((pp: any) => pp.clientKey === row.planProductClientKey)?.id ||
|
|
row.planProductId
|
|
saved.load.id ||= 'load-new'
|
|
saved.load.products.forEach((row: any, i: number) => {
|
|
row.id ||= `lp-new-${i}`
|
|
row.planProductId = resolve(row)
|
|
})
|
|
saved.deliveries.forEach((stop: any, i: number) => {
|
|
stop.id ||= `delivery-new-${i}`
|
|
stop.products.forEach((row: any, j: number) => {
|
|
row.id ||= `dp-new-${i}-${j}`
|
|
row.planProductId = resolve(row)
|
|
})
|
|
})
|
|
return clone(saved)
|
|
}
|
|
}
|
|
}
|
|
const api = new Function(
|
|
...Object.keys(b),
|
|
ts.transpile(code, { target: ts.ScriptTarget.ESNext }) + `;return {${names.join(',')}}`
|
|
)(...Object.values(b))
|
|
return { b, api, writes, events, saved: () => saved }
|
|
}
|
|
|
|
for (const mobile of [false, true]) {
|
|
const platform = mobile ? 'H5' : 'Web'
|
|
test(`${platform} actual edit/save/reopen/save keeps all existing and freshly assigned child IDs`, async () => {
|
|
const h = harness(mobile)
|
|
await h.api.loadPlanForEdit('plan')
|
|
h.b.loadForm.value.products.push({
|
|
...line,
|
|
packageSpec: '袋',
|
|
id: undefined,
|
|
planProductId: undefined
|
|
})
|
|
h.b.deliveryForms.value.push({
|
|
...stops.createEmptyDeliveryForm(2),
|
|
products: [{ ...line, packageSpec: '袋' }]
|
|
})
|
|
await h.api.handleSubmit('save')
|
|
assert.equal(h.b.innerVisible.value, false)
|
|
assert.deepEqual(h.events.at(-1), ['success', { planId: 'plan', arranged: false }])
|
|
const saved = clone(h.saved())
|
|
await h.api.loadPlanForEdit('plan')
|
|
await h.api.handleSubmit('save')
|
|
const payload = h.writes[1]
|
|
assert.deepEqual(
|
|
payload.products.map((p: any) => p.id),
|
|
saved.products.map((p: any) => p.id)
|
|
)
|
|
assert.equal(payload.load.id, saved.load.id)
|
|
assert.deepEqual(
|
|
payload.load.products.map((p: any) => p.id),
|
|
saved.load.products.map((p: any) => p.id)
|
|
)
|
|
assert.deepEqual(
|
|
payload.deliveries.map((d: any) => [d.id, ...d.products.map((p: any) => p.id)]),
|
|
saved.deliveries.map((d: any) => [d.id, ...d.products.map((p: any) => p.id)])
|
|
)
|
|
assert.ok(payload.load.products.every((p: any) => p.planProductId))
|
|
assert.ok(payload.deliveries.every((d: any) => d.products.every((p: any) => p.planProductId)))
|
|
})
|
|
test(`${platform} actual copy handler strips source parent/product IDs from submit payload`, async () => {
|
|
const h = harness(mobile)
|
|
await h.api.loadPlanForCopy('plan')
|
|
const payload = h.api.buildPlanCommonPayload()
|
|
assert.equal(payload.load.id, undefined)
|
|
assert.ok(payload.products.every((p: any) => !p.id))
|
|
assert.ok(payload.load.products.every((p: any) => !p.id && !p.planProductId))
|
|
assert.ok(
|
|
payload.deliveries.every(
|
|
(d: any) => !d.id && d.products.every((p: any) => !p.id && !p.planProductId)
|
|
)
|
|
)
|
|
})
|
|
}
|