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 { parse, compileScript } = require("@vue/compiler-sfc") const vue = require("vue") // 执行页面真实的 setup 和 Vue 挂载/异步组件生命周期,子表只记录请求参数,无需新增浏览器测试依赖。 const filename = path.join(__dirname, "../src/views/hard/hard-sh/index.vue") const descriptor = parse(fs.readFileSync(filename, "utf8"), { filename }).descriptor const source = compileScript(descriptor, { id: "hard-sh-lifecycle-test" }).content const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, esModuleInterop: true }, }).outputText function mountPage() { const requests = [] const exportQueries = [] const child = (kind) => vue.defineComponent({ setup(_, { expose }) { const query = (params) => requests.push({ kind, params: JSON.parse(JSON.stringify(params)) }) expose({ doSearch: query, doCancel: query }) return () => vue.h("table") }, }) const day = child("day") const month = child("month") const stubRequire = (id) => { if (id.includes("hard-sh-by-day.vue")) return day if (id.includes("hard-sh-by-month.vue")) return month if (id.endsWith("AsyncTaskProgress.vue")) return {} if (id.includes("hooks/batchExport")) return { useBatchExport: () => ({ exportTaskConfig: {}, submitExport: query => exportQueries.push(JSON.parse(JSON.stringify(query))), handleExportSuccess() {}, handleExportFailure() {} }), } if (id.includes("api/hard/hard-total")) return {} return require(id) } const loaded = { exports: {} } const autoImports = ["ref", "reactive", "watch", "defineAsyncComponent", "onMounted", "nextTick"] new Function("module", "exports", "require", ...autoImports, compiled)( loaded, loaded.exports, stubRequire, ...autoImports.map((key) => vue[key]), ) let bindings const renderer = vue.createRenderer({ createElement: (type) => ({ type, children: [] }), createText: (text) => ({ text }), createComment: (text) => ({ text }), setText: (node, text) => { node.text = text }, setElementText: (node, text) => { node.text = text }, parentNode: (node) => node.parent, nextSibling: (node) => node.parent?.children[node.parent.children.indexOf(node) + 1], patchProp() {}, insert(node, parent, anchor = null) { if (node.parent) node.parent.children.splice(node.parent.children.indexOf(node), 1) node.parent = parent const index = anchor ? parent.children.indexOf(anchor) : -1 parent.children.splice(index < 0 ? parent.children.length : index, 0, node) }, remove(node) { if (node.parent) node.parent.children.splice(node.parent.children.indexOf(node), 1) }, }) const host = vue.defineComponent({ setup() { bindings = loaded.exports.default.setup({}, { expose() {} }) bindings.draggableDepMchTreeRef.value = { resetData() {} } return () => bindings.activeTab.value === "MCH" ? vue.h(bindings.HardShByDay, { ref: bindings.hardShByDayRef }) : vue.h(bindings.HardShByMonth, { ref: bindings.hardShByMonthRef }) }, }) const app = renderer.createApp(host) app.mount({ children: [] }) return { bindings, requests, exportQueries, close: () => app.unmount() } } async function settle() { await new Promise((resolve) => setImmediate(resolve)) await vue.nextTick() } test("初次打开异步子表只查询一次,挂载前点击查询也不会报错", async () => { const page = mountPage() try { assert.doesNotThrow(() => page.bindings.search()) await settle() assert.equal(page.requests.length, 1) assert.equal(page.requests[0].kind, "day") assert.equal(page.requests[0].params.payTimeList.length, 2) assert.equal(page.requests[0].params.payTimeYearStr, "") } finally { page.close() } }) test("切换日/月子表每次只查询一次,查询条件重置后再发送", async () => { const page = mountPage() try { await settle() page.bindings.state.queryForm.merchantId = "999" page.bindings.activeTab.value = "MONTH" await settle() assert.equal(page.requests.length, 2) assert.equal(page.requests[1].kind, "month") assert.equal(page.requests[1].params.merchantId, undefined) assert.match(page.requests[1].params.payTimeYearStr, /^\d{4}$/) assert.deepEqual(page.requests[1].params.payTimeList, []) page.bindings.activeTab.value = "MCH" await settle() assert.equal(page.requests.length, 3) assert.equal(page.requests[2].kind, "day") assert.equal(page.requests[2].params.payTimeYearStr, "") assert.equal(page.requests[2].params.payTimeList.length, 2) } finally { page.close() } }) test("按月重置和导出清除隐藏日范围,按商户导出保留精确时间边界", async () => { const page = mountPage() try { await settle() page.bindings.activeTab.value = "MONTH" await settle() page.bindings.cancel() assert.deepEqual(page.requests.at(-1).params.payTimeList, []) assert.match(page.requests.at(-1).params.payTimeYearStr, /^\d{4}$/) page.bindings.state.queryForm.payTimeList = ["2026-09-01 00:00:00", "2026-10-01 00:00:00"] page.bindings.exportExcel() assert.deepEqual(page.exportQueries[0].payTimeList, []) assert.equal(page.bindings.exportTaskConfig.redisKey, "hard:export:merchant-month") page.bindings.activeTab.value = "MCH" await settle() const timeRange = ["2026-09-01 00:00:00", "2026-10-01 00:00:00"] Object.assign(page.bindings.state.queryForm, { payTimeList: timeRange, payTimeYearStr: "2025", userDeptIds: ["1875013738914156546"], isInclude: false }) page.bindings.exportExcel() assert.deepEqual(page.exportQueries[1].payTimeList, timeRange) assert.equal(page.exportQueries[1].payTimeYearStr, "") assert.deepEqual(page.exportQueries[1].userDeptIds, ["1875013738914156546"]) assert.equal(page.exportQueries[1].isInclude, false) assert.equal(page.bindings.exportTaskConfig.redisKey, "hard:export:merchant") } finally { page.close() } }) test("部门筛选和手动刷新仍各发出一次请求", async () => { const page = mountPage() try { await settle() page.bindings.handleGetSelectNode({ id: "10" }, [{ id: "11" }], false, "DEPT") assert.equal(page.requests.length, 2) assert.deepEqual(page.requests[1].params.userDeptIds, ["11"]) assert.equal(page.requests[1].params.isInclude, true) page.bindings.search() assert.equal(page.requests.length, 3) } finally { page.close() } })