import assert from 'node:assert/strict' import test from 'node:test' import { readFileSync } from 'node:fs' import { ref, nextTick } from 'vue' import ts from 'typescript' import { parse } from '@vue/compiler-sfc' import * as routeUtils from '../src/utils/mp-webview' import * as listState from '../src/views/m/crm/quote/listState' function harness(mode: 'manage' | 'approve') { const base = mode === 'approve' ? '/m/crm/quote/approval' : '/m/crm/quote' let route: any = { path: base, query: {} } let leave: any, unmount: any const hooks = new Set() const scrolls: number[] = [] const bindings = { ref, nextTick, useRoute: () => route, useRouter: () => ({ afterEach(fn: any) { hooks.add(fn) return () => hooks.delete(fn) } }), useUserStore: () => ({ accessToken: 'token', getUserInfo: { id: 'u', tenantNo: 'T' } }), onBeforeRouteLeave: (fn: any) => { leave = fn }, onBeforeUnmount: (fn: any) => { unmount = fn }, window: { scrollY: 720, scrollTo: ({ top }: any) => scrolls.push(top) } } const source = readFileSync( new URL('../src/views/m/crm/useListReturnState.ts', import.meta.url), 'utf8' ) .replace(/^import .*$/gm, '') .replace('export function', 'function') const useState = new Function( ...Object.keys(bindings), ts.transpile(source, { target: ts.ScriptTarget.ESNext }) + ';return useListReturnState' )(...Object.values(bindings)) // Evaluate the real quote call-site so this catches missing predicate wiring, not only the helper. const ast = ts.createSourceFile( 'list.ts', parse( readFileSync(new URL('../src/views/m/crm/quote/MpQuoteList.vue', import.meta.url), 'utf8') ).descriptor.scriptSetup!.content, ts.ScriptTarget.Latest, true ) const statement = ast.statements.find( (n) => ts.isVariableStatement(n) && n.declarationList.declarations.some((d) => d.name.getText(ast) === 'listReturnState') )! function create() { const keyword = ref(''), query = ref({}), view = ref(mode === 'approve' ? 'submitted' : 'all'), tabQueries = ref({}) const pageNum = ref(3), expandedIds = ref(new Set(['Q1'])), hasMore = ref(true) const requests: any[] = [] const local = { ...listState, ...routeUtils, props: { mode }, keyword, query, view, tabQueries, pageNum, expandedIds, hasMore, useListReturnState: useState, requestSnapshot: () => JSON.stringify([keyword.value, query.value, view.value]), fetchPage: async (reset: boolean) => { pageNum.value = reset ? 1 : pageNum.value + 1 requests.push({ page: pageNum.value, keyword: keyword.value, query: query.value }) expandedIds.value = new Set() return true } } const state = new Function( ...Object.keys(local), ts.transpile(statement.getText(ast), { target: ts.ScriptTarget.ESNext }) + ';return listReturnState' )(...Object.values(local)) return { state, keyword, query, pageNum, expandedIds, requests } } function navigate(to: any) { const from = route route = to for (const hook of hooks) hook(to, from) } function detail() { const to = { path: '/m/crm/quote/Q1', query: { from: mode } } leave(to) navigate(to) unmount() } return { base, create, navigate, detail, scrolls } } for (const mode of ['manage', 'approve'] as const) { test(`${mode}: quote/customer/quote round trip restores search, filters, pages and scroll`, async () => { const h = harness(mode), first = h.create() first.keyword.value = '移动验收' first.query.value = { finalAmountMin: '10' } h.detail() h.navigate({ path: '/m/crm/customer/C1', query: { from: 'quote', returnQuoteId: 'Q1', quoteFrom: mode } }) h.navigate({ path: '/m/crm/quote/Q1', query: { from: mode } }) h.navigate({ path: h.base, query: {} }) const back = h.create() await back.state.restore() assert.equal(back.keyword.value, '移动验收') assert.deepEqual(back.query.value, { finalAmountMin: '10' }) assert.deepEqual( back.requests.map((r: any) => r.page), [1, 2, 3] ) assert.deepEqual([...back.expandedIds.value], ['Q1']) assert.deepEqual(h.scrolls, [720]) }) } test('unrelated customer paths and forged quote return IDs discard the quote snapshot', async () => { for (const query of [ {}, { from: 'customer', returnQuoteId: 'Q1' }, { from: 'quote', returnQuoteId: '../Q1' } ]) { const h = harness('manage'), first = h.create() first.keyword.value = 'private' h.detail() h.navigate({ path: '/m/crm/customer/C1', query }) h.navigate({ path: '/m/crm/quote/Q1', query: {} }) h.navigate({ path: h.base, query: {} }) const back = h.create() await back.state.restore() assert.equal(back.keyword.value, '') } })