import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { parse } from '@vue/compiler-sfc' import ts from 'typescript' import * as stopForms from '../src/utils/pl-plan-stop-form' import { collectStopContractIds } from '../src/utils/pl-plan-contract-product' const source = parse(readFileSync(new URL('../src/components/business/pl-plan-form-dialog/index.vue', import.meta.url), 'utf8')).descriptor.scriptSetup!.content const ast = ts.createSourceFile('plan.ts', source, ts.ScriptTarget.Latest, true) function contractWatcher(side: 'load' | 'delivery', bindings: Record) { const statement = ast.statements.find((node) => { if (!ts.isExpressionStatement(node) || !ts.isCallExpression(node.expression)) return false const call = node.expression return call.expression.getText(ast) === 'watch' && (side === 'load' ? call.arguments[0].getText(ast) === '() => loadForm.value.contracts' : call.arguments[0].getText(ast).includes('contractKey: collectStopContractIds')) }) as ts.ExpressionStatement assert.ok(statement, 'actual contract watcher exists') const callback = (statement.expression as ts.CallExpression).arguments[1].getText(ast) return new Function(...Object.keys(bindings), ts.transpile(`let loadContractFetchSeq = 0; const run = ${callback};`) + ';return run')(...Object.values(bindings)) } for (const mobile of [false, true]) { for (const side of ['load', 'delivery'] as const) { for (const change of ['add', 'remove', 'clear'] as const) { test(`${mobile ? 'H5' : 'desktop'} ${side} ${change} contracts keeps unaffected row identities`, async () => { const kept = { id: 'row-a', planProductId: 'pp-a', contractId: 'a', contractProductId: 'line-a', productId: 'sku', inboundQty: 3, archiveRemark: 'preserve', planQty: 8 } const removed = { id: 'row-b', planProductId: 'pp-b', contractId: 'b', productId: 'sku', planQty: 6 } const legacy = { id: 'row-old', planProductId: 'pp-old', productId: 'legacy', planQty: 2 } const before = ['a', 'b'] const after = change === 'add' ? ['a', 'b', 'c'] : change === 'remove' ? ['a'] : [] const stop = { id: 'stop', contracts: after.map(contractId => ({ contractId })), products: [kept, removed, legacy] } const loadForm = { value: stop } const deliveryForms = { value: [stop] } const run = contractWatcher(side, { isPageVariant: { value: mobile }, pageReady: { value: true }, pageLoadGeneration: 1, detailLoading: { value: false }, suppressStopProductSync: { value: false }, loadForm, deliveryForms, collectStopContractIds, mobileStopIdentity: () => 1, deliveryContractFetchSeqByIndex: {}, loadContractProductIds: { value: [] }, contractProductIdsCache: { value: {} }, fetchContractProductIds: async () => {}, isPageLoadCurrent: () => true, unionContractProductIds: () => ['sku', 'legacy'], filterProductsByAllowedProductIds: (rows: unknown[]) => rows, syncFromLoadProducts: () => {}, retainStopProductsForContracts: (stopForms as any).retainStopProductsForContracts }) if (side === 'load') await run(stop.contracts, before.map(contractId => ({ contractId }))) else await run([{ identity: mobile ? 1 : undefined, contractKey: after.join(',') }], [{ identity: mobile ? 1 : undefined, contractKey: before.join(',') }]) const products = side === 'load' ? loadForm.value.products : deliveryForms.value[0].products const expected = change === 'add' ? [kept, removed, legacy] : change === 'remove' ? [kept] : [] assert.deepEqual(products, expected) expected.forEach((row, index) => assert.equal(products[index], row, 'keep original row object and all saved fields')) }) } } }