| 1 | import assert from "node:assert/strict"; |
| 2 | import { JSDOM } from "jsdom"; |
| 3 | import { installDesktopHostStub } from "./desktopHostStub"; |
| 4 | import { installPerformancePressureMonitor } from "../lib/crash"; |
| 5 | import type { ProcessDiagnosticsSnapshot, RendererProfileResult } from "../lib/processDiagnostics"; |
| 6 | |
| 7 | const dom = new JSDOM("<!doctype html><body></body>", { url: "http://localhost" }); |
| 8 | let now = 0; |
| 9 | let observe!: (list: { getEntries(): unknown[] }) => void; |
| 10 | let completeProfile!: (value: RendererProfileResult) => void; |
| 11 | let completeProcesses!: (value: ProcessDiagnosticsSnapshot) => void; |
| 12 | let cancelled = 0; |
| 13 | let heapRequests = 0; |
| 14 | const reports: string[] = []; |
| 15 | const copies: string[] = []; |
| 16 | Object.defineProperty(dom.window.document, "visibilityState", { value: "visible" }); |
| 17 | dom.window.document.hasFocus = () => true; |
| 18 | dom.window.setInterval = () => 1; |
| 19 | Object.assign(globalThis, { |
| 20 | window: dom.window, document: dom.window.document, performance: { now: () => now }, |
| 21 | PerformanceObserver: class { constructor(callback: typeof observe) { observe = callback; } observe() {} }, |
| 22 | }); |
| 23 | installDesktopHostStub({ ReportCrash: (_kind: string, payload: string) => { reports.push(payload); } }, { |
| 24 | clipboardWrites: copies, |
| 25 | processDiagnostics: () => new Promise((resolve) => { completeProcesses = resolve; }), |
| 26 | performance: { |
| 27 | captureRendererProfile: () => new Promise((resolve) => { completeProfile = resolve; }), |
| 28 | cancelRendererProfile: async () => { cancelled++; }, |
| 29 | exportHeapSnapshot: async () => { heapRequests++; return { status: "cancelled" }; }, |
| 30 | }, |
| 31 | }); |
| 32 | installPerformancePressureMonitor(); |
| 33 | now = 20_000; |
| 34 | observe({ getEntries: () => [{ startTime: 20_000, duration: 900, name: "self" }] }); |
| 35 | const host = document.getElementById("performance-report-prompt"); |
| 36 | assert.ok(host, "base report appears before either diagnostic promise settles"); |
| 37 | const copy = host.querySelector<HTMLButtonElement>(".performance-report__copy")!; |
| 38 | const send = host.querySelector<HTMLButtonElement>(".performance-report__send")!; |
| 39 | const flush = async () => { for (let i = 0; i < 20; i++) await Promise.resolve(); }; |
| 40 | await flush(); |
| 41 | completeProfile({ status: "captured", durationMs: 5000, frames: [{ label: "render (app.js:10)", samples: 4, selfMs: 40 }] }); |
| 42 | await flush(); |
| 43 | assert.equal(host.querySelector(".performance-report__copy"), copy, "enrichment preserves button identity"); |
| 44 | copy.click(); send.click(); |
| 45 | await flush(); |
| 46 | assert.match(copies[0], /render \(app.js:10\)/); |
| 47 | assert.match(reports[0], /CPU profile after trigger: captured/); |
| 48 | assert.equal(heapRequests, 0, "heap capture never happens automatically"); |
| 49 | host.querySelector<HTMLButtonElement>(".performance-report__dismiss")!.click(); |
| 50 | assert.equal(cancelled, 0, "closing a completed report cannot cancel a later capture"); |
| 51 | completeProcesses({ scope: "electron", samples: [] }); |
| 52 | await flush(); |
| 53 | assert.equal(document.getElementById("performance-report-prompt"), null, "late diagnostics never recreate a dismissed prompt"); |
| 54 | dom.window.close(); |
| 55 | console.log("performance report enrichment tests passed"); |
| 56 |