pl-plan-contract-diff.test.ts 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. import assert from 'node:assert/strict'
  2. import { readFileSync } from 'node:fs'
  3. import test from 'node:test'
  4. import { parse } from '@vue/compiler-sfc'
  5. import ts from 'typescript'
  6. import * as stopForms from '../src/utils/pl-plan-stop-form'
  7. import { collectStopContractIds } from '../src/utils/pl-plan-contract-product'
  8. const source = parse(readFileSync(new URL('../src/components/business/pl-plan-form-dialog/index.vue', import.meta.url), 'utf8')).descriptor.scriptSetup!.content
  9. const ast = ts.createSourceFile('plan.ts', source, ts.ScriptTarget.Latest, true)
  10. function contractWatcher(side: 'load' | 'delivery', bindings: Record<string, unknown>) {
  11. const statement = ast.statements.find((node) => {
  12. if (!ts.isExpressionStatement(node) || !ts.isCallExpression(node.expression)) return false
  13. const call = node.expression
  14. return call.expression.getText(ast) === 'watch' && (side === 'load'
  15. ? call.arguments[0].getText(ast) === '() => loadForm.value.contracts'
  16. : call.arguments[0].getText(ast).includes('contractKey: collectStopContractIds'))
  17. }) as ts.ExpressionStatement
  18. assert.ok(statement, 'actual contract watcher exists')
  19. const callback = (statement.expression as ts.CallExpression).arguments[1].getText(ast)
  20. return new Function(...Object.keys(bindings), ts.transpile(`let loadContractFetchSeq = 0; const run = ${callback};`) + ';return run')(...Object.values(bindings))
  21. }
  22. for (const mobile of [false, true]) {
  23. for (const side of ['load', 'delivery'] as const) {
  24. for (const change of ['add', 'remove', 'clear'] as const) {
  25. test(`${mobile ? 'H5' : 'desktop'} ${side} ${change} contracts keeps unaffected row identities`, async () => {
  26. const kept = { id: 'row-a', planProductId: 'pp-a', contractId: 'a', contractProductId: 'line-a', productId: 'sku', inboundQty: 3, archiveRemark: 'preserve', planQty: 8 }
  27. const removed = { id: 'row-b', planProductId: 'pp-b', contractId: 'b', productId: 'sku', planQty: 6 }
  28. const legacy = { id: 'row-old', planProductId: 'pp-old', productId: 'legacy', planQty: 2 }
  29. const before = ['a', 'b']
  30. const after = change === 'add' ? ['a', 'b', 'c'] : change === 'remove' ? ['a'] : []
  31. const stop = { id: 'stop', contracts: after.map(contractId => ({ contractId })), products: [kept, removed, legacy] }
  32. const loadForm = { value: stop }
  33. const deliveryForms = { value: [stop] }
  34. const run = contractWatcher(side, {
  35. isPageVariant: { value: mobile }, pageReady: { value: true }, pageLoadGeneration: 1,
  36. detailLoading: { value: false }, suppressStopProductSync: { value: false },
  37. loadForm, deliveryForms, collectStopContractIds, mobileStopIdentity: () => 1,
  38. deliveryContractFetchSeqByIndex: {}, loadContractProductIds: { value: [] },
  39. contractProductIdsCache: { value: {} }, fetchContractProductIds: async () => {},
  40. isPageLoadCurrent: () => true, unionContractProductIds: () => ['sku', 'legacy'],
  41. filterProductsByAllowedProductIds: (rows: unknown[]) => rows, syncFromLoadProducts: () => {},
  42. retainStopProductsForContracts: (stopForms as any).retainStopProductsForContracts
  43. })
  44. if (side === 'load') await run(stop.contracts, before.map(contractId => ({ contractId })))
  45. else await run([{ identity: mobile ? 1 : undefined, contractKey: after.join(',') }], [{ identity: mobile ? 1 : undefined, contractKey: before.join(',') }])
  46. const products = side === 'load' ? loadForm.value.products : deliveryForms.value[0].products
  47. const expected = change === 'add' ? [kept, removed, legacy] : change === 'remove' ? [kept] : []
  48. assert.deepEqual(products, expected)
  49. expected.forEach((row, index) => assert.equal(products[index], row, 'keep original row object and all saved fields'))
  50. })
  51. }
  52. }
  53. }