Files
shanxia2.0/web-admin/scripts/mobile-pl-record-identity.test.ts
T

735 lines
25 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 * as vue from 'vue'
function deferred() {
let resolve!: (value?: any) => void
let reject!: (error: any) => void
const promise = new Promise<any>((a, b) => {
resolve = a
reject = b
})
return { promise, resolve, reject }
}
const flush = async () => {
for (let i = 0; i < 20; i++) await Promise.resolve()
}
function harness(page: string, overrides: Record<string, any> = {}, initialProps: any = {}) {
const content = parse(
readFileSync(new URL(`../src/views/m/pl/${page}.vue`, import.meta.url), 'utf8')
).descriptor.scriptSetup!.content
const ast = ts.createSourceFile('page.ts', content, ts.ScriptTarget.Latest, true)
const route = vue.reactive({
params: { id: 'A' },
query: {} as Record<string, string>,
fullPath: '/A',
path: '/A'
})
const props = vue.reactive({ modelValue: true, planId: 'A', permitted: true, ...initialProps })
const messages: any[] = []
const unmounts: Array<() => void> = []
const writes: any[] = [],
pushes: any[] = [],
calls: string[] = []
const bindings: Record<string, any> = Object.fromEntries(
Object.entries(vue).filter(
([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default' && key !== 'version'
)
)
for (const s of ast.statements) {
if (!ts.isImportDeclaration(s)) continue
const clause = s.importClause
if (clause?.name) bindings[clause.name.text] = () => undefined
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings))
for (const item of clause.namedBindings.elements) bindings[item.name.text] = () => undefined
}
const read = async (arg: any) => {
const id = typeof arg === 'string' ? arg : arg.id
calls.push(id)
return {
data: {
id,
status: 'draft',
products: [{ id: 'p', productId: 'p', isToxic: 1 }],
productList: []
}
}
}
const planApi = {
get: read,
getArrangeInfo: async () => ({ data: {} }),
waitNotice: async (id: string) => {
writes.push(id)
return { data: { id, products: [] } }
},
cancelWaitNotice: async (id: string) => {
writes.push(id)
return { data: { id, products: [] } }
},
cancel: async (...args: any[]) => {
writes.push(args)
return { data: { id: args[0], products: [] } }
},
updateProductCerts: async (...args: any[]) => writes.push(args),
...overrides.planApi
}
Object.assign(bindings, {
useRoute: () => route,
useRouter: () => ({
push: (v: any) => pushes.push(v),
replace: async (v: any) => {
if (typeof v === 'object') route.query = v.query
else pushes.push(v)
}
}),
useUserStore: () => ({ accessToken: 'session', info: { id: 'user' } }),
useDictStore: () => ({ getLabel: () => '', getTagType: () => '' }),
onMounted() {},
onUnmounted: (fn: any) => unmounts.push(fn),
onBeforeUnmount: (fn: any) => unmounts.push(fn),
loadMobileDetailReads: async () => ({ errors: [] }),
resolvePlanStopsForList: (plan: any) => ({
load: plan.load || {},
deliveries: plan.deliveries || []
}),
mapPlanProductToFormRow: (product: any) => product,
mapLoadFromApi: (load: any) => load,
mapDeliveriesFromApi: (deliveries: any[]) => deliveries,
resolveStopProductsForView: (products: any[]) => products || [],
resolveLoadViewAddress: (_plan: any, load: any) => load.address || '',
resolveDeliveryViewAddress: (_plan: any, stop: any) => stop.address || '',
getContract: read,
getQuote: read,
fetchProductDetail: read,
listContractApproval: async () => ({ data: [] }),
ElMessage: Object.fromEntries(
['warning', 'error', 'success', 'info'].map((k) => [
k,
(...args: any[]) => messages.push([k, ...args])
])
),
fetchGetMenuCodes: async () => ({ all: true }),
canEditPlan: () => true,
canArrangeTransport: () => true,
canWaitNotice: () => true,
canCancelWaitNotice: () => true,
canVoidPlan: () => true,
canEdit: (p: any) => !!p,
canSave: (a: boolean, u: boolean, s: boolean, n: number) => a && !u && !s && n > 0,
certPayload: (p: any) => JSON.parse(JSON.stringify(p)),
isHazardProduct: (p: any) => !!p.isToxic,
enrichPlanProductsHazardFromCrm: async (p: any) => p,
isHazardCertReady: () => false,
planHasHazardProducts: (p: any) => !!p?.length,
buildPlanCard: () => ({}),
splitMpPlanDetailActions: () => ({ primary: [], more: [] }),
usePlPlanArrangeActions: (options: any) => arrangeHarness(planApi, options),
showCrmConfirmDialog: async () => {},
defineExpose() {},
defineProps: () => props,
defineEmits:
() =>
(...v: any[]) =>
pushes.push(v),
document: { querySelector: () => null },
window: { removeEventListener() {}, scrollTo() {} },
...overrides,
planApi
})
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.flatMap((d) =>
ts.isObjectBindingPattern(d.name)
? d.name.elements.map((e) => e.name.getText(ast))
: [d.name.getText(ast)]
)
: []
)
const code = ts.transpile(statements.map((s) => s.getText(ast)).join('\n'), {
target: ts.ScriptTarget.ESNext
})
const scope = vue.effectScope()
const api: any = scope.run(() =>
new Function(...Object.keys(bindings), `${code};return {${names.join(',')}}`)(
...Object.values(bindings)
)
)
return {
...api,
props: bindings.defineProps(),
messages,
planApi,
route,
calls,
writes,
pushes,
ready: api.onShellReady || api.onReady,
unmount: () => {
unmounts.forEach((fn) => fn())
scope.stop()
}
}
}
function arrangeHarness(planApi: any, options: any = undefined, overrides: any = {}) {
const text = readFileSync(
new URL('../src/composables/usePlPlanArrangeActions.ts', import.meta.url),
'utf8'
)
const ast = ts.createSourceFile('arrange.ts', text, ts.ScriptTarget.Latest, true)
const code = ts.transpile(
ast.statements
.filter((s) => !ts.isImportDeclaration(s))
.map((s) => s.getText(ast))
.join('\n')
.replace(/export /g, ''),
{ target: ts.ScriptTarget.ESNext }
)
const env = {
...Object.fromEntries(
Object.entries(vue).filter(([k]) => /^[a-zA-Z_$][\w$]*$/.test(k) && k !== 'version')
),
planApi,
pickBxData: (r: any) => r.data ?? r,
useUserStore: () => ({ accessToken: 'session', info: { id: 'user' } }),
useDictStore: () => ({ getLabel: () => '' }),
ElMessage: { success() {}, warning() {}, error() {} },
...overrides
}
delete (env as any).default
return new Function(...Object.keys(env), `${code}; return usePlPlanArrangeActions`)(
...Object.values(env)
)(options)
}
test('transport: ready gate, idempotence and sync reset', async () => {
const h = harness('plan/detail')
h.route.params.id = 'B'
await flush()
assert.deepEqual(h.calls, [])
h.onShellReady()
await flush()
h.onShellReady()
await flush()
assert.deepEqual(h.calls, ['B'])
h.moreOpen.value = h.voidOpen.value = h.vehicleOpen.value = true
h.route.params.id = 'C'
assert.equal(h.plan.value, null)
assert.equal(h.moreOpen.value, false)
assert.equal(h.voidOpen.value, false)
assert.equal(h.vehicleOpen.value, false)
await flush()
assert.deepEqual(h.calls, ['B', 'C'])
h.route.query.tab = 'info'
await flush()
assert.deepEqual(h.calls, ['B', 'C'])
h.unmount()
})
for (const result of [null, {}, { id: 'WRONG' }])
test(`transport invalid success ${JSON.stringify(result)}`, async () => {
const h = harness('plan/detail', { planApi: { get: async () => ({ data: result }) } })
h.onShellReady()
await flush()
assert.equal(h.plan.value, null)
assert.ok(h.errorText.value)
h.unmount()
})
for (const kind of ['wait', 'cancel-wait', 'void'])
test(`transport ${kind} delayed confirmation`, async () => {
const confirm = deferred()
const h = harness('plan/detail', { showCrmConfirmDialog: () => confirm.promise })
h.onShellReady()
await flush()
const pending = kind === 'void' ? h.confirmVoidPlan() : h.runAction(kind)
confirm.resolve()
h.route.params.id = 'B'
await pending
await flush()
assert.deepEqual(h.writes, [])
h.unmount()
})
test('arrange opt-in old internal info cannot publish after reset', async () => {
const old = deferred()
const a = arrangeHarness({ getArrangeInfo: () => old.promise }, { isolateRecord: true })
const pending = a.loadArrangeActions('A', { id: 'A', transportStatus: 'arranged' })
a.resetArrangeActions()
old.resolve({ data: { driverUrl: 'OLD', siteRecords: [{ siteUrl: 'OLD' }] } })
await pending
assert.equal(a.driverUrl.value, '')
assert.deepEqual(a.siteRecords.value, [])
})
test('cert upload old successful save cannot close or notify new record', async () => {
const saved = deferred()
const h = harness('plan-cert/CertUpload', {
planApi: { updateProductCerts: () => saved.promise }
})
await flush()
const pending = h.save()
h.props.planId = 'B'
await flush()
saved.resolve()
await pending
assert.deepEqual(h.pushes, [])
assert.deepEqual(h.messages, [])
h.unmount()
})
function detailLoader(read: (id: string) => Promise<any>, useInitialPlan = false) {
return () => {
const state: any = Object.fromEntries(
[
'plan',
'products',
'transport',
'freightSettlement',
'vehicleBundle',
'dibangRows',
'ioOrder'
].map((k) => [k, vue.ref(k === 'products' || k === 'dibangRows' ? [] : null)])
)
state.loading = vue.ref(false)
state.resetDetail = () => {
state.plan.value = null
}
state.loadDetail = async (id: string, options?: any) => {
state.loading.value = true
try {
state.plan.value =
useInitialPlan && options?.initialPlan ? options.initialPlan : await read(id)
} finally {
state.loading.value = false
}
}
return state
}
}
for (const reject of [false, true])
test(`cert detail unmount guards old ${reject ? 'error' : 'success'} and contract`, async () => {
const slow = deferred(),
contract = deferred()
const h = harness('plan-cert/CertDetail', {
usePlPlanViewDetail: detailLoader(() => slow.promise),
getContract: () => contract.promise
})
await flush()
const pending = h.openContract('C')
h.unmount()
if (reject) slow.reject(new Error('old'))
else slow.resolve({ id: 'A' })
contract.resolve({ data: { id: 'C' } })
await pending
await flush()
assert.equal(h.contractOpen.value, false)
assert.equal(h.error.value, '')
assert.equal(h.plan.value, null)
})
for (const reject of [false, true])
for (const transition of ['B', 'ABA', 'retry', 'unmount'])
test(`transport ${transition}: old ${reject ? 'error/finally' : 'success'} cannot publish`, async () => {
const old = deferred(),
fresh = deferred()
let n = 0
const h = harness('plan/detail', {
planApi: { get: () => (++n === 1 ? old.promise : fresh.promise) }
})
h.onShellReady()
await flush()
if (transition === 'unmount') h.unmount()
else if (transition === 'retry') void h.loadDetail()
else {
h.route.params.id = 'B'
if (transition === 'ABA') h.route.params.id = 'A'
}
if (reject) old.reject(new Error('old'))
else old.resolve({ data: { id: 'A' } })
await flush()
assert.equal(h.plan.value, null)
assert.equal(h.errorText.value, '')
if (transition !== 'unmount') {
assert.equal(h.loading.value, true)
fresh.resolve({ data: { id: h.route.params.id } })
await flush()
assert.equal(h.plan.value.id, h.route.params.id)
assert.equal(h.loading.value, false)
h.unmount()
}
})
for (const kind of ['wait', 'cancel-wait', 'void'])
for (const reject of [false, true])
test(`transport ${kind}: old issued write ${reject ? 'rejects' : 'resolves'} after switching`, async () => {
const old = deferred()
const apiName =
kind === 'wait' ? 'waitNotice' : kind === 'void' ? 'cancel' : 'cancelWaitNotice'
const h = harness('plan/detail', { planApi: { [apiName]: () => old.promise } })
h.onShellReady()
await flush()
const pending = kind === 'void' ? h.confirmVoidPlan() : h.runAction(kind)
await flush()
h.route.params.id = 'B'
await flush()
h.acting.value = true
h.voidOpen.value = true
if (reject) old.reject(new Error('old'))
else old.resolve({ data: { id: 'A' } })
await pending
assert.equal(h.plan.value.id, 'B')
assert.equal(h.acting.value, true)
assert.equal(h.voidOpen.value, true)
assert.deepEqual(h.messages, [])
assert.deepEqual(h.calls, ['A', 'B'])
h.unmount()
})
for (const kind of ['wait', 'cancel-wait', 'void'])
test(`transport ${kind}: cancelled confirmation is handled`, async () => {
const h = harness('plan/detail', {
showCrmConfirmDialog: async () => {
throw new Error('cancel')
}
})
h.onShellReady()
await flush()
await (kind === 'void' ? h.confirmVoidPlan() : h.runAction(kind))
assert.deepEqual(h.writes, [])
assert.equal(h.acting.value, false)
assert.deepEqual(h.messages, [])
h.unmount()
})
test('transport reason is captured before confirmation; unrelated query does not cancel', async () => {
const confirm = deferred(),
h = harness('plan/detail', { showCrmConfirmDialog: () => confirm.promise })
h.onShellReady()
await flush()
h.voidReason.value = 'original'
const pending = h.confirmVoidPlan()
h.voidReason.value = 'changed'
h.route.query.tab = 'x'
confirm.resolve()
await pending
assert.deepEqual(h.writes, [['A', { cancelReason: 'original' }]])
h.unmount()
})
test('transport entry rejects missing loading busy and denied actions', async () => {
const h = harness('plan/detail', {
canWaitNotice: () => false,
canCancelWaitNotice: () => false,
canVoidPlan: () => false
})
await h.runAction('wait')
h.onShellReady()
await flush()
for (const kind of ['wait', 'cancel-wait', 'void']) await h.runAction(kind)
await h.confirmVoidPlan()
assert.deepEqual(h.writes, [])
h.unmount()
})
test('arrange opt-in ABA cancels old get and reuses existing plan', async () => {
const old = deferred()
let reads = 0
const a = arrangeHarness(
{
get: () => {
reads++
return old.promise
},
getArrangeInfo: async (id: string) => ({ data: { driverUrl: id } })
},
{ isolateRecord: true }
)
const pending = a.loadArrangeActions('A')
await a.loadArrangeActions('B', { id: 'B', transportStatus: 'arranged' })
await a.loadArrangeActions('A', { id: 'A', transportStatus: 'arranged' })
old.resolve({ data: { id: 'A', transportStatus: 'pending' } })
await pending
assert.equal(a.driverUrl.value, 'A')
assert.equal(a.plan.value.transportStatus, 'arranged')
assert.equal(reads, 1)
})
test('arrange default PC retains legacy publication and lookup behavior', async () => {
const old = deferred()
let reads = 0
const a = arrangeHarness({
get: async () => {
reads++
return { data: { id: 'A', transportStatus: 'arranged' } }
},
getArrangeInfo: () => old.promise
})
const pending = a.loadArrangeActions('A')
await flush()
a.resetArrangeActions()
old.resolve({ data: { driverUrl: 'legacy' } })
await pending
assert.equal(a.driverUrl.value, 'legacy')
assert.equal(reads, 1)
})
test('arrange QR nextTick cannot copy newly bound dialog after reset', async () => {
let copies = 0
const a = arrangeHarness({}, { isolateRecord: true })
const pending = a.copyPoundSlipQrcodeForRecord({ dbInventoryId: 'A' })
a.resetArrangeActions()
a.qrcodeDialogRef.value = {
copyQrcodeImage: () => {
copies++
}
}
await pending
assert.equal(copies, 0)
assert.equal(a.qrcodeVisible.value, false)
})
for (const transition of ['B', 'ABA', 'close-reopen', 'unmount', 'invalidate', 'permission'])
for (const reject of [false, true])
test(`upload ${transition}: stale save ${reject ? 'error' : 'success'}`, async () => {
const old = deferred()
const h = harness('plan-cert/CertUpload', {
planApi: { updateProductCerts: () => old.promise }
})
await flush()
const pending = h.save()
if (transition === 'B' || transition === 'ABA') {
h.props.planId = 'B'
if (transition === 'ABA') h.props.planId = 'A'
}
if (transition === 'close-reopen') {
h.props.modelValue = false
h.props.modelValue = true
}
if (transition === 'unmount') h.unmount()
if (transition === 'invalidate') h.invalidateRecord()
if (transition === 'permission') h.props.permitted = false
await flush()
h.saving.value = true
if (reject) old.reject(new Error('old'))
else old.resolve()
await pending
assert.deepEqual(h.pushes, [])
assert.deepEqual(h.messages, [])
assert.equal(h.error.value, '')
assert.equal(h.saving.value, true)
if (transition !== 'unmount') h.unmount()
})
for (const invalid of [{ id: 'B', products: [] }, { id: 'A' }, null])
test(`upload invalid metadata response ${JSON.stringify(invalid)} blocks saving`, async () => {
const h = harness('plan-cert/CertUpload', { planApi: { get: async () => ({ data: invalid }) } })
await flush()
await h.save()
assert.ok(h.error.value)
assert.equal(h.allowed.value, false)
assert.deepEqual(h.writes, [])
h.unmount()
})
test('upload strict enrichment failure is explicit, retriable and blocks write', async () => {
let strictArg: any
const h = harness('plan-cert/CertUpload', {
enrichPlanProductsHazardFromCrm: async (_: any, strict: any) => {
strictArg = strict
throw new Error('metadata')
}
})
await flush()
await h.save()
assert.equal(strictArg, true)
assert.ok(h.error.value)
assert.deepEqual(h.writes, [])
h.unmount()
})
test('upload keeps edits when same ID/visibility and unrelated query change; payload is captured', async () => {
const old = deferred(),
writes: any[] = []
const h = harness('plan-cert/CertUpload', {
planApi: {
updateProductCerts: (...args: any[]) => {
writes.push(args)
return old.promise
}
}
})
await flush()
h.products.value[0].purchaseCertUrl = 'original'
h.props.planId = 'A'
h.props.modelValue = true
h.route.query.tab = 'info'
await flush()
assert.equal(h.products.value[0].purchaseCertUrl, 'original')
assert.equal(h.calls.length, 1)
const pending = h.save()
h.products.value[0].purchaseCertUrl = 'changed'
old.resolve()
await pending
assert.equal(writes[0][0], 'A')
assert.equal(writes[0][1][0].purchaseCertUrl, 'original')
assert.deepEqual(h.pushes, [
['update:modelValue', false],
['success', 'A']
])
h.unmount()
})
for (const reject of [false, true])
test(`upload stale metadata ${reject ? 'error' : 'success'} cannot clear new loading`, async () => {
const old = deferred(),
fresh = deferred()
let n = 0
const h = harness('plan-cert/CertUpload', {
planApi: { get: () => (++n === 1 ? old.promise : fresh.promise) }
})
h.props.planId = 'B'
if (reject) old.reject(new Error('old'))
else old.resolve({ data: { id: 'A', products: [] } })
await flush()
assert.equal(h.loading.value, true)
assert.equal(h.plan.value, null)
assert.equal(h.error.value, '')
await h.save()
assert.deepEqual(h.writes, [])
fresh.resolve({ data: { id: 'B', products: [] } })
await flush()
assert.equal(h.plan.value.id, 'B')
h.unmount()
})
for (const reject of [false, true])
test(`cert detail same-record retry isolates old loader ${reject ? 'error' : 'success/finally'}`, async () => {
const old = deferred(),
fresh = deferred()
let n = 0
const h = harness('plan-cert/CertDetail', {
usePlPlanViewDetail: detailLoader(() => (++n === 1 ? old.promise : fresh.promise))
})
await flush()
const pending = h.reload()
if (reject) old.reject(new Error('old'))
else old.resolve({ id: 'A', planNo: 'OLD' })
await flush()
assert.equal(h.plan.value, null)
assert.equal(h.loading.value, true)
assert.equal(h.error.value, '')
fresh.resolve({ id: 'A', planNo: 'FRESH' })
await pending
assert.equal(h.plan.value.planNo, 'FRESH')
h.unmount()
})
test('cert detail parent sync invalidation prevents stale contract and linked callbacks', async () => {
const contract = deferred(),
linked = deferred()
const h = harness('plan-cert/CertDetail', {
usePlPlanViewDetail: detailLoader(async (id) => ({ id })),
getContract: async () => ({ data: { id: 'C' } }),
enrichContractReadFields: () => contract.promise,
planApi: { list: () => linked.promise }
})
await flush()
const pending = h.openContract('C')
await flush()
contract.resolve({ contract: { id: 'C' }, warnings: [] })
await flush()
h.invalidateRecord()
linked.resolve({ data: { list: [{ id: 'OLD' }], total: 1 } })
await pending
assert.equal(h.contractOpen.value, false)
assert.deepEqual(h.linkedPlans.value, [])
assert.equal(h.linkedLoading.value, false)
h.unmount()
})
test('transport local tabs retain single-line 44px targets and bordered content fits available width', () => {
const sfc = parse(
readFileSync(new URL('../src/views/m/pl/plan/detail.vue', import.meta.url), 'utf8')
).descriptor
const style = sfc.styles.find((s) => s.scoped)!.content
assert.match(
style,
/\.mp-plan-detail-page\.mp-h5-detail-page[\s\S]*?:deep\(\.van-tab\)\s*\{[^}]*flex:\s*0 0 auto !important/
)
assert.match(style, /:deep\(\.van-tab__text\)\s*\{[^}]*white-space:\s*nowrap/)
assert.match(
style,
/:deep\(\.van-tabs__nav\)\s*\{[^}]*height:\s*44px[^}]*box-sizing:\s*border-box/
)
assert.match(style, /\.mp-wv-section\s*\{[^}]*min-width:\s*0[^}]*box-sizing:\s*border-box/)
})
for (const reject of [false, true])
test(`cert detail reload resets copy busy while obsolete ${reject ? 'failure' : 'success'} preserves new copy`, async () => {
const old = deferred(),
fresh = deferred()
const h = harness('plan-cert/CertDetail', {
usePlPlanViewDetail: detailLoader(async (id) => ({ id }))
})
await flush()
const first = h.copyAction(() => old.promise)
assert.equal(h.copying.value, true)
const reload = h.reload()
assert.equal(h.copying.value, false, 'new load must synchronously reset previous copy busy')
await reload
const second = h.copyAction(() => fresh.promise)
assert.equal(h.copying.value, true)
if (reject) old.reject(new Error('old copy'))
else old.resolve()
await first
assert.equal(h.copying.value, true, 'obsolete copy must not clear new copy busy')
assert.deepEqual(h.messages, [])
fresh.resolve()
await second
assert.equal(h.copying.value, false)
h.unmount()
})
test('plan detail identity switch invalidates old reads and closes every record action', async () => {
const older = deferred()
const user = vue.reactive({ accessToken: 'old', info: { id: 'old-user' } })
let reads = 0
const h = harness('plan/detail', {
useUserStore: () => user,
planApi: {
get: () => (++reads === 1 ? older.promise : Promise.resolve({ id: 'A', marker: 'new' }))
},
loadMobileDetailReads: async () => ({ errors: [] })
})
h.ready()
await flush()
h.moreOpen.value = h.voidOpen.value = h.vehicleOpen.value = true
user.accessToken = 'new'
assert.equal(h.moreOpen.value, false)
assert.equal(h.voidOpen.value, false)
assert.equal(h.vehicleOpen.value, false)
await flush()
older.resolve({ id: 'A', marker: 'old' })
await flush()
assert.equal(h.plan.value.marker, 'new')
assert.equal(reads, 2)
user.accessToken = ''
assert.equal(h.plan.value, null)
assert.equal(h.loading.value, false)
assert.match(h.errorText.value, /登录状态/)
await h.runAction('wait')
assert.deepEqual(h.writes, [])
h.unmount()
})
test('cert detail requests mobile metadata once and passes that snapshot to the shared read loader', async () => {
const snapshot = { id: 'A', products: [], deliveries: [] }
const reads: any[] = [],
snapshots: any[] = []
const loader = detailLoader(async () => snapshot)
const h = harness('plan-cert/CertDetail', {
planApi: {
get: async (...args: any[]) => {
reads.push(args)
return { data: snapshot }
}
},
usePlPlanViewDetail: () => {
const detail = loader()
const load = detail.loadDetail
detail.loadDetail = (id: string, options: any) => {
snapshots.push(options?.initialPlan)
return load(id)
}
return detail
}
})
await flush()
assert.deepEqual(reads, [['A', true]])
assert.equal(snapshots[0], snapshot)
assert.equal(h.plan.value.id, 'A')
h.unmount()
})