|
| 1 | +import assert from "node:assert/strict"; |
| 2 | +import { test } from "node:test"; |
| 3 | +import { openBrowser } from "../src/open-browser.mjs"; |
| 4 | + |
| 5 | +/** A recording child the spawn fake returns; `errorHandlers` proves the swallow listener is attached. */ |
| 6 | +function fakeChild() { |
| 7 | + const child = { errorHandlers: [], unrefCalled: false }; |
| 8 | + child.on = (event, cb) => { |
| 9 | + if (event === "error") child.errorHandlers.push(cb); |
| 10 | + return child; |
| 11 | + }; |
| 12 | + child.unref = () => { |
| 13 | + child.unrefCalled = true; |
| 14 | + }; |
| 15 | + return child; |
| 16 | +} |
| 17 | + |
| 18 | +test("openBrowser spawns the exact per-platform argv, detached and ignored", () => { |
| 19 | + // The argv table is the module (the GitHub App wizard shipped it first; the graph export reuses |
| 20 | + // it), so it is pinned literally per platform -- the up.mjs exact-argv doctrine. |
| 21 | + const cases = [ |
| 22 | + ["darwin", "open", ["https://x"]], |
| 23 | + ["win32", "cmd", ["/c", "start", "", "https://x"]], |
| 24 | + ["linux", "xdg-open", ["https://x"]], |
| 25 | + ]; |
| 26 | + for (const [platform, cmd, args] of cases) { |
| 27 | + const calls = []; |
| 28 | + const child = fakeChild(); |
| 29 | + openBrowser("https://x", { platform, spawn: (...a) => (calls.push(a), child) }); |
| 30 | + assert.equal(calls.length, 1, platform); |
| 31 | + assert.deepEqual(calls[0], [cmd, args, { stdio: "ignore", detached: true }], platform); |
| 32 | + assert.equal(child.errorHandlers.length, 1, "the async error path must be swallowed, or a missing opener crashes the process later"); |
| 33 | + assert.equal(child.unrefCalled, true, "the child must not hold the event loop open"); |
| 34 | + } |
| 35 | +}); |
| 36 | + |
| 37 | +test("openBrowser swallows a synchronous spawn failure -- the printed URL carries the flow", () => { |
| 38 | + assert.doesNotThrow(() => |
| 39 | + openBrowser("https://x", { |
| 40 | + platform: "linux", |
| 41 | + spawn: () => { |
| 42 | + throw new Error("ENOENT: no xdg-open"); |
| 43 | + }, |
| 44 | + }), |
| 45 | + ); |
| 46 | +}); |
| 47 | + |
| 48 | +test("openBrowser's swallowed error handler is inert when invoked", () => { |
| 49 | + const child = fakeChild(); |
| 50 | + openBrowser("https://x", { platform: "darwin", spawn: () => child }); |
| 51 | + assert.doesNotThrow(() => child.errorHandlers[0](new Error("spawn open ENOENT"))); |
| 52 | +}); |
0 commit comments