@fairfox/polly 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/polly.js +21 -1
- package/dist/cli/polly.js.map +3 -3
- package/dist/src/actions/error.d.ts +26 -0
- package/dist/src/actions/event-delegation.d.ts +48 -0
- package/dist/src/actions/form.d.ts +72 -0
- package/dist/src/actions/index.d.ts +13 -0
- package/dist/src/actions/index.js +525 -0
- package/dist/src/actions/index.js.map +15 -0
- package/dist/src/actions/overlay.d.ts +26 -0
- package/dist/src/actions/registry.d.ts +25 -0
- package/dist/src/actions/store.d.ts +26 -0
- package/dist/src/actions/testing.d.ts +26 -0
- package/dist/src/background/index.js +26 -1
- package/dist/src/background/index.js.map +2 -2
- package/dist/src/background/message-router.js +26 -1
- package/dist/src/background/message-router.js.map +2 -2
- package/dist/src/client/index.js +27 -2
- package/dist/src/client/index.js.map +3 -3
- package/dist/src/elysia/index.js +62 -35
- package/dist/src/elysia/index.js.map +3 -3
- package/dist/src/index.js +26 -1
- package/dist/src/index.js.map +2 -2
- package/dist/src/mesh-node.js +26 -1
- package/dist/src/mesh-node.js.map +2 -2
- package/dist/src/mesh.js +26 -1
- package/dist/src/mesh.js.map +2 -2
- package/dist/src/peer.js +32 -5
- package/dist/src/peer.js.map +3 -3
- package/dist/src/polly-ui/ActionForm.d.ts +21 -0
- package/dist/src/polly-ui/ActionInput.d.ts +41 -0
- package/dist/src/polly-ui/ConfirmDialog.d.ts +24 -0
- package/dist/src/polly-ui/Layout.d.ts +51 -0
- package/dist/src/polly-ui/Modal.d.ts +52 -0
- package/dist/src/polly-ui/OverlayRoot.d.ts +10 -0
- package/dist/src/polly-ui/TextInput.d.ts +31 -0
- package/dist/src/polly-ui/Toast.d.ts +19 -0
- package/dist/src/polly-ui/index.css +319 -0
- package/dist/src/polly-ui/index.d.ts +17 -0
- package/dist/src/polly-ui/index.js +953 -0
- package/dist/src/polly-ui/index.js.map +22 -0
- package/dist/src/polly-ui/internal/focus-trap.d.ts +10 -0
- package/dist/src/polly-ui/internal/input-base.d.ts +18 -0
- package/dist/src/polly-ui/internal/scroll-lock.d.ts +9 -0
- package/dist/src/polly-ui/styles.css +70 -0
- package/dist/src/polly-ui/theme.css +163 -0
- package/dist/src/shared/adapters/index.js +26 -1
- package/dist/src/shared/adapters/index.js.map +2 -2
- package/dist/src/shared/lib/context-helpers.js +26 -1
- package/dist/src/shared/lib/context-helpers.js.map +2 -2
- package/dist/src/shared/lib/errors.js +26 -1
- package/dist/src/shared/lib/errors.js.map +2 -2
- package/dist/src/shared/lib/message-bus.js +26 -1
- package/dist/src/shared/lib/message-bus.js.map +2 -2
- package/dist/src/shared/lib/peer-repo-server.d.ts +8 -8
- package/dist/src/shared/lib/resource.js +26 -1
- package/dist/src/shared/lib/resource.js.map +2 -2
- package/dist/src/shared/lib/state.js +26 -1
- package/dist/src/shared/lib/state.js.map +2 -2
- package/dist/src/shared/lib/test-helpers.js +26 -1
- package/dist/src/shared/lib/test-helpers.js.map +2 -2
- package/dist/src/shared/state/app-state.js +26 -1
- package/dist/src/shared/state/app-state.js.map +2 -2
- package/dist/src/shared/types/messages.js +26 -1
- package/dist/src/shared/types/messages.js.map +2 -2
- package/dist/tools/quality/src/cli.js +640 -28
- package/dist/tools/quality/src/cli.js.map +11 -5
- package/dist/tools/quality/src/css/check-layout.d.ts +19 -0
- package/dist/tools/quality/src/css/check-quality.d.ts +24 -0
- package/dist/tools/quality/src/css/check-unused.d.ts +20 -0
- package/dist/tools/quality/src/css/check-vars.d.ts +22 -0
- package/dist/tools/quality/src/css/shared.d.ts +33 -0
- package/dist/tools/quality/src/index.d.ts +12 -0
- package/dist/tools/quality/src/index.js +557 -18
- package/dist/tools/quality/src/index.js.map +10 -4
- package/dist/tools/quality/src/logger.d.ts +26 -0
- package/dist/tools/test/src/adapters/index.js +26 -1
- package/dist/tools/test/src/adapters/index.js.map +2 -2
- package/dist/tools/test/src/browser/index.js +26 -1
- package/dist/tools/test/src/browser/index.js.map +2 -2
- package/dist/tools/test/src/browser/run.js +238 -0
- package/dist/tools/test/src/browser/run.js.map +11 -0
- package/dist/tools/test/src/index.js +26 -1
- package/dist/tools/test/src/index.js.map +2 -2
- package/dist/tools/test/src/test-utils.js +26 -1
- package/dist/tools/test/src/test-utils.js.map +2 -2
- package/dist/tools/test/src/visual/compare.d.ts +23 -0
- package/dist/tools/test/src/visual/harness.d.ts +53 -0
- package/dist/tools/test/src/visual/index.d.ts +12 -0
- package/dist/tools/test/src/visual/index.js +13968 -0
- package/dist/tools/test/src/visual/index.js.map +41 -0
- package/dist/tools/verify/src/cli.js +3 -3
- package/dist/tools/verify/src/cli.js.map +1 -1
- package/dist/tools/verify/src/config.js +26 -1
- package/dist/tools/verify/src/config.js.map +2 -2
- package/package.json +21 -2
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// tools/test/src/browser/run.ts
|
|
21
|
+
import { resolve } from "node:path";
|
|
22
|
+
import { Glob } from "bun";
|
|
23
|
+
import { Elysia as Elysia2 } from "elysia";
|
|
24
|
+
import puppeteer from "puppeteer";
|
|
25
|
+
|
|
26
|
+
// src/elysia/signaling-server-plugin.ts
|
|
27
|
+
import { Elysia } from "elysia";
|
|
28
|
+
function signalingServer(options = {}) {
|
|
29
|
+
const path = options.path ?? "/polly/signaling";
|
|
30
|
+
const peerSockets = new Map;
|
|
31
|
+
const parseMessage = (raw) => {
|
|
32
|
+
try {
|
|
33
|
+
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const handleJoin = (ws, peerId) => {
|
|
39
|
+
peerSockets.set(peerId, ws);
|
|
40
|
+
const wsWithData = ws;
|
|
41
|
+
wsWithData.data.peerId = peerId;
|
|
42
|
+
};
|
|
43
|
+
const sendUnknownTarget = (ws, targetPeerId) => {
|
|
44
|
+
ws.send({
|
|
45
|
+
type: "error",
|
|
46
|
+
reason: "unknown-target",
|
|
47
|
+
targetPeerId
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const findOpenTarget = (targetPeerId) => {
|
|
51
|
+
const target = peerSockets.get(targetPeerId);
|
|
52
|
+
if (!target)
|
|
53
|
+
return;
|
|
54
|
+
const readyState = target.readyState;
|
|
55
|
+
const OPEN = 1;
|
|
56
|
+
if (readyState !== undefined && readyState !== OPEN) {
|
|
57
|
+
peerSockets.delete(targetPeerId);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
return target;
|
|
61
|
+
};
|
|
62
|
+
const handleSignal = (ws, msg) => {
|
|
63
|
+
const wsWithData = ws;
|
|
64
|
+
const senderId = wsWithData.data.peerId;
|
|
65
|
+
if (!senderId) {
|
|
66
|
+
wsWithData.send({ type: "error", reason: "not-joined" });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const target = findOpenTarget(msg.targetPeerId);
|
|
70
|
+
if (!target) {
|
|
71
|
+
sendUnknownTarget(ws, msg.targetPeerId);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const relayed = {
|
|
75
|
+
type: "signal",
|
|
76
|
+
peerId: senderId,
|
|
77
|
+
targetPeerId: msg.targetPeerId,
|
|
78
|
+
payload: msg.payload
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
target.send(relayed);
|
|
82
|
+
} catch {
|
|
83
|
+
peerSockets.delete(msg.targetPeerId);
|
|
84
|
+
sendUnknownTarget(ws, msg.targetPeerId);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return new Elysia().ws(path, {
|
|
88
|
+
message(ws, raw) {
|
|
89
|
+
const msg = parseMessage(raw);
|
|
90
|
+
if (!msg) {
|
|
91
|
+
ws.send({ type: "error", reason: "malformed" });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (msg.type === "join") {
|
|
95
|
+
handleJoin(ws, msg.peerId);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (msg.type === "signal") {
|
|
99
|
+
handleSignal(ws, msg);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
ws.send({ type: "error", reason: "malformed" });
|
|
103
|
+
},
|
|
104
|
+
close(ws) {
|
|
105
|
+
const peerId = ws.data.peerId;
|
|
106
|
+
if (peerId) {
|
|
107
|
+
if (peerSockets.get(peerId) === ws) {
|
|
108
|
+
peerSockets.delete(peerId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// tools/test/src/browser/run.ts
|
|
116
|
+
var automergeBase64Path = resolve(process.cwd(), "node_modules/@automerge/automerge/dist/mjs/entrypoints/fullfat_base64.js");
|
|
117
|
+
var automergeBase64Plugin = {
|
|
118
|
+
name: "automerge-base64",
|
|
119
|
+
setup(build) {
|
|
120
|
+
build.onResolve({ filter: /^@automerge\/automerge(\/slim)?$/ }, () => {
|
|
121
|
+
return { path: automergeBase64Path };
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var testDir = resolve(process.cwd(), process.argv[2] ?? "tests/browser");
|
|
126
|
+
var filter = process.argv[3] ?? "";
|
|
127
|
+
var headless = process.env["HEADLESS"] !== "false";
|
|
128
|
+
var glob = new Glob("**/*.browser.{ts,tsx}");
|
|
129
|
+
var testFiles = [];
|
|
130
|
+
for await (const file of glob.scan({ cwd: testDir, absolute: true })) {
|
|
131
|
+
if (file.includes("harness"))
|
|
132
|
+
continue;
|
|
133
|
+
if (filter && !file.includes(filter))
|
|
134
|
+
continue;
|
|
135
|
+
testFiles.push(file);
|
|
136
|
+
}
|
|
137
|
+
if (testFiles.length === 0) {
|
|
138
|
+
console.log(`[browser-runner] no test files found${filter ? ` matching "${filter}"` : ""}`);
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
console.log(`[browser-runner] found ${testFiles.length} test file(s)`);
|
|
142
|
+
var signalingPort = 39000 + Math.floor(Math.random() * 1000);
|
|
143
|
+
var signalingApp = new Elysia2().use(signalingServer({ path: "/polly/signaling" })).listen(signalingPort);
|
|
144
|
+
console.log(`[browser-runner] signaling server on ws://127.0.0.1:${signalingPort}/polly/signaling`);
|
|
145
|
+
var browser = await puppeteer.launch({
|
|
146
|
+
headless,
|
|
147
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
148
|
+
});
|
|
149
|
+
var totalPassed = 0;
|
|
150
|
+
var totalFailed = 0;
|
|
151
|
+
for (const testFile of testFiles) {
|
|
152
|
+
const shortName = testFile.replace(`${testDir}/`, "");
|
|
153
|
+
console.log(`
|
|
154
|
+
[browser-runner] running ${shortName}`);
|
|
155
|
+
const buildResult = await Bun.build({
|
|
156
|
+
entrypoints: [testFile],
|
|
157
|
+
target: "browser",
|
|
158
|
+
format: "esm",
|
|
159
|
+
minify: false,
|
|
160
|
+
sourcemap: "inline",
|
|
161
|
+
plugins: [automergeBase64Plugin],
|
|
162
|
+
define: {
|
|
163
|
+
"process.env.SIGNALING_URL": JSON.stringify(`ws://127.0.0.1:${signalingPort}/polly/signaling`)
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
if (!buildResult.success) {
|
|
167
|
+
console.log(" ❌ build failed:");
|
|
168
|
+
for (const log of buildResult.logs) {
|
|
169
|
+
console.log(` ${log}`);
|
|
170
|
+
}
|
|
171
|
+
totalFailed += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const jsText = await buildResult.outputs[0]?.text();
|
|
175
|
+
if (!jsText) {
|
|
176
|
+
console.log(" ❌ build produced no output");
|
|
177
|
+
totalFailed += 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const html = `<!DOCTYPE html>
|
|
181
|
+
<html><head><meta charset="utf-8"></head>
|
|
182
|
+
<body>
|
|
183
|
+
<script type="module">${jsText}</script>
|
|
184
|
+
</body></html>`;
|
|
185
|
+
const server = Bun.serve({
|
|
186
|
+
port: 0,
|
|
187
|
+
fetch() {
|
|
188
|
+
return new Response(html, { headers: { "Content-Type": "text/html" } });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
const page = await browser.newPage();
|
|
192
|
+
page.on("console", (msg) => {
|
|
193
|
+
const text = msg.text();
|
|
194
|
+
if (text.includes("[test]")) {
|
|
195
|
+
console.log(` ${text}`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
page.on("pageerror", (err) => {
|
|
199
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
200
|
+
console.log(` ❌ page error: ${msg}`);
|
|
201
|
+
});
|
|
202
|
+
await page.goto(`http://127.0.0.1:${server.port}/`, { waitUntil: "domcontentloaded" });
|
|
203
|
+
const timeout = 15000;
|
|
204
|
+
const deadline = Date.now() + timeout;
|
|
205
|
+
let finished = false;
|
|
206
|
+
while (Date.now() < deadline) {
|
|
207
|
+
finished = await page.evaluate(() => window["__done"] === true);
|
|
208
|
+
if (finished)
|
|
209
|
+
break;
|
|
210
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
211
|
+
}
|
|
212
|
+
if (!finished) {
|
|
213
|
+
console.log(` ❌ timed out after ${timeout}ms`);
|
|
214
|
+
totalFailed += 1;
|
|
215
|
+
await page.close();
|
|
216
|
+
server.stop();
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const results = await page.evaluate(() => window["__testResults"]);
|
|
220
|
+
for (const r of results ?? []) {
|
|
221
|
+
if (r.passed) {
|
|
222
|
+
console.log(` ✅ ${r.name}`);
|
|
223
|
+
totalPassed += 1;
|
|
224
|
+
} else {
|
|
225
|
+
console.log(` ❌ ${r.name}: ${r.error}`);
|
|
226
|
+
totalFailed += 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
await page.close();
|
|
230
|
+
server.stop();
|
|
231
|
+
}
|
|
232
|
+
await browser.close();
|
|
233
|
+
signalingApp.server?.stop?.(true);
|
|
234
|
+
console.log(`
|
|
235
|
+
[browser-runner] ${totalPassed} passed, ${totalFailed} failed`);
|
|
236
|
+
process.exit(totalFailed > 0 ? 1 : 0);
|
|
237
|
+
|
|
238
|
+
//# debugId=A0CE9A3A933C98B164756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../tools/test/src/browser/run.ts", "../src/elysia/signaling-server-plugin.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"#!/usr/bin/env bun\n\n/**\n * Browser test runner for Polly applications.\n *\n * Finds all *.browser.ts files in a given directory, bundles each with\n * Bun.build for the browser target (with an internal Automerge WASM fix),\n * serves the bundle on an ephemeral port, opens a Puppeteer page, and\n * polls window.__done for results. Prints pass/fail per test and exits\n * non-zero if any test failed.\n *\n * A signalling server for WebRTC tests starts automatically on a random\n * port. The URL is injected into the bundle via process.env.SIGNALING_URL.\n *\n * Usage (from project root):\n *\n * bun tools/test/src/browser/run.ts [testDir] [filter]\n *\n * Examples:\n *\n * bun tools/test/src/browser/run.ts tests/browser\n * bun tools/test/src/browser/run.ts tests/browser mesh-webrtc\n * HEADLESS=false bun tools/test/src/browser/run.ts tests/browser\n *\n * When invoked without a testDir, defaults to tests/browser relative to cwd.\n */\n\nimport { resolve } from \"node:path\";\nimport { type BunPlugin, Glob } from \"bun\";\nimport { Elysia } from \"elysia\";\nimport puppeteer from \"puppeteer\";\nimport { signalingServer } from \"../../../../src/elysia/signaling-server-plugin\";\n\n// ─── Automerge WASM fix ───────────────────────────────────────────────────\n// Bun.build's target: \"browser\" picks Automerge's fullfat_bundler.js which\n// does a static .wasm import that Bun can't wire up. Redirect to the\n// base64 variant which embeds the WASM as a string and self-initialises.\n\nconst automergeBase64Path = resolve(\n process.cwd(),\n \"node_modules/@automerge/automerge/dist/mjs/entrypoints/fullfat_base64.js\"\n);\n\nconst automergeBase64Plugin: BunPlugin = {\n name: \"automerge-base64\",\n setup(build) {\n build.onResolve({ filter: /^@automerge\\/automerge(\\/slim)?$/ }, () => {\n return { path: automergeBase64Path };\n });\n },\n};\n\n// ─── Argument parsing ──────────────────────────────────────────────────────\n\nconst testDir = resolve(process.cwd(), process.argv[2] ?? \"tests/browser\");\nconst filter = process.argv[3] ?? \"\";\nconst headless = process.env[\"HEADLESS\"] !== \"false\";\n\nconst glob = new Glob(\"**/*.browser.{ts,tsx}\");\nconst testFiles: string[] = [];\nfor await (const file of glob.scan({ cwd: testDir, absolute: true })) {\n if (file.includes(\"harness\")) continue;\n if (filter && !file.includes(filter)) continue;\n testFiles.push(file);\n}\n\nif (testFiles.length === 0) {\n console.log(`[browser-runner] no test files found${filter ? ` matching \"${filter}\"` : \"\"}`);\n process.exit(0);\n}\n\nconsole.log(`[browser-runner] found ${testFiles.length} test file(s)`);\n\n// ─── Start server-side infrastructure ──────────────────────────────────────\n\nconst signalingPort = 39000 + Math.floor(Math.random() * 1000);\nconst signalingApp = new Elysia()\n .use(signalingServer({ path: \"/polly/signaling\" }))\n .listen(signalingPort);\nconsole.log(`[browser-runner] signaling server on ws://127.0.0.1:${signalingPort}/polly/signaling`);\n\n// ─── Launch browser ────────────────────────────────────────────────────────\n\nconst browser = await puppeteer.launch({\n headless,\n args: [\"--no-sandbox\", \"--disable-setuid-sandbox\"],\n});\n\nlet totalPassed = 0;\nlet totalFailed = 0;\n\nfor (const testFile of testFiles) {\n const shortName = testFile.replace(`${testDir}/`, \"\");\n console.log(`\\n[browser-runner] running ${shortName}`);\n\n const buildResult = await Bun.build({\n entrypoints: [testFile],\n target: \"browser\",\n format: \"esm\",\n minify: false,\n sourcemap: \"inline\",\n plugins: [automergeBase64Plugin],\n define: {\n \"process.env.SIGNALING_URL\": JSON.stringify(\n `ws://127.0.0.1:${signalingPort}/polly/signaling`\n ),\n },\n });\n\n if (!buildResult.success) {\n console.log(\" ❌ build failed:\");\n for (const log of buildResult.logs) {\n console.log(` ${log}`);\n }\n totalFailed += 1;\n continue;\n }\n\n const jsText = await buildResult.outputs[0]?.text();\n if (!jsText) {\n console.log(\" ❌ build produced no output\");\n totalFailed += 1;\n continue;\n }\n\n const html = `<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"></head>\n<body>\n<script type=\"module\">${jsText}</script>\n</body></html>`;\n\n const server = Bun.serve({\n port: 0,\n fetch() {\n return new Response(html, { headers: { \"Content-Type\": \"text/html\" } });\n },\n });\n\n const page = await browser.newPage();\n page.on(\"console\", (msg) => {\n const text = msg.text();\n if (text.includes(\"[test]\")) {\n console.log(` ${text}`);\n }\n });\n page.on(\"pageerror\", (err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.log(` ❌ page error: ${msg}`);\n });\n\n await page.goto(`http://127.0.0.1:${server.port}/`, { waitUntil: \"domcontentloaded\" });\n\n const timeout = 15_000;\n const deadline = Date.now() + timeout;\n let finished = false;\n while (Date.now() < deadline) {\n finished = await page.evaluate(\n () => (window as unknown as Record<string, unknown>)[\"__done\"] === true\n );\n if (finished) break;\n await new Promise((r) => setTimeout(r, 100));\n }\n\n if (!finished) {\n console.log(` ❌ timed out after ${timeout}ms`);\n totalFailed += 1;\n await page.close();\n server.stop();\n continue;\n }\n\n const results = await page.evaluate(\n () =>\n (window as unknown as Record<string, unknown>)[\"__testResults\"] as unknown as Array<{\n name: string;\n passed: boolean;\n error?: string;\n }>\n );\n\n for (const r of results ?? []) {\n if (r.passed) {\n console.log(` ✅ ${r.name}`);\n totalPassed += 1;\n } else {\n console.log(` ❌ ${r.name}: ${r.error}`);\n totalFailed += 1;\n }\n }\n\n await page.close();\n server.stop();\n}\n\nawait browser.close();\n(signalingApp as unknown as { server?: { stop?: (f?: boolean) => void } }).server?.stop?.(true);\n\nconsole.log(`\\n[browser-runner] ${totalPassed} passed, ${totalFailed} failed`);\nprocess.exit(totalFailed > 0 ? 1 : 0);\n",
|
|
6
|
+
"// @ts-nocheck - Optional peer dependencies (elysia, @elysiajs/eden)\n/**\n * signalingServer — Phase 2 Elysia plugin that exposes a stateless\n * WebSocket route for SDP/ICE relay between $meshState peers.\n *\n * The mesh transport is a star-of-data-channels: peers establish direct\n * WebRTC connections to each other and exchange document operations\n * peer-to-peer once those channels are open. WebRTC connection setup\n * needs an out-of-band channel for SDP offer/answer and ICE candidate\n * exchange, and that channel is what this plugin provides. The plugin\n * does not own any document state, does not hold any encryption keys,\n * and never inspects the contents of the messages it relays. It is a\n * pure pub-sub by peer id.\n *\n * Once two peers have completed the SDP exchange and opened a direct\n * data channel, the signalling server is no longer on the critical\n * path — the peers talk directly. The signalling server's role is\n * therefore intermittent: peers connect to it only during the brief\n * windows when they are establishing or re-establishing connections.\n *\n * Wire protocol:\n *\n * Client → server (join):\n * { type: \"join\", peerId: \"peer-alice\" }\n *\n * Client → server (signal to another peer):\n * { type: \"signal\", peerId: \"peer-alice\", targetPeerId: \"peer-bob\",\n * payload: { ... } }\n *\n * Server → client (delivered signal):\n * { type: \"signal\", peerId: \"peer-alice\", targetPeerId: \"peer-bob\",\n * payload: { ... } }\n *\n * Server → client (notification of unknown target):\n * { type: \"error\", reason: \"unknown-target\", targetPeerId: \"...\" }\n *\n * The `payload` is opaque to the signalling server — typically it\n * carries an SDP offer, SDP answer, or ICE candidate. Applications can\n * also use the relay for any other peer-to-peer message that needs an\n * intermediary, such as the initial handshake of a pairing flow.\n *\n * @example\n * ```ts\n * import { Elysia } from \"elysia\";\n * import { signalingServer } from \"@fairfox/polly/elysia\";\n *\n * const app = new Elysia()\n * .use(signalingServer({ path: \"/polly/signaling\" }))\n * .listen(8080);\n * ```\n */\n\nimport { Elysia } from \"elysia\";\n\n/** A signalling message. The `type` discriminates between join (peer\n * registration), signal (relayed message), and error (server response). */\nexport type SignalingMessage =\n | {\n type: \"join\";\n /** The peer registering itself with the signalling server. */\n peerId: string;\n }\n | {\n type: \"signal\";\n /** The peer sending the signal. */\n peerId: string;\n /** The peer the signal is being relayed to. */\n targetPeerId: string;\n /** Opaque payload, typically SDP or ICE. */\n payload: unknown;\n }\n | {\n type: \"error\";\n reason: \"unknown-target\" | \"not-joined\" | \"malformed\";\n targetPeerId?: string;\n };\n\nexport interface SignalingServerOptions {\n /** WebSocket route path. Defaults to \"/polly/signaling\". */\n path?: string;\n}\n\n/**\n * Construct the signalling-server Elysia plugin. The plugin keeps a\n * per-instance map of peer id → WebSocket connection so that incoming\n * \"signal\" messages can be routed to the right target socket. The map\n * is local to the plugin instance, not shared across processes; for\n * multi-instance deployments behind a load balancer, applications need\n * sticky connection routing or a shared backplane (Redis pub-sub or\n * similar). That is a follow-up.\n */\nexport function signalingServer(options: SignalingServerOptions = {}) {\n const path = options.path ?? \"/polly/signaling\";\n // Per-peer-id map of joined sockets. The inverse mapping is stored\n // directly on ws.data (a mutable property bag that Elysia preserves\n // across message callbacks for a given connection); the webrtc-p2p-chat\n // example in examples/ confirms this pattern is stable under Bun.\n const peerSockets = new Map<string, { send: (msg: unknown) => void }>();\n\n // Intentionally unnamed — Elysia deduplicates plugins by name, and each\n // signalingServer() call needs its own closure-captured peer map.\n const parseMessage = (raw: unknown): SignalingMessage | undefined => {\n try {\n return typeof raw === \"string\" ? JSON.parse(raw) : (raw as unknown as SignalingMessage);\n } catch {\n return undefined;\n }\n };\n\n const handleJoin = (ws: unknown, peerId: string): void => {\n peerSockets.set(peerId, ws as unknown as { send: (m: unknown) => void });\n const wsWithData = ws as unknown as { data: Record<string, unknown> };\n wsWithData.data.peerId = peerId;\n };\n\n const sendUnknownTarget = (ws: unknown, targetPeerId: string): void => {\n (ws as unknown as { send: (m: unknown) => void }).send({\n type: \"error\",\n reason: \"unknown-target\",\n targetPeerId,\n } as unknown as SignalingMessage);\n };\n\n /** Look up a target socket and confirm it is still open. */\n const findOpenTarget = (targetPeerId: string): { send: (msg: unknown) => void } | undefined => {\n const target = peerSockets.get(targetPeerId);\n if (!target) return undefined;\n const readyState = (target as unknown as { readyState?: number }).readyState;\n const OPEN = 1;\n if (readyState !== undefined && readyState !== OPEN) {\n peerSockets.delete(targetPeerId);\n return undefined;\n }\n return target;\n };\n\n const handleSignal = (ws: unknown, msg: Extract<SignalingMessage, { type: \"signal\" }>): void => {\n const wsWithData = ws as unknown as {\n data: Record<string, unknown>;\n send: (m: unknown) => void;\n };\n const senderId = wsWithData.data.peerId as unknown as string | undefined;\n if (!senderId) {\n wsWithData.send({ type: \"error\", reason: \"not-joined\" } as unknown as SignalingMessage);\n return;\n }\n const target = findOpenTarget(msg.targetPeerId);\n if (!target) {\n sendUnknownTarget(ws, msg.targetPeerId);\n return;\n }\n const relayed: SignalingMessage = {\n type: \"signal\",\n peerId: senderId,\n targetPeerId: msg.targetPeerId,\n payload: msg.payload,\n };\n try {\n target.send(relayed);\n } catch {\n peerSockets.delete(msg.targetPeerId);\n sendUnknownTarget(ws, msg.targetPeerId);\n }\n };\n\n return new Elysia().ws(path, {\n message(ws, raw) {\n const msg = parseMessage(raw);\n if (!msg) {\n ws.send({ type: \"error\", reason: \"malformed\" } as unknown as SignalingMessage);\n return;\n }\n if (msg.type === \"join\") {\n handleJoin(ws, msg.peerId);\n return;\n }\n if (msg.type === \"signal\") {\n handleSignal(ws, msg);\n return;\n }\n ws.send({ type: \"error\", reason: \"malformed\" } as unknown as SignalingMessage);\n },\n\n close(ws) {\n const peerId = (ws.data as unknown as Record<string, unknown>).peerId as unknown as\n | string\n | undefined;\n if (peerId) {\n // Only delete the entry if it still points at *this* socket, so a\n // stale close after a reconnect does not evict the new socket.\n if (peerSockets.get(peerId) === (ws as unknown as { send: (m: unknown) => void })) {\n peerSockets.delete(peerId);\n }\n }\n },\n });\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "A0CE9A3A933C98B164756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
1
3
|
var __defProp = Object.defineProperty;
|
|
2
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -5,6 +7,28 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
5
7
|
function __accessProp(key) {
|
|
6
8
|
return this[key];
|
|
7
9
|
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
8
32
|
var __toCommonJS = (from) => {
|
|
9
33
|
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
34
|
if (entry)
|
|
@@ -22,6 +46,7 @@ var __toCommonJS = (from) => {
|
|
|
22
46
|
return entry;
|
|
23
47
|
};
|
|
24
48
|
var __moduleCache;
|
|
49
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
25
50
|
var __returnValue = (v) => v;
|
|
26
51
|
function __exportSetter(name, newValue) {
|
|
27
52
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -450,4 +475,4 @@ export {
|
|
|
450
475
|
createMockAdapters
|
|
451
476
|
};
|
|
452
477
|
|
|
453
|
-
//# debugId=
|
|
478
|
+
//# debugId=C3F8571E4BAD0E3E64756E2164756E21
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"import { createMockContextMenus, type MockContextMenus } from \"./context-menus.mock\";\nimport { createMockFetch, type MockFetch } from \"./fetch.mock\";\nimport { createMockLogger, type MockLogger } from \"./logger.mock\";\nimport { createMockOffscreen, type MockOffscreen } from \"./offscreen.mock\";\nimport { createMockPort, createMockRuntime, type MockPort, type MockRuntime } from \"./runtime.mock\";\nimport { createMockStorageArea, type MockStorageArea } from \"./storage.mock\";\nimport { createMockTabs, type MockTabs } from \"./tabs.mock\";\nimport { createMockWindow, type MockWindow } from \"./window.mock\";\n\n/**\n * Mock adapters with full type information including mock-specific properties\n */\nexport interface MockExtensionAdapters {\n runtime: MockRuntime;\n storage: MockStorageArea;\n tabs: MockTabs;\n window: MockWindow;\n offscreen: MockOffscreen;\n contextMenus: MockContextMenus;\n fetch: MockFetch;\n logger: MockLogger;\n}\n\n/**\n * Convenience interface grouping Chrome-like mock APIs\n * Useful when tests need direct access to internal mock state\n */\nexport interface MockChrome {\n runtime: MockRuntime;\n storage: {\n local: MockStorageArea;\n };\n tabs: MockTabs;\n}\n\n/**\n * Create a mock Chrome object with grouped APIs\n * Use this when you need access to internal mock state (e.g., mockChrome.tabs._tabs)\n */\nexport function createMockChrome(): MockChrome {\n return {\n runtime: createMockRuntime(),\n storage: {\n local: createMockStorageArea(),\n },\n tabs: createMockTabs(),\n };\n}\n\n/**\n * Create a complete set of mock adapters for testing\n * Returns mock adapters with full type information\n */\nexport function createMockAdapters(): MockExtensionAdapters {\n return {\n runtime: createMockRuntime(),\n storage: createMockStorageArea(),\n tabs: createMockTabs(),\n window: createMockWindow(),\n offscreen: createMockOffscreen(),\n contextMenus: createMockContextMenus(),\n fetch: createMockFetch(),\n logger: createMockLogger({ silent: true }),\n };\n}\n\nexport type {\n MockContextMenus,\n MockFetch,\n MockLogger,\n MockOffscreen,\n MockPort,\n MockRuntime,\n MockStorageArea,\n MockTabs,\n MockWindow,\n};\n// Re-export individual mock factories and types for convenience\nexport {\n createMockContextMenus,\n createMockFetch,\n createMockLogger,\n createMockOffscreen,\n createMockPort,\n createMockRuntime,\n createMockStorageArea,\n createMockTabs,\n createMockWindow,\n};\n",
|
|
14
14
|
"import type { ExtensionMessage, RoutedMessage } from \"@fairfox/polly/types\";\n\n/**\n * Test utilities for extension testing\n */\n\nexport function createMockRoutedMessage<T extends ExtensionMessage>(\n payload: T,\n overrides?: Partial<Omit<RoutedMessage<T>, \"payload\">>\n): RoutedMessage<T> {\n return {\n id: overrides?.id || `msg-${Date.now()}-${Math.random()}`,\n source: overrides?.source || \"background\",\n targets: overrides?.targets || [\"content\"],\n tabId: overrides?.tabId,\n timestamp: overrides?.timestamp || Date.now(),\n payload,\n };\n}\n\nexport function waitFor(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function waitForCondition(\n condition: () => boolean,\n timeout = 1000,\n interval = 10\n): Promise<void> {\n const startTime = Date.now();\n while (!condition()) {\n if (Date.now() - startTime > timeout) {\n throw new Error(\"Condition not met within timeout\");\n }\n await waitFor(interval);\n }\n}\n\nexport function expectType<T>(_value: T): void {\n // Type assertion helper for compile-time checks\n}\n\n/**\n * No-op function for mocks that don't need to do anything\n * Use this instead of empty arrow functions to satisfy linter\n */\n// biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op for mocks\nexport function noOp(): void {}\n\n/**\n * Async no-op function for async mocks that don't need to do anything\n * Use this instead of empty async arrow functions to satisfy linter\n */\n// biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op for mocks\nexport async function noOpAsync(): Promise<void> {}\n"
|
|
15
15
|
],
|
|
16
|
-
"mappings": "
|
|
17
|
-
"debugId": "
|
|
16
|
+
"mappings": "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",
|
|
17
|
+
"debugId": "C3F8571E4BAD0E3E64756E2164756E21",
|
|
18
18
|
"names": []
|
|
19
19
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
1
3
|
var __defProp = Object.defineProperty;
|
|
2
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -5,6 +7,28 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
5
7
|
function __accessProp(key) {
|
|
6
8
|
return this[key];
|
|
7
9
|
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
8
32
|
var __toCommonJS = (from) => {
|
|
9
33
|
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
34
|
if (entry)
|
|
@@ -22,6 +46,7 @@ var __toCommonJS = (from) => {
|
|
|
22
46
|
return entry;
|
|
23
47
|
};
|
|
24
48
|
var __moduleCache;
|
|
49
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
25
50
|
var __returnValue = (v) => v;
|
|
26
51
|
function __exportSetter(name, newValue) {
|
|
27
52
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -79,4 +104,4 @@ export {
|
|
|
79
104
|
createMockRoutedMessage
|
|
80
105
|
};
|
|
81
106
|
|
|
82
|
-
//# debugId=
|
|
107
|
+
//# debugId=1D7B3D33F0FB579464756E2164756E21
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { ExtensionMessage, RoutedMessage } from \"@fairfox/polly/types\";\n\n/**\n * Test utilities for extension testing\n */\n\nexport function createMockRoutedMessage<T extends ExtensionMessage>(\n payload: T,\n overrides?: Partial<Omit<RoutedMessage<T>, \"payload\">>\n): RoutedMessage<T> {\n return {\n id: overrides?.id || `msg-${Date.now()}-${Math.random()}`,\n source: overrides?.source || \"background\",\n targets: overrides?.targets || [\"content\"],\n tabId: overrides?.tabId,\n timestamp: overrides?.timestamp || Date.now(),\n payload,\n };\n}\n\nexport function waitFor(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function waitForCondition(\n condition: () => boolean,\n timeout = 1000,\n interval = 10\n): Promise<void> {\n const startTime = Date.now();\n while (!condition()) {\n if (Date.now() - startTime > timeout) {\n throw new Error(\"Condition not met within timeout\");\n }\n await waitFor(interval);\n }\n}\n\nexport function expectType<T>(_value: T): void {\n // Type assertion helper for compile-time checks\n}\n\n/**\n * No-op function for mocks that don't need to do anything\n * Use this instead of empty arrow functions to satisfy linter\n */\n// biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op for mocks\nexport function noOp(): void {}\n\n/**\n * Async no-op function for async mocks that don't need to do anything\n * Use this instead of empty async arrow functions to satisfy linter\n */\n// biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op for mocks\nexport async function noOpAsync(): Promise<void> {}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMO,SAAS,uBAAmD,CACjE,SACA,WACkB;AAAA,EAClB,OAAO;AAAA,IACL,IAAI,WAAW,MAAM,OAAO,KAAK,IAAI,KAAK,KAAK,OAAO;AAAA,IACtD,QAAQ,WAAW,UAAU;AAAA,IAC7B,SAAS,WAAW,WAAW,CAAC,SAAS;AAAA,IACzC,OAAO,WAAW;AAAA,IAClB,WAAW,WAAW,aAAa,KAAK,IAAI;AAAA,IAC5C;AAAA,EACF;AAAA;AAGK,SAAS,OAAO,CAAC,IAA2B;AAAA,EACjD,OAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA;AAGzD,eAAsB,gBAAgB,CACpC,WACA,UAAU,MACV,WAAW,IACI;AAAA,EACf,MAAM,YAAY,KAAK,IAAI;AAAA,EAC3B,OAAO,CAAC,UAAU,GAAG;AAAA,IACnB,IAAI,KAAK,IAAI,IAAI,YAAY,SAAS;AAAA,MACpC,MAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AAAA,IACA,MAAM,QAAQ,QAAQ;AAAA,EACxB;AAAA;AAGK,SAAS,UAAa,CAAC,QAAiB;AASxC,SAAS,IAAI,GAAS;AAO7B,eAAsB,SAAS,GAAkB;",
|
|
8
|
+
"debugId": "1D7B3D33F0FB579464756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PNG comparison via pixelmatch.
|
|
3
|
+
*
|
|
4
|
+
* Returns the number of differing pixels and the mismatch ratio against
|
|
5
|
+
* a tolerance. Emits a diff PNG on failure so the author can see which
|
|
6
|
+
* region changed.
|
|
7
|
+
*/
|
|
8
|
+
export type VisualCompareOptions = {
|
|
9
|
+
/** Pixel-level colour tolerance (0..1). Default 0.1 — per pixel diff threshold. */
|
|
10
|
+
threshold?: number;
|
|
11
|
+
/** Max differing pixels allowed as a ratio of total pixels. Default 0.001. */
|
|
12
|
+
mismatchRatio?: number;
|
|
13
|
+
/** When true, includes anti-aliased pixels in the count. Default false. */
|
|
14
|
+
includeAA?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type VisualCompareResult = {
|
|
17
|
+
match: boolean;
|
|
18
|
+
diffPixels: number;
|
|
19
|
+
totalPixels: number;
|
|
20
|
+
ratio: number;
|
|
21
|
+
diffPngPath?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function compareImages(baselinePath: string, actualPath: string, diffPath: string, options?: VisualCompareOptions): VisualCompareResult;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual regression harness.
|
|
3
|
+
*
|
|
4
|
+
* Runs on the Node side of the Puppeteer browser test runner (not in
|
|
5
|
+
* the browser tab). Takes a full-page or selector-scoped screenshot,
|
|
6
|
+
* compares it against a baseline PNG, writes a diff PNG on mismatch,
|
|
7
|
+
* and returns the result. Set `POLLY_VISUAL_UPDATE=1` to overwrite the
|
|
8
|
+
* baseline instead of comparing — use only locally. CI should refuse
|
|
9
|
+
* that env var and fail on any diff.
|
|
10
|
+
*/
|
|
11
|
+
import type { Page } from "puppeteer";
|
|
12
|
+
import { type VisualCompareOptions, type VisualCompareResult } from "./compare.ts";
|
|
13
|
+
export type VisualMatchOptions = VisualCompareOptions & {
|
|
14
|
+
/** CSS selector to scope the screenshot. Defaults to the full page. */
|
|
15
|
+
selector?: string;
|
|
16
|
+
/** Elements matching these selectors are replaced with blanks before capture. */
|
|
17
|
+
maskSelectors?: string[];
|
|
18
|
+
/** Force prefers-color-scheme. Defaults to system. */
|
|
19
|
+
theme?: "light" | "dark";
|
|
20
|
+
/** Force prefers-reduced-motion. Defaults to system. */
|
|
21
|
+
motion?: "full" | "reduced";
|
|
22
|
+
/** Wait this long after setting emulation and before the shot. Default 100ms. */
|
|
23
|
+
settleMs?: number;
|
|
24
|
+
};
|
|
25
|
+
export type VisualMatchSuccess = {
|
|
26
|
+
name: string;
|
|
27
|
+
passed: true;
|
|
28
|
+
result: VisualCompareResult;
|
|
29
|
+
};
|
|
30
|
+
export type VisualMatchFailure = {
|
|
31
|
+
name: string;
|
|
32
|
+
passed: false;
|
|
33
|
+
reason: string;
|
|
34
|
+
result?: VisualCompareResult;
|
|
35
|
+
baselinePath: string;
|
|
36
|
+
actualPath: string;
|
|
37
|
+
diffPath?: string;
|
|
38
|
+
};
|
|
39
|
+
export type VisualMatchResult = VisualMatchSuccess | VisualMatchFailure;
|
|
40
|
+
export declare function isUpdateMode(): boolean;
|
|
41
|
+
export declare function isCi(): boolean;
|
|
42
|
+
/** Refuses update mode in CI. Call at the top of a visual test runner. */
|
|
43
|
+
export declare function assertSafeUpdateMode(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Take a screenshot, compare against the committed baseline, and return
|
|
46
|
+
* a structured result. When the baseline is missing, creates it (in
|
|
47
|
+
* update mode) or fails with a clear message (otherwise).
|
|
48
|
+
*/
|
|
49
|
+
export declare function matchBaseline(page: Page, name: string, baselinesDir: string, diffsDir: string, options?: VisualMatchOptions): Promise<VisualMatchResult>;
|
|
50
|
+
export declare function resolveVisualPaths(projectRoot: string): {
|
|
51
|
+
baselinesDir: string;
|
|
52
|
+
diffsDir: string;
|
|
53
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fairfox/polly/test/visual — visual regression harness.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing Puppeteer browser harness with screenshot capture
|
|
5
|
+
* and baseline diffing via pixelmatch. Consumer tests call
|
|
6
|
+
* `matchBaseline(page, name, baselinesDir, diffsDir)` on an already-
|
|
7
|
+
* rendered page; the result carries pass/fail plus the diff PNG path.
|
|
8
|
+
*
|
|
9
|
+
* import { matchBaseline, resolveVisualPaths } from "@fairfox/polly/test/visual";
|
|
10
|
+
*/
|
|
11
|
+
export { compareImages, type VisualCompareOptions, type VisualCompareResult, } from "./compare.ts";
|
|
12
|
+
export { assertSafeUpdateMode, isCi, isUpdateMode, matchBaseline, resolveVisualPaths, type VisualMatchFailure, type VisualMatchOptions, type VisualMatchResult, type VisualMatchSuccess, } from "./harness.ts";
|