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 * as vue from 'vue' import { createRouter, createMemoryHistory, useRoute, useRouter } from 'vue-router' import * as navigation from '../src/utils/mp-webview' const flush = async () => { for (let i = 0; i < 20; i++) await Promise.resolve() } async function mount(kind: 'contract' | 'quote', embedded = false) { const filename = `../src/views/m/crm/${kind}/detail.vue` const script = parse(readFileSync(new URL(filename, import.meta.url), 'utf8')).descriptor.scriptSetup!.content const ast = ts.createSourceFile('detail.ts', script, ts.ScriptTarget.Latest, true) const bindings: Record = {} for (const n of ast.statements.filter(ts.isImportDeclaration)) { if (n.importClause?.name) bindings[n.importClause.name.text] = () => undefined const named = n.importClause?.namedBindings if (named && ts.isNamedImports(named)) for (const e of named.elements) bindings[e.name.text] = () => undefined } const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/m/crm/contract/:id', name: 'MpCrmContractDetail', component: {} }, { path: '/m/crm/quote/:id', name: 'MpCrmQuoteDetail', component: {} }, { path: '/m/crm/customer/:id', name: 'MpCrmCustomerDetail', component: {} }, { path: '/m/pl/plan/:id', name: 'MpPlanDetail', component: {} } ] }) await router.push(embedded ? '/m/pl/plan/P1' : `/m/crm/${kind}/A`) const reads: Array<{ id: string; resolve: (value: any) => void }> = [] const props = vue.reactive({ embedded, recordId: 'E1', hideProductPrice: false }) const transport = (id: string) => new Promise((resolve) => reads.push({ id, resolve })) Object.assign(bindings, Object.fromEntries(Object.entries(vue).filter(([k]) => /^[a-zA-Z_$][\w$]*$/.test(k) && k !== 'default')), navigation, { useRoute, useRouter, defineProps: () => props, withDefaults: (value: any) => value, useUserStore: () => ({ info: {} }), useDictStore: () => ({ dictMap: {}, getOptions: () => [], getLabel: () => '', getTagType: () => 'info' }), getContract: transport, getQuote: transport, fetchCategoryTree: async () => [], buildPathLabelMapFromTreeRoots: () => new Map(), listContractApproval: async () => ({ data: [] }), listApproval: async () => ({ data: [] }), listQuoteProduct: async () => ({ rows: [] }), enrichQuoteDetailProductMeta: async () => {}, document: { querySelector: () => null }, window: { addEventListener() {}, removeEventListener() {}, clearTimeout() {} } }) const statements = ast.statements.filter((s) => !ts.isImportDeclaration(s)) const names = statements.flatMap((s) => ts.isFunctionDeclaration(s) ? [s.name!.text] : ts.isVariableStatement(s) ? s.declarationList.declarations.map((d) => d.name.getText(ast)) : []) const code = ts.transpile(statements.map((n) => n.getText(ast)).join('\n'), {target:ts.ScriptTarget.ESNext}) const renderer = vue.createRenderer({patchProp(){},insert(){},remove(){},createElement:()=>({}),createText:()=>({}),createComment:()=>({}),setText(){},setElementText(){},parentNode:()=>null,nextSibling:()=>null}) let api: any const app = renderer.createApp({setup() { api = new Function(...Object.keys(bindings), `${code};return {${names.join(',')}}`)(...Object.values(bindings)) return () => vue.h('div') }}) app.use(router) app.mount({}) api.onShellReady() await flush() return { api, reads, router, props, stop: () => app.unmount() } } for (const kind of ['contract', 'quote'] as const) for (const nextId of ['A', 'B']) { test(`${kind}离开到客户${nextId},不请求客户ID且迟到详情无权回写`, async () => { const h = await mount(kind) assert.deepEqual(h.reads.map((r) => r.id), ['A']) await h.router.push(`/m/crm/customer/${nextId}`) await flush() assert.deepEqual(h.reads.map((r) => r.id), ['A'], '离开watch不能发起外模块请求') void h.api.loadDetail() await flush() assert.deepEqual(h.reads.map((r) => r.id), ['A']) h.reads[0].resolve({ id: 'A', products: [], productList: [] }) await flush() assert.equal(h.api[kind].value, null) assert.equal(h.api.currentRecordId(), '') await h.router.push(`/m/crm/${kind}/A`) await flush() assert.deepEqual(h.reads.map((r) => r.id), ['A', 'A']) h.stop() }) } test('embedded合同只认recordId,宿主路径和ID变化不触发详情读取', async () => { const h = await mount('contract', true) await h.router.push('/m/crm/customer/B') await flush() assert.deepEqual(h.reads.map((r) => r.id), ['E1']) h.props.recordId = 'E2' await flush() assert.deepEqual(h.reads.map((r) => r.id), ['E1', 'E2']) h.reads[0].resolve({id:'E1'}) await flush() assert.equal(h.api.contract.value, null) h.stop() })