const { test } = require("node:test") const assert = require("node:assert/strict") const fs = require("node:fs") const path = require("node:path") const ts = require("typescript") const vue = require("vue") const { parse, compileScript, compileTemplate } = require("@vue/compiler-sfc") const root = path.join(__dirname, "../src") const filename = path.join(root, "views/wallet/wallet-qb/index.vue") const descriptor = parse(fs.readFileSync(filename, "utf8"), { filename }).descriptor const source = compileScript(descriptor, { id: "wallet-qb-statistics-test" }).content function load(source, mocks) { const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, esModuleInterop: true }, }).outputText const mod = { exports: {} } const autoImports = ["ref", "reactive", "onMounted"] new Function("module", "exports", "require", ...autoImports, compiled)( mod, mod.exports, id => id in mocks ? mocks[id] : require(id), ...autoImports.map(key => vue[key]), ) return mod.exports } function mount() { const requests = [] const lists = [] const errors = [] const table = load(fs.readFileSync(path.join(root, "hooks/table.ts"), "utf8"), { "element-plus": { ElMessage: { error: message => errors.push(message) } }, "/@/utils/other": {}, "../utils/formatTime": {}, }) const page = load(source, { "/@/hooks/table": table, "/@/api/wallet/wallet-manage": { fetchListJoinQb: query => { lists.push(query); return Promise.resolve({ data: { records: [], total: 100 } }) }, qbTotal: (query, signal) => new Promise((resolve, reject) => requests.push({ query, signal, resolve, reject })), }, "/@/hooks/batchExport": { useBatchExport: () => ({}) }, "/@/components/AsyncTaskProgress.vue": {}, "/@/hooks/message": { useMessage: () => ({ error: message => errors.push(message) }) }, "/@/hooks/dict": { useDict: () => ({}) }, "/@/utils/formatMoney": {}, "/@/utils/formatTime": { formatDate: () => "2026-10" }, }) const renderer = vue.createRenderer({ createElement: () => ({}), createText: () => ({}), createComment: () => ({}), setText() {}, setElementText() {}, patchProp() {}, insert() {}, remove() {}, parentNode: () => null, nextSibling: () => null, }) let state const app = renderer.createApp({ setup() { state = page.default.setup({}, { expose() {} }); return () => null }, }) app.mount({}) return { state, requests, lists, errors, close: () => app.unmount() } } const settle = () => new Promise(resolve => setImmediate(resolve)) const result = total => ({ code: 0, data: { total, eatTotal: total, payTotal: 0, topupTotal: 0 } }) test("列表首次加载、筛选和翻页均不触发全量余额统计", async () => { const page = mount() try { await settle() assert.equal(page.lists.length, 1) await page.state.getDataList() page.state.currentChangeHandle(2) page.state.sizeChangeHandle(20) await settle() assert.equal(page.lists.length, 4) assert.equal(page.requests.length, 0) } finally { page.close() } }) test("统计请求在途去重,数组筛选冻结且加载完成前不展示旧统计", async () => { const page = mount() try { Object.assign(page.state.state.queryForm, { deptIds: ["11"], walletTypeIdList: ["9007199254740993999"], isInclude: false }) page.state.viewBalanceStatistics() page.state.viewBalanceStatistics() assert.equal(page.requests.length, 1) assert.equal(page.state.balanceStatisticsLoading.value, true) assert.equal(page.state.balanceStatisticsLoaded.value, false) page.state.state.queryForm.deptIds.push("12") assert.deepEqual(page.requests[0].query, { walletRecordMonth: "2026-10", deptIds: ["11"], walletTypeIdList: ["9007199254740993999"], isInclude: false, }) page.requests[0].resolve(result(1200)) await settle() assert.equal(page.state.balanceStatistics.total, 1200) assert.equal(page.state.balanceStatisticsLoaded.value, true) assert.equal(page.state.balanceStatisticsLoading.value, false) } finally { page.close() } }) test("关闭和重开立即取消旧请求,旧成功或失败不污染新统计与 loading", async () => { const page = mount() try { page.state.viewBalanceStatistics() page.state.drawerClose() assert.equal(page.requests[0].signal.aborted, true) page.state.state.queryForm.walletRecordMonth = "2026-09" page.state.viewBalanceStatistics() page.requests[0].reject(new Error("旧请求失败")) await settle() assert.equal(page.state.balanceStatisticsLoading.value, true) assert.deepEqual(page.errors, []) page.state.drawerClose() page.state.viewBalanceStatistics() page.requests[2].resolve(result(3000)) await settle() page.requests[1].resolve(result(1000)) await settle() assert.equal(page.state.balanceStatistics.total, 3000) assert.equal(page.state.balanceStatisticsLoaded.value, true) assert.equal(page.state.balanceStatisticsLoading.value, false) } finally { page.close() } }) test("网络失败展示可读错误并可重试,卸载取消在途请求", async () => { const page = mount() page.state.viewBalanceStatistics() page.requests[0].reject(new Error("查询超时")) await settle() assert.deepEqual(page.errors, ["查询超时"]) assert.equal(page.state.balanceStatisticsLoaded.value, false) assert.equal(page.state.balanceStatisticsLoading.value, false) page.state.viewBalanceStatistics() assert.equal(page.requests.length, 2) page.close() assert.equal(page.requests[1].signal.aborted, true) page.requests[1].resolve(result(2000)) await settle() assert.equal(page.state.balanceStatisticsLoaded.value, false) }) test("统计 API 保留路径与 POST 参数并传递取消信号,实际 Vue 模板可编译", async () => { const requests = [] const api = load(fs.readFileSync(path.join(root, "api/wallet/wallet-manage.ts"), "utf8"), { "/@/utils/request": options => { requests.push(options); return Promise.resolve({}) }, "/@/api/asyncTask": {}, }) const controller = new AbortController() const query = { walletRecordMonth: "2026-10", deptIds: ["11"] } await api.qbTotal(query, controller.signal) assert.equal(requests[0].url, "/wallet/wallet-manage/qb-total") assert.equal(requests[0].method, "post") assert.equal(requests[0].data, query) assert.equal(requests[0].signal, controller.signal) const script = compileScript(descriptor, { id: "wallet-qb-statistics-test" }) assert.deepEqual(compileTemplate({ source: descriptor.template.content, filename, id: "wallet-qb-statistics-test", compilerOptions: { bindingMetadata: script.bindings } }).errors, []) })