import assert from 'node:assert/strict' import test from 'node:test' import { readFileSync } from 'node:fs' import ts from 'typescript' function fn(path: string, name: string, context: Record = {}) { const source = readFileSync(new URL(path, import.meta.url), 'utf8') const script = source.match(/]*>([\s\S]*?)<\/script>/)?.[1] || source const ast = ts.createSourceFile('page.ts', script, ts.ScriptTarget.Latest, true) const node = ast.statements.find((s) => ts.isFunctionDeclaration(s) && s.name?.text === name) assert.ok(node, name) const js = ts.transpileModule(node.getText(ast), { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText return new Function(...Object.keys(context), `${js}; return ${name}`)(...Object.values(context)) } const detail = { id: 'detail-a', expenseItemId: 'expense', amount: 10, remark: 'Reason', occurDate: '2026-01-01' } const webPage = '../src/views/bx/reimburse/apply/index.vue' const nativePage = '../../miniprogram/pages/bx/reimburse/apply.vue' for (const [editing, occurDate] of [[true, detail.occurDate], [false, detail.occurDate], [true, ''], [false, '']] as const) { test(`Web/H5 expense payload ${editing ? 'preserves edit ID' : 'clears source ID on create'} with ${occurDate || 'cleared date'}`, () => { const build = fn(webPage, 'buildAddPayload', { addForm: { details: [{ ...detail, occurDate }], remark: '', reimburseDate: '2026-01-01' }, selectedInvoicePicks: { value: [] }, editingId: { value: editing ? 'apply' : null }, ElMessage: { warning() {} } }) const row = build().details[0] assert.equal(row.id, editing ? 'detail-a' : undefined) assert.equal(row.remark, 'Reason') assert.equal(row.occurDate, occurDate || null) }) test(`Native expense payload ${editing ? 'preserves edit ID' : 'clears source ID on create'} with ${occurDate || 'cleared date'}`, async () => { let payload: any const save = async (...args: any[]) => { payload = args.at(-1) return { data: { id: 'apply', details: [ { id: 'server-detail', expenseId: 'expense', amount: 10, remark: 'Reason', occurDate: '2026-01-01' } ] } } } const context = { validateReimburseApplyForm: () => [{ ...detail, occurDate }], uploading: { value: false }, submitting: { value: false }, editId: { value: editing ? 'apply' : '' }, savedId: { value: '' }, buildInvoicePayload: () => ({}), form: { details: [{ ...detail }], title: '', remark: '', applicantId: '', reimburseDate: '' }, reimburseApi: { update: save, create: save }, bindNewAttachments: async () => {}, uni: { showModal() {}, showToast() {} } } await fn(nativePage, 'onSubmit', context)() assert.ok(payload) assert.equal(payload.details[0].id, editing ? 'detail-a' : undefined) assert.equal(payload.details[0].occurDate, occurDate || null) assert.equal( context.form.details[0].id, 'server-detail', 'continued editing uses returned child ID' ) }) } for (const page of ['reimburse', 'advance']) { test(`${page} workflow payload retains node ID and action`, () => { const build = fn(`../src/views/bx/workflow/${page}/index.vue`, 'buildNodePayload') const [row] = build([{ id: 'node-a', nodeName: 'Review', actionType: 'disburse' }]) assert.equal(row.id, 'node-a') assert.equal(row.actionType, 'disburse') }) } test('Native workflow payload retains node ID and action', () => { const build = fn('../../miniprogram/components/bx-ui/bx-wf-config-page.vue', 'buildNodePayload') const [row] = build([{ id: 'node-a', nodeName: 'Review', actionType: 'disburse' }]) assert.equal(row.id, 'node-a') assert.equal(row.actionType, 'disburse') }) test('Web/H5 save response refreshes IDs before submit retry', async () => { const addForm: any = { details: [{ ...detail }] } const editingId = { value: null } const save = fn(webPage, 'saveAddForm', { addForm, editingId, pickBxData: (res: any) => res.data, reimburseApi: { create: async () => ({ data: { id: 'apply', details: [ { id: 'server-detail', expenseId: 'expense', amount: 10, remark: 'Reason', occurDate: '2026-01-01' } ] } }) } }) assert.equal(await save({}), 'apply') assert.equal(editingId.value, 'apply') assert.equal(addForm.details[0].id, 'server-detail') }) test('Native edit loading retains detail identity', async () => { const form: any = {} const load = fn(nativePage, 'loadEditData', { form, reimburseApi: { getById: async () => ({ data: { details: [{ id: 'detail-a', expenseId: 'expense', amount: 10 }] } }) }, userStore: { userInfo: { id: 'user' } }, todayYmd: () => '2026-01-01', refreshWallet: async () => {}, selectedInvoicePicks: { value: [] }, kickAttachPreviewLoads() {}, attachmentApi: { listByBiz: async () => ({ data: [] }) }, files: { value: [] }, uni: { showToast() { assert.fail('edit loading failed') } } }) await load('apply') assert.equal(form.details[0].id, 'detail-a') }) test('Web/H5 edit loading retains detail identity', async () => { const addForm: any = {} const load = fn(webPage, 'openEditDialog', { addForm, reimburseApi: { getById: async () => ({ data: { details: [{ id: 'detail-a', expenseId: 'expense', amount: 10 }] } }) }, selectedInvoicePicks: { value: [] }, applyAttachments: { value: [] }, currentUserId: () => 'user', todayYmd: () => '2026-01-01', titleAutoLinked: { value: false }, isAutoReimburseTitle: () => false, editingId: { value: null }, dialogs: {}, loadApplicantOptions: async () => {}, refreshWalletBalance: async () => {}, loadSelectedPicksFromInvoices: async () => {}, loadBxAttachments: async () => [], ElMessage: { error() { assert.fail('edit loading failed') } } }) await load({ id: 'apply' }) assert.equal(addForm.details[0].id, 'detail-a') }) test('Recharge workflow payload retains node ID for rename', () => { const build = fn('../src/views/pl/vehicle-recharge-workflow/index.vue', 'buildNodePayload') const [row] = build([{ id: 'node-a', nodeName: 'Renamed', actionType: 'disburse' }]) assert.equal(row.id, 'node-a') assert.equal(row.actionType, 'disburse') })