import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { Transport } from "../src/transport"; import { mergeConfig } from "../src/config"; import { startPageSession, getPageSession, onSegmentChange } from "../src/page"; import type { PageSession } from "../src/page"; import { startBusinessTracking } from "../src/business"; // business.ts(spec §1.1-§1.3 / §1.5):contact_click(链接协议前缀 + beacon flush)、 // CF7 全过程(form_start 去重 + _ctk_sid 空占位 / submit capture 生成 submission_id / // wpcf7statuschanged 终态映射)、product_view(路径段匹配 + onSegmentChange 切段)。 // 事件属性恰合服务端白名单(constants.py EVENT_PROP_WHITELIST + _biz_props_ok 值域: // channel 枚举 / form_id cf7-N / submission_id uuid4 小写 / product_id slug≤100)。 // 注意:startPageSession/startBusinessTracking 安装的监听与 history 包装跨用例存留 // (模块级 hooksInstalled),旧用例监听把事件推入旧 transport(信封 site 不同——按 // envelope site_id 过滤免疫);每用例独立 siteId(沿 page.test.ts / stay.test.ts 模式)。 // isTrusted:happy-dom 合成事件默认 false——dispatch 前 defineProperty 绕过(同 // page.test.ts persisted 模式),并保留 isTrusted=false 不触发的守护断言。 const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; const UUID4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; let fetchMock: ReturnType; let beacon: ReturnType; function stubNavigator() { beacon = vi.fn().mockReturnValue(true); vi.stubGlobal("navigator", { userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", language: "zh-CN", sendBeacon: beacon, }); } function mkTransport(siteId: string) { // flushMs 拉大到 24h:业务事件只入队,flush 全部由用例显式驱动(beacon 除外) return new Transport( mergeConfig({ siteId, endpoint: "/api/collect/batch", batchSize: 50, flushMs: 86_400_000 }) ); } /** fetch 路径(显式 t.flush())事件汇总(按信封 site_id 过滤,免疫旧 transport 串扰) */ function fetchedEvents(siteId: string) { const out: Record[] = []; for (const call of fetchMock.mock.calls as unknown as [string, RequestInit][]) { const body = JSON.parse(call[1].body as string); if (body.site_id === siteId) out.push(...body.events); } return out; } /** beacon 路径(contact_click 的 flush(true))事件汇总 */ async function beaconEvents(siteId: string) { const out: Record[] = []; for (const [, blob] of beacon.mock.calls as unknown as [string, Blob][]) { const body = JSON.parse(await blob.text()); if (body.site_id === siteId) out.push(...body.events); } return out; } /** URL 重置:走原型方法绕开 page.ts 的 pushState 包装——不触发切段杂事件 */ function setUrl(url: string) { const proto = Object.getPrototypeOf(window.history) as { replaceState: (data: unknown, unused: string, url?: string | URL | null) => void; }; proto.replaceState.call(window.history, {}, "", url); } function mkLink(href: string) { const a = document.createElement("a"); a.href = href; document.body.appendChild(a); return a; } function mkForm(opts: { action?: string; id?: string } = {}) { const form = document.createElement("form"); if (opts.action !== undefined) form.setAttribute("action", opts.action); if (opts.id !== undefined) form.id = opts.id; form.className = "wpcf7-form init"; // CF7 真实形态(class 含 wpcf7-form) const input = document.createElement("input"); input.name = "your-name"; form.appendChild(input); document.body.appendChild(form); return form; } function ctkSidField(form: HTMLFormElement) { return form.querySelector('input[name="_ctk_sid"]') as HTMLInputElement | null; } function click(el: Element) { el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); } /** isTrusted=true 的合成事件(happy-dom 默认 false) */ function trusted(type: string, el: Element) { const ev = new Event(type, { bubbles: true }); Object.defineProperty(ev, "isTrusted", { value: true }); el.dispatchEvent(ev); } function submit(form: HTMLFormElement) { form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true })); } /** CF7 6.x 状态事件(bubbles:true + detail.status——线上 6.1.6 源码实测形态) */ function cf7status(form: HTMLFormElement, status: string) { form.dispatchEvent( new CustomEvent("wpcf7statuschanged", { detail: { status }, bubbles: true }) ); } describe("business / startBusinessTracking", () => { beforeEach(() => { localStorage.clear(); vi.useFakeTimers(); fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200 } as Response); vi.stubGlobal("fetch", fetchMock); stubNavigator(); }); afterEach(() => { vi.unstubAllGlobals(); vi.useRealTimers(); document.body.innerHTML = ""; // 清掉本用例挂的链接/表单 }); // 必须为本文件首例:startBusinessTracking 先于 startPageSession(异常接线顺序) // ——getPageSession() 为 null 时无从归属,静默不发。依赖模块级 current 尚为 null。 it("未启动 PageSession 时静默不发(接线顺序守卫)", () => { const t = mkTransport("biz-nops"); startBusinessTracking(t); click(mkLink("https://wa.me/8613800138000")); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); trusted("focusin", form.querySelector('input[name="your-name"]')!); submit(form); cf7status(form, "mail_sent"); t.flush(); expect(fetchedEvents("biz-nops")).toHaveLength(0); }); // ---- page.ts onSegmentChange(spec §1.3:触发点钉死在 onRouteChange 新段创建后) ---- it("onSegmentChange:切段后以新段回调;初始 startPageSession/同 path 不触发;回调 throw 不冒泡不阻断后续", () => { setUrl("/seg-a"); const t = mkTransport("biz-seg"); startPageSession(t); const seen: PageSession[] = []; onSegmentChange(() => { throw new Error("listener boom"); // 先注册:异常不得阻断后续回调 }); onSegmentChange((ps) => seen.push(ps)); startBusinessTracking(t); // business 也注册段回调(产品页判定) expect(seen).toEqual([]); // 初始 startPageSession 不触发——init 判定由消费方自理 window.history.pushState({}, "", "/seg-a?x=1"); // path 未变(仅 query)→ 不切 expect(seen).toEqual([]); expect(() => window.history.pushState({}, "", "/seg-b")).not.toThrow(); // thrower 不冒泡 expect(seen).toHaveLength(1); expect(seen[0]).toBe(getPageSession()); // 回调收到的是新段(创建之后) expect(seen[0]!.path).toBe("/seg-b"); }); // ---- contact_click(spec §1.1) ---- it("contact_click:wa.me/api.whatsapp.com/whatsapp:// 三形态(含 http)→ whatsapp + flush(true) beacon", async () => { setUrl("/cc-wa"); const t = mkTransport("biz-cc-wa"); startPageSession(t); startBusinessTracking(t); click(mkLink("https://wa.me/8613800138000")); click(mkLink("https://api.whatsapp.com/send?phone=8613800138000")); click(mkLink("whatsapp://send?phone=8613800138000")); click(mkLink("http://wa.me/8613800138000")); // https? 两形态 const ccs = (await beaconEvents("biz-cc-wa")).filter((e) => e.event_name === "contact_click"); expect(ccs).toHaveLength(4); // flush(true) 被调——beacon 路径送达(非 fetch) expect(ccs.map((e) => e.properties)).toEqual( Array(4).fill({ channel: "whatsapp" }) // 属性恰为白名单单键 ); expect(ccs[0].event_id).toMatch(UUID_RE); expect(ccs[0].occurred_at).toMatch(/^\d{4}-\d{2}-\d{2}T/); expect(ccs[0].path).toBe("/cc-wa"); expect(ccs[0].title).toBe(document.title); // 顶层归属完整(events 形态 + Attribution 展开,同 stay.ts) expect(ccs[0].site_id).toBe("biz-cc-wa"); expect(ccs[0].visitor_id).toMatch(UUID_RE); expect(ccs[0].session_id).toMatch(UUID_RE); expect(ccs[0].page_id).toBe(getPageSession()!.pageId); expect(typeof ccs[0].seq).toBe("number"); expect(ccs[0].purpose).toBe(null); expect(ccs[0].epoch).toBe(null); expect(ccs[0].grant_ref).toBe(null); }); it("contact_click:tel:/mailto:(大小写不敏感)→ phone/email", async () => { setUrl("/cc-tel"); const t = mkTransport("biz-cc-tel"); startPageSession(t); startBusinessTracking(t); click(mkLink("tel:+8613800138000")); click(mkLink("mailto:sales@example.com")); click(mkLink("MAILTO:sales@example.com")); // 大小写不敏感 const ccs = (await beaconEvents("biz-cc-tel")).filter((e) => e.event_name === "contact_click"); expect(ccs.map((e) => e.properties)).toEqual([ { channel: "phone" }, { channel: "email" }, { channel: "email" }, ]); }); it("contact_click:非链接点击/普通 http 链接/相对链接不触发", async () => { setUrl("/cc-none"); const t = mkTransport("biz-cc-none"); startPageSession(t); startBusinessTracking(t); t.flush(); // 先排空 page_view(此后 beacon 只可能由 contact_click 的 flush 触发) const div = document.createElement("div"); document.body.appendChild(div); click(div); click(mkLink("https://example.com/some-page")); click(mkLink("/relative/page")); t.flush(); expect(fetchedEvents("biz-cc-none").some((e) => e.event_name === "contact_click")).toBe(false); expect((await beaconEvents("biz-cc-none")).some((e) => e.event_name === "contact_click")).toBe(false); }); it("contact_click:链接内子元素点击经 closest 委托命中", async () => { setUrl("/cc-child"); const t = mkTransport("biz-cc-child"); startPageSession(t); startBusinessTracking(t); const a = mkLink("https://wa.me/8613800138000"); const span = document.createElement("span"); a.appendChild(span); click(span); // e.target 是 span——closest("a[href]") 委托 const ccs = (await beaconEvents("biz-cc-child")).filter((e) => e.event_name === "contact_click"); expect(ccs).toHaveLength(1); expect(ccs[0].properties).toEqual({ channel: "whatsapp" }); }); // ---- form_*(spec §1.2 / §1.5,CF7 6.x 状态机) ---- it("form_start:isTrusted focusin → 事件 + _ctk_sid 空占位;合成 focus 不触发;同表单第二次不重发", () => { setUrl("/fs"); const t = mkTransport("biz-fs"); startPageSession(t); startBusinessTracking(t); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); const input = form.querySelector('input[name="your-name"]')!; const plain = document.createElement("form"); // 非 CF7 表单:不触发 const plainInput = document.createElement("input"); plain.appendChild(plainInput); document.body.appendChild(plain); trusted("focusin", plainInput); // 非 .wpcf7-form:无事件 input.dispatchEvent(new Event("focusin", { bubbles: true })); // isTrusted=false:不触发 t.flush(); expect(fetchedEvents("biz-fs").some((e) => e.event_name === "form_start")).toBe(false); trusted("focusin", input); // 首次真实聚焦 t.flush(); const fss = fetchedEvents("biz-fs").filter((e) => e.event_name === "form_start"); expect(fss).toHaveLength(1); expect(fss[0].properties).toEqual({ form_id: "cf7-156" }); // 属性恰为白名单单键 expect(fss[0].page_id).toBe(getPageSession()!.pageId); expect(fss[0].site_id).toBe("biz-fs"); const field = ctkSidField(form); expect(field).toBeTruthy(); // _ctk_sid 空占位已注入(DOM API) expect(field!.type).toBe("hidden"); expect(field!.value).toBe(""); trusted("focusin", input); // 同表单第二次:WeakSet 去重 const input2 = document.createElement("input"); input2.name = "your-email"; form.appendChild(input2); trusted("focusin", input2); // 同表单另一字段:仍是同一 form 实例 t.flush(); expect(fetchedEvents("biz-fs").filter((e) => e.event_name === "form_start")).toHaveLength(1); }); it("form_start:isTrusted input 事件同触发(focusin 缺席路径)", () => { setUrl("/fs-input"); const t = mkTransport("biz-fs-input"); startPageSession(t); startBusinessTracking(t); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); trusted("input", form.querySelector('input[name="your-name"]')!); t.flush(); const fss = fetchedEvents("biz-fs-input").filter((e) => e.event_name === "form_start"); expect(fss).toHaveLength(1); expect(fss[0].properties).toEqual({ form_id: "cf7-156" }); expect(ctkSidField(form)).toBeTruthy(); }); it("form_submit:原生 submit → submission_id 生成 + _ctk_sid 值更新 + 事件引用同 ID;两次提交两个不同 ID", () => { setUrl("/fsub"); const t = mkTransport("biz-fsub"); startPageSession(t); startBusinessTracking(t); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); trusted("focusin", form.querySelector('input[name="your-name"]')!); // 先 start 注入占位 submit(form); t.flush(); let subs = fetchedEvents("biz-fsub").filter((e) => e.event_name === "form_submit"); expect(subs).toHaveLength(1); expect(subs[0].properties.form_id).toBe("cf7-156"); const sid1 = subs[0].properties.submission_id; expect(sid1).toMatch(UUID4_RE); // uuid4 小写(服务端 _SUBMIT_RE) expect(ctkSidField(form)!.value).toBe(sid1); // 隐藏字段值 === 事件引用的 ID expect(Object.keys(subs[0].properties).sort()).toEqual(["form_id", "submission_id"]); const form2 = mkForm({ action: "#wpcf7-f156-p20-o1" }); // 未经 form_start 直接 submit:找/建字段 submit(form2); submit(form); // 同表单第二次提交:新 submission_id t.flush(); subs = fetchedEvents("biz-fsub").filter((e) => e.event_name === "form_submit"); expect(subs).toHaveLength(3); const sid2 = subs[2].properties.submission_id; expect(sid2).toMatch(UUID4_RE); expect(sid2).not.toBe(sid1); // 每次尝试独立 expect(ctkSidField(form)!.value).toBe(sid2); // 字段随最近一次提交更新 expect(ctkSidField(form2)!.value).toMatch(UUID4_RE); // submit 时自建字段并填值 }); it("form_error:invalid/unaccepted→invalid、spam→spam;field_count 数 .wpcf7-not-valid-tip;引用最近 submission_id;中间态无事件", () => { setUrl("/ferr"); const t = mkTransport("biz-ferr"); startPageSession(t); startBusinessTracking(t); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); cf7status(form, "invalid"); // 无前置 submit → submission_id 空串(服务端允许空) t.flush(); let errs = fetchedEvents("biz-ferr").filter((e) => e.event_name === "form_error"); expect(errs).toHaveLength(1); expect(errs[0].properties).toEqual({ form_id: "cf7-156", submission_id: "", error_code: "invalid", field_count: 0, }); submit(form); // 生成当次 submission_id form.insertAdjacentHTML( "beforeend", 'e1e2' ); cf7status(form, "unaccepted"); // spec §1.2 映射 → invalid(勿漏) t.flush(); errs = fetchedEvents("biz-ferr").filter((e) => e.event_name === "form_error"); expect(errs).toHaveLength(2); expect(errs[1].properties.error_code).toBe("invalid"); expect(errs[1].properties.field_count).toBe(2); // 从 DOM .wpcf7-not-valid-tip 计数 expect(errs[1].properties.submission_id).toBe(ctkSidField(form)!.value); // 引用最近 submission_id expect(errs[1].properties.submission_id).toMatch(UUID4_RE); cf7status(form, "spam"); t.flush(); errs = fetchedEvents("biz-ferr").filter((e) => e.event_name === "form_error"); expect(errs).toHaveLength(3); expect(errs[2].properties).toEqual({ form_id: "cf7-156", submission_id: errs[1].properties.submission_id, error_code: "spam", field_count: 2, }); for (const s of ["init", "validating", "sending", "resetting"]) cf7status(form, s); t.flush(); const all = fetchedEvents("biz-ferr"); expect(all.filter((e) => e.event_name === "form_error")).toHaveLength(3); // 中间态不产生事件 expect(all.filter((e) => e.event_name === "form_submit_result")).toHaveLength(0); }); it("form_submit_result:mail_sent/mail_failed → result_code;引用最近 submission_id", () => { setUrl("/fres"); const t = mkTransport("biz-fres"); startPageSession(t); startBusinessTracking(t); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); submit(form); const sid1 = ctkSidField(form)!.value; cf7status(form, "mail_sent"); submit(form); // 第二次尝试 → 新 ID const sid2 = ctkSidField(form)!.value; expect(sid2).not.toBe(sid1); cf7status(form, "mail_failed"); t.flush(); const rs = fetchedEvents("biz-fres").filter((e) => e.event_name === "form_submit_result"); expect(rs).toHaveLength(2); expect(rs[0].properties).toEqual({ form_id: "cf7-156", submission_id: sid1, result_code: "mail_sent", }); expect(rs[1].properties).toEqual({ form_id: "cf7-156", submission_id: sid2, result_code: "mail_failed", }); }); it("form_id 规整:action #wpcf7-fN-pM-oK → cf7-N;action 解析不出走 form.id;都失败 → cf7-0", () => { setUrl("/fid"); const t = mkTransport("biz-fid"); startPageSession(t); startBusinessTracking(t); const f1 = mkForm({ action: "#wpcf7-f156-p20-o1" }); trusted("focusin", f1.querySelector("input")!); const f2 = mkForm({ action: "/contact-us/", id: "wpcf7-f42-o1" }); // id 兜底路径 trusted("focusin", f2.querySelector("input")!); const f3 = mkForm({ action: "/contact-us/" }); // 无 id:解析不出 trusted("focusin", f3.querySelector("input")!); t.flush(); const ids = fetchedEvents("biz-fid") .filter((e) => e.event_name === "form_start") .map((e) => e.properties.form_id); expect(ids).toEqual(["cf7-156", "cf7-42", "cf7-0"]); }); it("form_id 回归(T4 审查遗留):11 位 ID 不截断匹配 → cf7-0(服务端 cf7-N 上限 10 位)", () => { setUrl("/fid11"); const t = mkTransport("biz-fid11"); startPageSession(t); startBusinessTracking(t); const form = mkForm({ action: "#wpcf7-f12345678901" }); trusted("focusin", form.querySelector("input")!); t.flush(); const fss = fetchedEvents("biz-fid11").filter((e) => e.event_name === "form_start"); expect(fss).toHaveLength(1); expect(fss[0].properties.form_id).toBe("cf7-0"); // (?!\d) 拒绝截断——按解析失败归 cf7-0 }); // ---- product_view(spec §1.3:非锚定段匹配 + onSegmentChange 切段) ---- it("product_view:init 时 /product/slug/ 判定 → inferred=true rule_version=v1 + 顶层归属", () => { setUrl("/product/hydraulic-press-100t/"); const t = mkTransport("biz-pv-init"); const ps = startPageSession(t); startBusinessTracking(t); t.flush(); const pvs = fetchedEvents("biz-pv-init").filter((e) => e.event_name === "product_view"); expect(pvs).toHaveLength(1); const pv = pvs[0]; expect(pv.properties).toEqual({ product_id: "hydraulic-press-100t", inferred: true, rule_version: "v1", }); expect(pv.event_id).toMatch(UUID_RE); expect(pv.occurred_at).toMatch(/^\d{4}-\d{2}-\d{2}T/); expect(pv.path).toBe("/product/hydraulic-press-100t/"); expect(pv.title).toBe(document.title); expect(pv.site_id).toBe("biz-pv-init"); expect(pv.visitor_id).toMatch(UUID_RE); expect(pv.session_id).toMatch(UUID_RE); expect(pv.page_id).toBe(ps.pageId); expect(typeof pv.seq).toBe("number"); expect(pv.purpose).toBe(null); // V1.1 预留从快照展开 expect(pv.epoch).toBe(null); expect(pv.grant_ref).toBe(null); }); it("product_view:/de/product/slug/ 多语言前缀命中(非锚定段匹配);slug 截断 ≤100", () => { setUrl("/de/product/flachbett-drucker/"); let t = mkTransport("biz-pv-de"); startPageSession(t); startBusinessTracking(t); t.flush(); let pvs = fetchedEvents("biz-pv-de").filter((e) => e.event_name === "product_view"); expect(pvs).toHaveLength(1); expect(pvs[0].properties.product_id).toBe("flachbett-drucker"); setUrl("/product/" + "a".repeat(150) + "/"); // 超 100 截断(对齐服务端 _PROD_RE 上限) t = mkTransport("biz-pv-long"); startPageSession(t); startBusinessTracking(t); t.flush(); pvs = fetchedEvents("biz-pv-long").filter((e) => e.event_name === "product_view"); expect(pvs).toHaveLength(1); expect(pvs[0].properties.product_id).toHaveLength(100); }); it("product_view:/product-category/suv/、/products/x/、裸 /product/ 不触发(段匹配精确性)", () => { setUrl("/product-category/suv/"); let t = mkTransport("biz-pv-cat"); startPageSession(t); startBusinessTracking(t); setUrl("/products/x/"); // 复数段 ≠ "product"(精确段匹配) t = mkTransport("biz-pv-plural"); startPageSession(t); startBusinessTracking(t); setUrl("/product/"); // "product" 后无 slug 段 t = mkTransport("biz-pv-bare"); startPageSession(t); startBusinessTracking(t); setUrl("/product/%E4%B8%AD%E6%96%87/"); // 百分号编码 slug:服务端 _PROD_RE 必拒——客户端即不发 t = mkTransport("biz-pv-enc"); startPageSession(t); startBusinessTracking(t); t.flush(); expect(fetchedEvents("biz-pv-cat").some((e) => e.event_name === "product_view")).toBe(false); expect(fetchedEvents("biz-pv-plural").some((e) => e.event_name === "product_view")).toBe(false); expect(fetchedEvents("biz-pv-bare").some((e) => e.event_name === "product_view")).toBe(false); expect(fetchedEvents("biz-pv-enc").some((e) => e.event_name === "product_view")).toBe(false); }); it("form_error:field_count 钳制上限 99(服务端 0-99 契约)", () => { setUrl("/ferr-cap"); const t = mkTransport("biz-ferr-cap"); startPageSession(t); startBusinessTracking(t); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); for (let i = 0; i < 105; i++) { const tip = document.createElement("span"); tip.className = "wpcf7-not-valid-tip"; form.appendChild(tip); } cf7status(form, "invalid"); t.flush(); const errs = fetchedEvents("biz-ferr-cap").filter((e) => e.event_name === "form_error"); expect(errs).toHaveLength(1); expect(errs[0].properties.field_count).toBe(99); // 105 → 钳 99,不落服务端拒绝区 }); it("product_view:onSegmentChange 切段到产品页 → 事件带新 page_id;切段到非产品页不发", () => { setUrl("/pv-spa-home"); const t = mkTransport("biz-pv-spa"); const ps1 = startPageSession(t); startBusinessTracking(t); t.flush(); expect(fetchedEvents("biz-pv-spa").some((e) => e.event_name === "product_view")).toBe(false); window.history.pushState({}, "", "/pv-spa-mid/"); // 切段到非产品页 t.flush(); expect(fetchedEvents("biz-pv-spa").some((e) => e.event_name === "product_view")).toBe(false); window.history.pushState({}, "", "/ar/product/suv-ar/"); // 切段到产品页(阿语前缀) const ps2 = getPageSession()!; expect(ps2).not.toBe(ps1); t.flush(); const pvs = fetchedEvents("biz-pv-spa").filter((e) => e.event_name === "product_view"); expect(pvs).toHaveLength(1); expect(pvs[0].properties).toEqual({ product_id: "suv-ar", inferred: true, rule_version: "v1" }); expect(pvs[0].page_id).toBe(ps2.pageId); // 事件带新 page_id expect(pvs[0].page_id).not.toBe(ps1.pageId); }); // ---- 红线(spec §4.5):监听器内异常不冒泡宿主 ---- it("红线:emit 内部异常(t.push 抛错)不冒泡——click/focusin/submit/status 各路径", () => { setUrl("/red"); const t = mkTransport("biz-red"); startPageSession(t); startBusinessTracking(t); vi.spyOn(t, "push").mockImplementation(() => { throw new Error("push boom"); }); const form = mkForm({ action: "#wpcf7-f156-p20-o1" }); expect(() => click(mkLink("https://wa.me/8613800138000"))).not.toThrow(); expect(() => trusted("focusin", form.querySelector("input")!)).not.toThrow(); expect(() => submit(form)).not.toThrow(); expect(() => cf7status(form, "mail_sent")).not.toThrow(); expect(() => window.history.pushState({}, "", "/red-next")).not.toThrow(); // 段回调 emit 异常同静默 }); });