@fairfox/polly 0.20.1 → 0.22.0

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.
Files changed (62) hide show
  1. package/README.md +83 -3
  2. package/dist/cli/polly.js +21 -1
  3. package/dist/cli/polly.js.map +3 -3
  4. package/dist/src/background/index.js.map +7 -7
  5. package/dist/src/background/message-router.js.map +7 -7
  6. package/dist/src/elysia/index.d.ts +2 -0
  7. package/dist/src/elysia/index.js +177 -17
  8. package/dist/src/elysia/index.js.map +8 -5
  9. package/dist/src/elysia/peer-repo-plugin.d.ts +79 -0
  10. package/dist/src/elysia/signaling-server-plugin.d.ts +121 -0
  11. package/dist/src/index.d.ts +4 -0
  12. package/dist/src/index.js +90 -1
  13. package/dist/src/index.js.map +15 -13
  14. package/dist/src/mesh.d.ts +29 -0
  15. package/dist/src/mesh.js +1502 -0
  16. package/dist/src/mesh.js.map +22 -0
  17. package/dist/src/peer.d.ts +29 -0
  18. package/dist/src/peer.js +928 -0
  19. package/dist/src/peer.js.map +20 -0
  20. package/dist/src/shared/adapters/index.js.map +6 -6
  21. package/dist/src/shared/lib/_client-only.d.ts +38 -0
  22. package/dist/src/shared/lib/access.d.ts +124 -0
  23. package/dist/src/shared/lib/blob-ref.d.ts +72 -0
  24. package/dist/src/shared/lib/context-helpers.js.map +7 -7
  25. package/dist/src/shared/lib/crdt-specialised.d.ts +129 -0
  26. package/dist/src/shared/lib/crdt-state.d.ts +86 -0
  27. package/dist/src/shared/lib/encryption.d.ts +117 -0
  28. package/dist/src/shared/lib/mesh-network-adapter.d.ts +130 -0
  29. package/dist/src/shared/lib/mesh-signaling-client.d.ts +85 -0
  30. package/dist/src/shared/lib/mesh-state.d.ts +102 -0
  31. package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +132 -0
  32. package/dist/src/shared/lib/message-bus.js.map +7 -7
  33. package/dist/src/shared/lib/migrate-primitive.d.ts +100 -0
  34. package/dist/src/shared/lib/pairing.d.ts +170 -0
  35. package/dist/src/shared/lib/peer-relay-adapter.d.ts +80 -0
  36. package/dist/src/shared/lib/peer-repo-server.d.ts +83 -0
  37. package/dist/src/shared/lib/peer-state.d.ts +117 -0
  38. package/dist/src/shared/lib/primitive-registry.d.ts +88 -0
  39. package/dist/src/shared/lib/resource.js.map +4 -4
  40. package/dist/src/shared/lib/revocation.d.ts +126 -0
  41. package/dist/src/shared/lib/schema-version.d.ts +129 -0
  42. package/dist/src/shared/lib/signing.d.ts +118 -0
  43. package/dist/src/shared/lib/state.js.map +4 -4
  44. package/dist/src/shared/state/app-state.js.map +5 -5
  45. package/dist/tools/init/src/cli.js.map +1 -1
  46. package/dist/tools/quality/src/cli.js +162 -0
  47. package/dist/tools/quality/src/cli.js.map +11 -0
  48. package/dist/tools/test/src/adapters/index.js.map +2 -2
  49. package/dist/tools/test/src/browser/harness.d.ts +80 -0
  50. package/dist/tools/test/src/browser/index.d.ts +32 -0
  51. package/dist/tools/test/src/browser/index.js +243 -0
  52. package/dist/tools/test/src/browser/index.js.map +10 -0
  53. package/dist/tools/test/src/browser/run.d.ts +26 -0
  54. package/dist/tools/test/src/index.js.map +2 -2
  55. package/dist/tools/verify/specs/tla/MeshState.cfg +21 -0
  56. package/dist/tools/verify/specs/tla/MeshState.tla +247 -0
  57. package/dist/tools/verify/specs/tla/PeerState.cfg +27 -0
  58. package/dist/tools/verify/specs/tla/PeerState.tla +238 -0
  59. package/dist/tools/verify/specs/tla/README.md +27 -3
  60. package/dist/tools/verify/src/cli.js.map +8 -8
  61. package/dist/tools/visualize/src/cli.js.map +7 -7
  62. package/package.json +51 -5
@@ -0,0 +1,162 @@
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/quality/src/no-as-casting.ts
21
+ import { readFileSync } from "node:fs";
22
+ import { Glob } from "bun";
23
+ function isLineClean(line) {
24
+ if (!line.includes(" as "))
25
+ return true;
26
+ const trimmed = line.trim();
27
+ if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
28
+ return true;
29
+ }
30
+ if (line.match(/\bas const\b/)) {
31
+ const withoutAsConst = line.replace(/\bas const\b/g, "");
32
+ if (!withoutAsConst.includes(" as "))
33
+ return true;
34
+ }
35
+ if (line.includes(" as unknown as ") || line.trimEnd().endsWith("as unknown as")) {
36
+ const withoutEscapeHatch = line.replace(/\bas unknown as\b/g, "");
37
+ if (!withoutEscapeHatch.includes(" as "))
38
+ return true;
39
+ }
40
+ if (line.match(/\b(import|export)\s+.*\s+as\s+\w+/) || line.match(/\b(import|export)\s+\*\s+as\s+\w+/) || line.match(/\b(import|export)\s+type\s+.*\s+as\s+\w+/) || line.match(/^\s*\w+\s+as\s+\w+,?\s*$/) || line.match(/^\s*type\s+\w+\s+as\s+\w+,?\s*$/)) {
41
+ return true;
42
+ }
43
+ if (line.match(/\bas\s*[=:,]/))
44
+ return true;
45
+ if (line.match(/\)\s+as\s+\w+/))
46
+ return true;
47
+ const idx = line.indexOf(" as ");
48
+ if (idx >= 0) {
49
+ const before = line.substring(0, idx);
50
+ const singleQuotes = (before.match(/'/g) ?? []).length;
51
+ const doubleQuotes = (before.match(/"/g) ?? []).length;
52
+ const backticks = (before.match(/`/g) ?? []).length;
53
+ if (singleQuotes % 2 === 1 || doubleQuotes % 2 === 1 || backticks % 2 === 1) {
54
+ return true;
55
+ }
56
+ }
57
+ const commentIdx = line.indexOf("//");
58
+ if (commentIdx >= 0 && line.indexOf(" as ", commentIdx) >= 0) {
59
+ const beforeComment = line.substring(0, commentIdx);
60
+ if (!beforeComment.includes(" as "))
61
+ return true;
62
+ }
63
+ if (line.includes(" satisfies "))
64
+ return true;
65
+ return false;
66
+ }
67
+ function suggestFix(line) {
68
+ if (line.includes("JSON.parse")) {
69
+ return "Use a validation function or type guard to parse and validate the result.";
70
+ }
71
+ if (line.includes("as HTMLInputElement") || line.includes("as HTMLTextAreaElement") || line.includes("as HTMLButtonElement")) {
72
+ return "Use instanceof: if (el instanceof HTMLInputElement) { el.value ... }";
73
+ }
74
+ if (line.includes("as HTMLElement") || line.includes("as Element")) {
75
+ return "Use instanceof: if (el instanceof HTMLElement) { ... }";
76
+ }
77
+ if (line.includes(".doc()") && line.includes("as ")) {
78
+ return "Type the DocHandle generic: repo.find<MyType>(id) returns DocHandle<MyType>.";
79
+ }
80
+ if (line.includes("Record<string, unknown>") && (line.includes("window") || line.includes("globalThis"))) {
81
+ return "Extract a type guard: function getGlobalProp(name: string): unknown { ... }";
82
+ }
83
+ if (line.includes("Record<string, unknown>")) {
84
+ return "Use a type guard function that narrows the unknown value to the target shape.";
85
+ }
86
+ if (line.includes("as PeerId") || line.includes("as DocumentId")) {
87
+ return "Use the library's branded-type constructor if available, or centralise the cast in a factory.";
88
+ }
89
+ if (line.includes("as string") || line.includes("as number") || line.includes("as boolean")) {
90
+ return "Narrow with typeof: if (typeof x === 'string') { ... }";
91
+ }
92
+ if (line.includes("as any")) {
93
+ return "Replace 'any' with 'unknown' and add a type guard or validation at the boundary.";
94
+ }
95
+ return;
96
+ }
97
+ async function checkNoAsCasting(options) {
98
+ const rootDir = options.rootDir;
99
+ const excludeDirs = new Set(options.exclude ?? ["node_modules", "dist", ".git", ".bun"]);
100
+ const pattern = options.filePatterns ?? "**/*.{ts,tsx}";
101
+ const glob = new Glob(pattern);
102
+ const violations = [];
103
+ for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {
104
+ const relative = file.replace(`${rootDir}/`, "");
105
+ const segments = relative.split("/");
106
+ if (segments.some((s) => excludeDirs.has(s)))
107
+ continue;
108
+ const content = readFileSync(file, "utf-8");
109
+ const lines = content.split(`
110
+ `);
111
+ for (let i = 0;i < lines.length; i++) {
112
+ const line = lines[i] ?? "";
113
+ if (!isLineClean(line)) {
114
+ violations.push({
115
+ file: relative,
116
+ line: i + 1,
117
+ content: line.trim(),
118
+ advice: suggestFix(line.trim())
119
+ });
120
+ }
121
+ }
122
+ }
123
+ return {
124
+ violations,
125
+ print() {
126
+ if (violations.length === 0) {
127
+ console.log("[no-as-casting] ✅ No violations found.");
128
+ return;
129
+ }
130
+ console.log(`[no-as-casting] ❌ ${violations.length} violation(s) found:
131
+ `);
132
+ for (const v of violations) {
133
+ console.log(` ${v.file}:${v.line}`);
134
+ console.log(` ${v.content}`);
135
+ if (v.advice)
136
+ console.log(` \uD83D\uDCA1 ${v.advice}`);
137
+ console.log();
138
+ }
139
+ console.log("[no-as-casting] Use type guards, validation, or fix the types at the source.");
140
+ console.log('[no-as-casting] Only "as const" and "as unknown as" are allowed.');
141
+ }
142
+ };
143
+ }
144
+
145
+ // tools/quality/src/cli.ts
146
+ var args = process.argv.slice(2);
147
+ function getFlag(name) {
148
+ const idx = args.indexOf(`--${name}`);
149
+ return idx >= 0 ? args[idx + 1] : undefined;
150
+ }
151
+ var rootDir = getFlag("root") ?? process.cwd();
152
+ var exclude = getFlag("exclude")?.split(",") ?? ["node_modules", "dist", ".git", ".bun"];
153
+ var filePatterns = getFlag("pattern");
154
+ var result = await checkNoAsCasting({
155
+ rootDir,
156
+ exclude,
157
+ ...filePatterns ? { filePatterns } : {}
158
+ });
159
+ result.print();
160
+ process.exit(result.violations.length > 0 ? 1 : 0);
161
+
162
+ //# debugId=C4D3FE8A6910608764756E2164756E21
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../tools/quality/src/no-as-casting.ts", "../tools/quality/src/cli.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * No-as-casting conformance check.\n *\n * Bans all TypeScript type assertions (`as Type`) except the allowed\n * patterns: `as const` (literal narrowing), `as unknown as` (explicit\n * escape hatch), import/export renames, and `as` inside strings or\n * comments. Violations include pattern-specific fix advice.\n *\n * This module exports the check logic as a library so consuming\n * applications can import it from `@fairfox/polly/quality` and run it\n * programmatically. Polly's own `scripts/check-no-as-casting.ts` is a\n * thin CLI wrapper around these exports.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { Glob } from \"bun\";\n\nexport interface Violation {\n file: string;\n line: number;\n content: string;\n advice?: string;\n}\n\nexport interface CheckResult {\n violations: Violation[];\n print: () => void;\n}\n\nexport interface CheckOptions {\n rootDir: string;\n exclude?: string[];\n filePatterns?: string;\n}\n\n/**\n * Check whether a line contains a forbidden `as` type assertion.\n * Returns true if the line is clean (no violation), false if it violates.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Source-text scanning with many skip rules is inherently branchy.\nexport function isLineClean(line: string): boolean {\n if (!line.includes(\" as \")) return true;\n\n const trimmed = line.trim();\n\n if (trimmed.startsWith(\"//\") || trimmed.startsWith(\"*\") || trimmed.startsWith(\"/*\")) {\n return true;\n }\n\n if (line.match(/\\bas const\\b/)) {\n const withoutAsConst = line.replace(/\\bas const\\b/g, \"\");\n if (!withoutAsConst.includes(\" as \")) return true;\n }\n\n if (line.includes(\" as unknown as \") || line.trimEnd().endsWith(\"as unknown as\")) {\n const withoutEscapeHatch = line.replace(/\\bas unknown as\\b/g, \"\");\n if (!withoutEscapeHatch.includes(\" as \")) return true;\n }\n\n if (\n line.match(/\\b(import|export)\\s+.*\\s+as\\s+\\w+/) ||\n line.match(/\\b(import|export)\\s+\\*\\s+as\\s+\\w+/) ||\n line.match(/\\b(import|export)\\s+type\\s+.*\\s+as\\s+\\w+/) ||\n line.match(/^\\s*\\w+\\s+as\\s+\\w+,?\\s*$/) ||\n line.match(/^\\s*type\\s+\\w+\\s+as\\s+\\w+,?\\s*$/)\n ) {\n return true;\n }\n\n if (line.match(/\\bas\\s*[=:,]/)) return true;\n if (line.match(/\\)\\s+as\\s+\\w+/)) return true;\n\n const idx = line.indexOf(\" as \");\n if (idx >= 0) {\n const before = line.substring(0, idx);\n const singleQuotes = (before.match(/'/g) ?? []).length;\n const doubleQuotes = (before.match(/\"/g) ?? []).length;\n const backticks = (before.match(/`/g) ?? []).length;\n if (singleQuotes % 2 === 1 || doubleQuotes % 2 === 1 || backticks % 2 === 1) {\n return true;\n }\n }\n\n const commentIdx = line.indexOf(\"//\");\n if (commentIdx >= 0 && line.indexOf(\" as \", commentIdx) >= 0) {\n const beforeComment = line.substring(0, commentIdx);\n if (!beforeComment.includes(\" as \")) return true;\n }\n\n if (line.includes(\" satisfies \")) return true;\n\n return false;\n}\n\n/**\n * Suggest a concrete fix for a specific violation pattern.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Pattern-matching advice is a linear chain of if-returns.\nexport function suggestFix(line: string): string | undefined {\n if (line.includes(\"JSON.parse\")) {\n return \"Use a validation function or type guard to parse and validate the result.\";\n }\n if (\n line.includes(\"as HTMLInputElement\") ||\n line.includes(\"as HTMLTextAreaElement\") ||\n line.includes(\"as HTMLButtonElement\")\n ) {\n return \"Use instanceof: if (el instanceof HTMLInputElement) { el.value ... }\";\n }\n if (line.includes(\"as HTMLElement\") || line.includes(\"as Element\")) {\n return \"Use instanceof: if (el instanceof HTMLElement) { ... }\";\n }\n if (line.includes(\".doc()\") && line.includes(\"as \")) {\n return \"Type the DocHandle generic: repo.find<MyType>(id) returns DocHandle<MyType>.\";\n }\n if (\n line.includes(\"Record<string, unknown>\") &&\n (line.includes(\"window\") || line.includes(\"globalThis\"))\n ) {\n return \"Extract a type guard: function getGlobalProp(name: string): unknown { ... }\";\n }\n if (line.includes(\"Record<string, unknown>\")) {\n return \"Use a type guard function that narrows the unknown value to the target shape.\";\n }\n if (line.includes(\"as PeerId\") || line.includes(\"as DocumentId\")) {\n return \"Use the library's branded-type constructor if available, or centralise the cast in a factory.\";\n }\n if (line.includes(\"as string\") || line.includes(\"as number\") || line.includes(\"as boolean\")) {\n return \"Narrow with typeof: if (typeof x === 'string') { ... }\";\n }\n if (line.includes(\"as any\")) {\n return \"Replace 'any' with 'unknown' and add a type guard or validation at the boundary.\";\n }\n return undefined;\n}\n\n/**\n * Run the no-as-casting check against a directory. Returns a result\n * object with the violations and a print function for CLI output.\n */\nexport async function checkNoAsCasting(options: CheckOptions): Promise<CheckResult> {\n const rootDir = options.rootDir;\n const excludeDirs = new Set(options.exclude ?? [\"node_modules\", \"dist\", \".git\", \".bun\"]);\n const pattern = options.filePatterns ?? \"**/*.{ts,tsx}\";\n const glob = new Glob(pattern);\n const violations: Violation[] = [];\n\n for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {\n const relative = file.replace(`${rootDir}/`, \"\");\n const segments = relative.split(\"/\");\n if (segments.some((s) => excludeDirs.has(s))) continue;\n\n const content = readFileSync(file, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n if (!isLineClean(line)) {\n violations.push({\n file: relative,\n line: i + 1,\n content: line.trim(),\n advice: suggestFix(line.trim()),\n });\n }\n }\n }\n\n return {\n violations,\n print() {\n if (violations.length === 0) {\n console.log(\"[no-as-casting] ✅ No violations found.\");\n return;\n }\n console.log(`[no-as-casting] ❌ ${violations.length} violation(s) found:\\n`);\n for (const v of violations) {\n console.log(` ${v.file}:${v.line}`);\n console.log(` ${v.content}`);\n if (v.advice) console.log(` 💡 ${v.advice}`);\n console.log();\n }\n console.log(\"[no-as-casting] Use type guards, validation, or fix the types at the source.\");\n console.log('[no-as-casting] Only \"as const\" and \"as unknown as\" are allowed.');\n },\n };\n}\n",
6
+ "#!/usr/bin/env bun\n\n/**\n * CLI entry point for Polly quality checks.\n *\n * polly quality [--root <dir>] [--exclude <dirs>] [--pattern <glob>]\n *\n * Runs all conformance checks (currently: no-as-casting) against the\n * target directory and exits non-zero when violations are found.\n */\n\nimport { checkNoAsCasting } from \"./no-as-casting\";\n\nconst args = process.argv.slice(2);\n\nfunction getFlag(name: string): string | undefined {\n const idx = args.indexOf(`--${name}`);\n return idx >= 0 ? args[idx + 1] : undefined;\n}\n\nconst rootDir = getFlag(\"root\") ?? process.cwd();\nconst exclude = getFlag(\"exclude\")?.split(\",\") ?? [\"node_modules\", \"dist\", \".git\", \".bun\"];\nconst filePatterns = getFlag(\"pattern\");\n\nconst result = await checkNoAsCasting({\n rootDir,\n exclude,\n ...(filePatterns ? { filePatterns } : {}),\n});\n\nresult.print();\nprocess.exit(result.violations.length > 0 ? 1 : 0);\n"
7
+ ],
8
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAcA;AACA;AAyBO,SAAS,WAAW,CAAC,MAAuB;AAAA,EACjD,IAAI,CAAC,KAAK,SAAS,MAAM;AAAA,IAAG,OAAO;AAAA,EAEnC,MAAM,UAAU,KAAK,KAAK;AAAA,EAE1B,IAAI,QAAQ,WAAW,IAAI,KAAK,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,IAAI,GAAG;AAAA,IACnF,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,KAAK,MAAM,cAAc,GAAG;AAAA,IAC9B,MAAM,iBAAiB,KAAK,QAAQ,iBAAiB,EAAE;AAAA,IACvD,IAAI,CAAC,eAAe,SAAS,MAAM;AAAA,MAAG,OAAO;AAAA,EAC/C;AAAA,EAEA,IAAI,KAAK,SAAS,iBAAiB,KAAK,KAAK,QAAQ,EAAE,SAAS,eAAe,GAAG;AAAA,IAChF,MAAM,qBAAqB,KAAK,QAAQ,sBAAsB,EAAE;AAAA,IAChE,IAAI,CAAC,mBAAmB,SAAS,MAAM;AAAA,MAAG,OAAO;AAAA,EACnD;AAAA,EAEA,IACE,KAAK,MAAM,mCAAmC,KAC9C,KAAK,MAAM,mCAAmC,KAC9C,KAAK,MAAM,0CAA0C,KACrD,KAAK,MAAM,0BAA0B,KACrC,KAAK,MAAM,iCAAiC,GAC5C;AAAA,IACA,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,KAAK,MAAM,cAAc;AAAA,IAAG,OAAO;AAAA,EACvC,IAAI,KAAK,MAAM,eAAe;AAAA,IAAG,OAAO;AAAA,EAExC,MAAM,MAAM,KAAK,QAAQ,MAAM;AAAA,EAC/B,IAAI,OAAO,GAAG;AAAA,IACZ,MAAM,SAAS,KAAK,UAAU,GAAG,GAAG;AAAA,IACpC,MAAM,gBAAgB,OAAO,MAAM,IAAI,KAAK,CAAC,GAAG;AAAA,IAChD,MAAM,gBAAgB,OAAO,MAAM,IAAI,KAAK,CAAC,GAAG;AAAA,IAChD,MAAM,aAAa,OAAO,MAAM,IAAI,KAAK,CAAC,GAAG;AAAA,IAC7C,IAAI,eAAe,MAAM,KAAK,eAAe,MAAM,KAAK,YAAY,MAAM,GAAG;AAAA,MAC3E,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,KAAK,QAAQ,IAAI;AAAA,EACpC,IAAI,cAAc,KAAK,KAAK,QAAQ,QAAQ,UAAU,KAAK,GAAG;AAAA,IAC5D,MAAM,gBAAgB,KAAK,UAAU,GAAG,UAAU;AAAA,IAClD,IAAI,CAAC,cAAc,SAAS,MAAM;AAAA,MAAG,OAAO;AAAA,EAC9C;AAAA,EAEA,IAAI,KAAK,SAAS,aAAa;AAAA,IAAG,OAAO;AAAA,EAEzC,OAAO;AAAA;AAOF,SAAS,UAAU,CAAC,MAAkC;AAAA,EAC3D,IAAI,KAAK,SAAS,YAAY,GAAG;AAAA,IAC/B,OAAO;AAAA,EACT;AAAA,EACA,IACE,KAAK,SAAS,qBAAqB,KACnC,KAAK,SAAS,wBAAwB,KACtC,KAAK,SAAS,sBAAsB,GACpC;AAAA,IACA,OAAO;AAAA,EACT;AAAA,EACA,IAAI,KAAK,SAAS,gBAAgB,KAAK,KAAK,SAAS,YAAY,GAAG;AAAA,IAClE,OAAO;AAAA,EACT;AAAA,EACA,IAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,KAAK,GAAG;AAAA,IACnD,OAAO;AAAA,EACT;AAAA,EACA,IACE,KAAK,SAAS,yBAAyB,MACtC,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,YAAY,IACtD;AAAA,IACA,OAAO;AAAA,EACT;AAAA,EACA,IAAI,KAAK,SAAS,yBAAyB,GAAG;AAAA,IAC5C,OAAO;AAAA,EACT;AAAA,EACA,IAAI,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,eAAe,GAAG;AAAA,IAChE,OAAO;AAAA,EACT;AAAA,EACA,IAAI,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,YAAY,GAAG;AAAA,IAC3F,OAAO;AAAA,EACT;AAAA,EACA,IAAI,KAAK,SAAS,QAAQ,GAAG;AAAA,IAC3B,OAAO;AAAA,EACT;AAAA,EACA;AAAA;AAOF,eAAsB,gBAAgB,CAAC,SAA6C;AAAA,EAClF,MAAM,UAAU,QAAQ;AAAA,EACxB,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAW,CAAC,gBAAgB,QAAQ,QAAQ,MAAM,CAAC;AAAA,EACvF,MAAM,UAAU,QAAQ,gBAAgB;AAAA,EACxC,MAAM,OAAO,IAAI,KAAK,OAAO;AAAA,EAC7B,MAAM,aAA0B,CAAC;AAAA,EAEjC,iBAAiB,QAAQ,KAAK,KAAK,EAAE,KAAK,SAAS,UAAU,KAAK,CAAC,GAAG;AAAA,IACpE,MAAM,WAAW,KAAK,QAAQ,GAAG,YAAY,EAAE;AAAA,IAC/C,MAAM,WAAW,SAAS,MAAM,GAAG;AAAA,IACnC,IAAI,SAAS,KAAK,CAAC,MAAM,YAAY,IAAI,CAAC,CAAC;AAAA,MAAG;AAAA,IAE9C,MAAM,UAAU,aAAa,MAAM,OAAO;AAAA,IAC1C,MAAM,QAAQ,QAAQ,MAAM;AAAA,CAAI;AAAA,IAEhC,SAAS,IAAI,EAAG,IAAI,MAAM,QAAQ,KAAK;AAAA,MACrC,MAAM,OAAO,MAAM,MAAM;AAAA,MACzB,IAAI,CAAC,YAAY,IAAI,GAAG;AAAA,QACtB,WAAW,KAAK;AAAA,UACd,MAAM;AAAA,UACN,MAAM,IAAI;AAAA,UACV,SAAS,KAAK,KAAK;AAAA,UACnB,QAAQ,WAAW,KAAK,KAAK,CAAC;AAAA,QAChC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA,KAAK,GAAG;AAAA,MACN,IAAI,WAAW,WAAW,GAAG;AAAA,QAC3B,QAAQ,IAAI,wCAAuC;AAAA,QACnD;AAAA,MACF;AAAA,MACA,QAAQ,IAAI,qBAAoB,WAAW;AAAA,CAA8B;AAAA,MACzE,WAAW,KAAK,YAAY;AAAA,QAC1B,QAAQ,IAAI,KAAK,EAAE,QAAQ,EAAE,MAAM;AAAA,QACnC,QAAQ,IAAI,OAAO,EAAE,SAAS;AAAA,QAC9B,IAAI,EAAE;AAAA,UAAQ,QAAQ,IAAI,oBAAS,EAAE,QAAQ;AAAA,QAC7C,QAAQ,IAAI;AAAA,MACd;AAAA,MACA,QAAQ,IAAI,8EAA8E;AAAA,MAC1F,QAAQ,IAAI,kEAAkE;AAAA;AAAA,EAElF;AAAA;;;AC5KF,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,SAAS,OAAO,CAAC,MAAkC;AAAA,EACjD,MAAM,MAAM,KAAK,QAAQ,KAAK,MAAM;AAAA,EACpC,OAAO,OAAO,IAAI,KAAK,MAAM,KAAK;AAAA;AAGpC,IAAM,UAAU,QAAQ,MAAM,KAAK,QAAQ,IAAI;AAC/C,IAAM,UAAU,QAAQ,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC,gBAAgB,QAAQ,QAAQ,MAAM;AACzF,IAAM,eAAe,QAAQ,SAAS;AAEtC,IAAM,SAAS,MAAM,iBAAiB;AAAA,EACpC;AAAA,EACA;AAAA,KACI,eAAe,EAAE,aAAa,IAAI,CAAC;AACzC,CAAC;AAED,OAAO,MAAM;AACb,QAAQ,KAAK,OAAO,WAAW,SAAS,IAAI,IAAI,CAAC;",
9
+ "debugId": "C4D3FE8A6910608764756E2164756E21",
10
+ "names": []
11
+ }
@@ -3,11 +3,11 @@
3
3
  "sources": ["../tools/test/src/adapters/context-menus.mock.ts", "../tools/test/src/adapters/fetch.mock.ts", "../tools/test/src/adapters/logger.mock.ts", "../tools/test/src/adapters/offscreen.mock.ts", "../tools/test/src/adapters/runtime.mock.ts", "../tools/test/src/adapters/storage.mock.ts", "../tools/test/src/adapters/tabs.mock.ts", "../tools/test/src/adapters/window.mock.ts", "../tools/test/src/adapters/index.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { ContextMenusAdapter } from \"@/shared/adapters/context-menus.adapter\";\n\nexport interface MockContextMenus extends ContextMenusAdapter {\n _menus: Map<string, chrome.contextMenus.CreateProperties>;\n}\n\nexport function createMockContextMenus(): MockContextMenus {\n const menus = new Map<string, chrome.contextMenus.CreateProperties>();\n\n return {\n create: async (createProperties: chrome.contextMenus.CreateProperties): Promise<void> => {\n if (createProperties.id) {\n menus.set(createProperties.id, createProperties);\n }\n },\n update: async (\n _id: string,\n _updateProperties: Omit<chrome.contextMenus.CreateProperties, \"id\">\n ): Promise<void> => {\n // Mock implementation\n },\n remove: async (_id: string): Promise<void> => {\n // Mock implementation\n },\n removeAll: async (): Promise<void> => {\n // Mock implementation\n },\n onClicked: (\n _callback: (info: chrome.contextMenus.OnClickData, tab?: chrome.tabs.Tab) => void\n ): void => {\n // Mock implementation\n },\n _menus: menus,\n };\n}\n",
6
- "import type { FetchAdapter } from \"@/shared/adapters/fetch.adapter\";\n\nexport interface MockFetch extends FetchAdapter {\n _responses: Array<Partial<Response>>;\n _calls: Array<{ input: string | URL; init?: RequestInit }>;\n}\n\nexport function createMockFetch(): MockFetch {\n const responses: Array<Partial<Response>> = [];\n const calls: Array<{ input: string | URL; init?: RequestInit }> = [];\n\n return {\n fetch: async (input: string | URL, init?: RequestInit): Promise<Response> => {\n calls.push({ input, ...(init && { init }) });\n\n const mockResponse = responses.shift() || {\n ok: true,\n status: 200,\n headers: new Headers(),\n statusText: \"OK\",\n json: async () => ({}),\n text: async () => \"\",\n blob: async () => new Blob(),\n arrayBuffer: async () => new ArrayBuffer(0),\n formData: async () => new FormData(),\n };\n\n return mockResponse as Response;\n },\n _responses: responses,\n _calls: calls,\n };\n}\n",
6
+ "import type { FetchAdapter } from \"@/shared/adapters/fetch.adapter\";\n\nexport interface MockFetch extends FetchAdapter {\n _responses: Array<Partial<Response>>;\n _calls: Array<{ input: string | URL; init?: RequestInit }>;\n}\n\nexport function createMockFetch(): MockFetch {\n const responses: Array<Partial<Response>> = [];\n const calls: Array<{ input: string | URL; init?: RequestInit }> = [];\n\n return {\n fetch: async (input: string | URL, init?: RequestInit): Promise<Response> => {\n calls.push({ input, ...(init && { init }) });\n\n const mockResponse = responses.shift() || {\n ok: true,\n status: 200,\n headers: new Headers(),\n statusText: \"OK\",\n json: async () => ({}),\n text: async () => \"\",\n blob: async () => new Blob(),\n arrayBuffer: async () => new ArrayBuffer(0),\n formData: async () => new FormData(),\n };\n\n return mockResponse as unknown as Response;\n },\n _responses: responses,\n _calls: calls,\n };\n}\n",
7
7
  "// Mock logger adapter for testing\nimport type { LoggerAdapter } from \"@/shared/adapters/logger.adapter\";\nimport type { LogLevel } from \"@/shared/types/messages\";\n\nexport interface LogCall {\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n error?: Error;\n timestamp: number;\n}\n\nexport interface MockLogger extends LoggerAdapter {\n _calls: LogCall[];\n _clear(): void;\n}\n\nexport function createMockLogger(options?: { silent?: boolean }): MockLogger {\n const calls: LogCall[] = [];\n const silent = options?.silent ?? true;\n\n const logToConsole = (level: LogLevel, message: string, context?: Record<string, unknown>) => {\n if (!silent) {\n // biome-ignore lint/suspicious/noConsole: Mock logger intentionally uses console for testing\n const consoleMethod = level === \"debug\" ? console.log : console[level];\n consoleMethod(message, context);\n }\n };\n\n return {\n debug(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"debug\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"debug\", message, context);\n },\n\n info(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"info\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"info\", message, context);\n },\n\n warn(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"warn\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"warn\", message, context);\n },\n\n error(message: string, error?: Error, context?: Record<string, unknown>): void {\n calls.push({\n level: \"error\",\n message,\n ...(error && { error }),\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"error\", message, { ...context, error });\n },\n\n log(level: LogLevel, message: string, context?: Record<string, unknown>): void {\n calls.push({\n level,\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(level, message, context);\n },\n\n // Test-only internals\n _calls: calls,\n _clear() {\n calls.length = 0;\n },\n };\n}\n",
8
8
  "import type {\n CreateOffscreenDocumentParameters,\n OffscreenAdapter,\n} from \"@/shared/adapters/offscreen.adapter\";\n\nexport interface MockOffscreen extends OffscreenAdapter {\n _hasDocument: boolean;\n}\n\nexport function createMockOffscreen(): MockOffscreen {\n let hasDocument = false;\n\n return {\n createDocument: async (_parameters: CreateOffscreenDocumentParameters): Promise<void> => {\n hasDocument = true;\n },\n closeDocument: async (): Promise<void> => {\n hasDocument = false;\n },\n hasDocument: async (): Promise<boolean> => {\n return hasDocument;\n },\n _hasDocument: hasDocument,\n };\n}\n",
9
9
  "import type { MessageSender, PortAdapter, RuntimeAdapter } from \"@/shared/adapters/runtime.adapter\";\n\nexport interface MockPort extends PortAdapter {\n _listeners: Set<(message: unknown) => void>;\n _disconnectListeners: Set<() => void>;\n}\n\nexport function createMockPort(name: string): MockPort {\n const listeners = new Set<(message: unknown) => void>();\n const disconnectListeners = new Set<() => void>();\n\n return {\n name,\n onMessage: (callback) => listeners.add(callback),\n onDisconnect: (callback) => disconnectListeners.add(callback),\n postMessage: (message) => {\n for (const listener of listeners) {\n listener(message);\n }\n },\n disconnect: () => {\n for (const listener of disconnectListeners) {\n listener();\n }\n },\n _listeners: listeners,\n _disconnectListeners: disconnectListeners,\n };\n}\n\nexport interface MockRuntime extends RuntimeAdapter {\n id: string;\n _messageListeners: Set<\n (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => void\n >;\n _connectListeners: Set<(port: PortAdapter) => void>;\n}\n\nexport function createMockRuntime(id = \"test-extension-id\"): MockRuntime {\n const messageListeners = new Set<\n (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => void\n >();\n const connectListeners = new Set<(port: PortAdapter) => void>();\n\n return {\n id,\n sendMessage: async <T>(message: T): Promise<unknown> => {\n // Check if this is a response message\n if (typeof message === \"object\" && message !== null && \"success\" in message) {\n // This is a response, route it back to all listeners\n for (const listener of messageListeners) {\n listener(message, { url: \"\" }, () => {\n // Empty response handler for mock\n });\n }\n return undefined;\n }\n\n // This is a request, call ALL listeners (Chrome calls all, but only first response is used)\n if (messageListeners.size === 0) {\n return undefined;\n }\n\n return new Promise((resolve) => {\n let resolved = false;\n const sharedSendResponse = (res: unknown) => {\n if (!resolved) {\n resolved = true;\n resolve(res);\n }\n };\n\n // Call all listeners (Chrome behavior)\n for (const listener of messageListeners) {\n const result = listener(message, { url: \"\" }, sharedSendResponse);\n // If listener returns true, it will send response asynchronously\n // If it returns false/undefined/void and we haven't resolved yet, continue to next listener\n if (typeof result === \"boolean\" && result === true) {\n // Listener will send response asynchronously, wait for it\n }\n }\n\n // If no listener handled it, resolve with undefined\n if (!resolved) {\n resolve(undefined);\n }\n });\n },\n onMessage: (\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ) => {\n messageListeners.add(callback);\n },\n removeMessageListener: (\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ) => {\n messageListeners.delete(callback);\n },\n connect: (name: string): PortAdapter => {\n const port = createMockPort(name);\n for (const listener of connectListeners) {\n listener(port);\n }\n return port;\n },\n onConnect: (callback: (port: PortAdapter) => void) => {\n connectListeners.add(callback);\n },\n getURL: (path: string): string => {\n return `chrome-extension://${id}/${path}`;\n },\n getId: (): string => {\n return id;\n },\n openOptionsPage: (): void => {\n // Mock implementation - no-op for tests\n },\n _messageListeners: messageListeners,\n _connectListeners: connectListeners,\n };\n}\n",
10
- "import type { StorageAdapter, StorageChanges } from \"@/shared/adapters/storage.adapter\";\n\nexport interface MockStorageArea extends StorageAdapter {\n _data: Map<string, unknown>;\n}\n\nexport function createMockStorageArea(): MockStorageArea {\n const data = new Map<string, unknown>();\n\n return {\n get: async <T = Record<string, unknown>>(\n keys?: string | string[] | Record<string, unknown> | null\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Mock storage needs to handle multiple key types\n ): Promise<T> => {\n if (!keys) {\n return Object.fromEntries(data) as T;\n }\n if (typeof keys === \"string\") {\n return (data.has(keys) ? { [keys]: data.get(keys) } : {}) as T;\n }\n if (Array.isArray(keys)) {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (data.has(key)) {\n result[key] = data.get(key);\n }\n }\n return result as T;\n }\n // Object with defaults\n const result: Record<string, unknown> = {};\n for (const [key, defaultValue] of Object.entries(keys)) {\n result[key] = data.has(key) ? data.get(key) : defaultValue;\n }\n return result as T;\n },\n set: async (items) => {\n for (const [key, value] of Object.entries(items)) {\n data.set(key, value);\n }\n },\n remove: async (keys) => {\n const keyArray = Array.isArray(keys) ? keys : [keys];\n for (const key of keyArray) {\n data.delete(key);\n }\n },\n clear: async () => {\n data.clear();\n },\n onChanged: (_callback: (changes: StorageChanges, areaName: string) => void) => {\n // Mock implementation - not needed for current tests\n },\n _data: data,\n };\n}\n",
10
+ "import type { StorageAdapter, StorageChanges } from \"@/shared/adapters/storage.adapter\";\n\nexport interface MockStorageArea extends StorageAdapter {\n _data: Map<string, unknown>;\n}\n\nexport function createMockStorageArea(): MockStorageArea {\n const data = new Map<string, unknown>();\n\n return {\n get: async <T = Record<string, unknown>>(\n keys?: string | string[] | Record<string, unknown> | null\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Mock storage needs to handle multiple key types\n ): Promise<T> => {\n if (!keys) {\n return Object.fromEntries(data) as T;\n }\n if (typeof keys === \"string\") {\n return (data.has(keys) ? { [keys]: data.get(keys) } : {}) as T;\n }\n if (Array.isArray(keys)) {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (data.has(key)) {\n result[key] = data.get(key);\n }\n }\n return result as unknown as T;\n }\n // Object with defaults\n const result: Record<string, unknown> = {};\n for (const [key, defaultValue] of Object.entries(keys)) {\n result[key] = data.has(key) ? data.get(key) : defaultValue;\n }\n return result as unknown as T;\n },\n set: async (items) => {\n for (const [key, value] of Object.entries(items)) {\n data.set(key, value);\n }\n },\n remove: async (keys) => {\n const keyArray = Array.isArray(keys) ? keys : [keys];\n for (const key of keyArray) {\n data.delete(key);\n }\n },\n clear: async () => {\n data.clear();\n },\n onChanged: (_callback: (changes: StorageChanges, areaName: string) => void) => {\n // Mock implementation - not needed for current tests\n },\n _data: data,\n };\n}\n",
11
11
  "import type { TabsAdapter } from \"@/shared/adapters/tabs.adapter\";\n\nexport interface MockTabs extends TabsAdapter {\n _tabs: Map<number, chrome.tabs.Tab>;\n}\n\nexport function createMockTabs(): MockTabs {\n const tabs = new Map<number, chrome.tabs.Tab>();\n\n return {\n query: async (queryInfo: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> => {\n const results: chrome.tabs.Tab[] = [];\n for (const tab of tabs.values()) {\n let matches = true;\n if (queryInfo.active !== undefined && tab.active !== queryInfo.active) {\n matches = false;\n }\n if (queryInfo.currentWindow !== undefined) {\n matches = false;\n }\n if (matches) {\n results.push(tab);\n }\n }\n return results;\n },\n get: async (tabId: number): Promise<chrome.tabs.Tab> => {\n const tab = tabs.get(tabId);\n if (!tab) {\n throw new Error(`Tab ${tabId} not found`);\n }\n return tab;\n },\n sendMessage: async (_tabId: number, _message: unknown): Promise<unknown> => {\n return Promise.resolve({ success: true });\n },\n reload: async (\n _tabId: number,\n _reloadProperties?: { bypassCache?: boolean }\n ): Promise<void> => {\n // Mock implementation\n },\n onRemoved: (\n _callback: (tabId: number, removeInfo: chrome.tabs.OnRemovedInfo) => void\n ): void => {\n // Mock implementation - register listener\n },\n onUpdated: (\n _callback: (\n tabId: number,\n changeInfo: chrome.tabs.OnUpdatedInfo,\n tab: chrome.tabs.Tab\n ) => void\n ): void => {\n // Mock implementation - register listener\n },\n onActivated: (_callback: (activeInfo: { tabId: number; windowId: number }) => void): void => {\n // Mock implementation - register listener\n },\n create: async (createProperties: chrome.tabs.CreateProperties): Promise<chrome.tabs.Tab> => {\n const newTab: chrome.tabs.Tab = {\n id: Math.floor(Math.random() * 10000),\n index: tabs.size,\n pinned: false,\n highlighted: false,\n windowId: 1,\n active: true,\n incognito: false,\n selected: true,\n discarded: false,\n autoDiscardable: true,\n groupId: -1,\n url: createProperties.url || \"about:blank\",\n title: createProperties.url || \"New Tab\",\n frozen: false,\n };\n if (newTab.id !== undefined) {\n tabs.set(newTab.id, newTab);\n }\n return newTab;\n },\n _tabs: tabs,\n };\n}\n",
12
12
  "import type { WindowAdapter } from \"@/shared/adapters/window.adapter\";\n\nexport interface MockWindow extends WindowAdapter {\n _messageListeners: Set<(event: MessageEvent) => void>;\n}\n\nexport function createMockWindow(): MockWindow {\n const messageListeners = new Set<(event: MessageEvent) => void>();\n\n return {\n postMessage: (message: unknown, targetOrigin: string) => {\n const event = new MessageEvent(\"message\", {\n data: message,\n origin: targetOrigin,\n source: null,\n });\n for (const listener of messageListeners) {\n listener(event);\n }\n },\n addEventListener: (type: string, listener: (event: MessageEvent) => void) => {\n if (type === \"message\") {\n messageListeners.add(listener);\n }\n },\n removeEventListener: (type: string, listener: (event: MessageEvent) => void) => {\n if (type === \"message\") {\n messageListeners.delete(listener);\n }\n },\n _messageListeners: messageListeners,\n };\n}\n",
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"
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Browser-side test harness for Polly applications.
3
+ *
4
+ * Provides describe/test/expect/done that run inside a Puppeteer-launched
5
+ * browser tab and record results on window.__testResults for the Node-side
6
+ * runner to collect. Matchers cover both value assertions and DOM element
7
+ * assertions so that Preact component tests and WebRTC adapter tests use
8
+ * the same harness.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { describe, test, expect, done, flush, cleanup } from "@fairfox/polly/test/browser";
13
+ *
14
+ * const app = document.getElementById("app")!;
15
+ *
16
+ * describe("my feature", () => {
17
+ * test("renders correctly", async () => {
18
+ * render(<MyComponent />, app);
19
+ * await flush();
20
+ * expect(app.querySelector("h1")).toHaveTextContent("Hello");
21
+ * cleanup(app);
22
+ * });
23
+ * });
24
+ *
25
+ * done();
26
+ * ```
27
+ */
28
+ export declare function describe(name: string, fn: () => void): void;
29
+ export declare function test(name: string, fn: () => Promise<void> | void): void;
30
+ export declare function expect<T>(actual: T): {
31
+ toBe(expected: T): void;
32
+ toEqual(expected: T): void;
33
+ toContain(sub: string): void;
34
+ toBeTruthy(): void;
35
+ toBeFalsy(): void;
36
+ toBeNull(): void;
37
+ toBeDefined(): void;
38
+ toBeUndefined(): void;
39
+ toBeGreaterThan(expected: number): void;
40
+ toHaveLength(expected: number): void;
41
+ toExist(): void;
42
+ toHaveTextContent(expected: string): void;
43
+ toBeChecked(): void;
44
+ toBeDisabled(): void;
45
+ toHaveValue(expected: string): void;
46
+ toHaveAttribute(name: string, value?: string): void;
47
+ not: {
48
+ toBe(expected: T): void;
49
+ toEqual(expected: T): void;
50
+ toContain(sub: string): void;
51
+ toBeNull(): void;
52
+ toExist(): void;
53
+ toBeChecked(): void;
54
+ toBeDisabled(): void;
55
+ toHaveAttribute(name: string): void;
56
+ };
57
+ };
58
+ /**
59
+ * Flush microtasks and pending DOM updates. Call after signal assignments
60
+ * or render calls to give the reactive system and the browser a chance to
61
+ * settle before asserting on the result.
62
+ */
63
+ export declare function flush(ms?: number): Promise<void>;
64
+ /**
65
+ * Clear a container's rendered content. Call at the end of each test to
66
+ * prevent state leaking between tests. If you use Preact's render(), pass
67
+ * the same container; the function calls render(null, container) if Preact
68
+ * is available, otherwise sets innerHTML to "".
69
+ */
70
+ export declare function cleanup(container: Element): void;
71
+ /**
72
+ * Run all registered tests and write results to window.__testResults.
73
+ * Call this at the end of every .browser.ts test file.
74
+ */
75
+ export declare function done(): Promise<void>;
76
+ /**
77
+ * Wait until a predicate returns true, polling every intervalMs. Rejects
78
+ * after timeoutMs.
79
+ */
80
+ export declare function waitFor(predicate: () => boolean | Promise<boolean>, timeoutMs?: number, intervalMs?: number): Promise<void>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fairfox/polly/test/browser — browser-side test harness for Polly
3
+ * applications.
4
+ *
5
+ * Provides a lightweight describe/test/expect harness that runs inside a
6
+ * Puppeteer-launched browser tab. Results are recorded on window.__testResults
7
+ * and polled by the companion runner (run.ts). Applications import this
8
+ * module in their *.browser.ts test files and call done() at the end.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { describe, test, expect, done, waitFor } from "@fairfox/polly/test/browser";
13
+ *
14
+ * describe("my feature", () => {
15
+ * test("works in a real browser", async () => {
16
+ * expect(1 + 1).toBe(2);
17
+ * });
18
+ * });
19
+ *
20
+ * done();
21
+ * ```
22
+ *
23
+ * The runner is at tools/test/src/browser/run.ts and is invoked via:
24
+ *
25
+ * bun tools/test/src/browser/run.ts [filter]
26
+ *
27
+ * It bundles each test file with Bun.build (using an internal plugin that
28
+ * redirects Automerge to the base64 WASM variant), serves it on an
29
+ * ephemeral port, launches Puppeteer, and collects results. A signalling
30
+ * server for WebRTC tests starts automatically.
31
+ */
32
+ export { cleanup, describe, done, expect, flush, test, waitFor } from "./harness";
@@ -0,0 +1,243 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
8
+ var __toCommonJS = (from) => {
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
10
+ if (entry)
11
+ return entry;
12
+ entry = __defProp({}, "__esModule", { value: true });
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ __moduleCache.set(from, entry);
22
+ return entry;
23
+ };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
29
+ var __export = (target, all) => {
30
+ for (var name in all)
31
+ __defProp(target, name, {
32
+ get: all[name],
33
+ enumerable: true,
34
+ configurable: true,
35
+ set: __exportSetter.bind(all, name)
36
+ });
37
+ };
38
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
39
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
40
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
41
+ }) : x)(function(x) {
42
+ if (typeof require !== "undefined")
43
+ return require.apply(this, arguments);
44
+ throw Error('Dynamic require of "' + x + '" is not supported');
45
+ });
46
+
47
+ // tools/test/src/browser/harness.ts
48
+ var results = [];
49
+ var suites = [];
50
+ function describe(name, fn) {
51
+ suites.push({ name, tests: [] });
52
+ fn();
53
+ }
54
+ function test(name, fn) {
55
+ const suite = suites[suites.length - 1];
56
+ if (suite) {
57
+ suite.tests.push({ name, fn });
58
+ }
59
+ }
60
+ function assertElement(value) {
61
+ if (!(value instanceof Element)) {
62
+ throw new Error(`Expected an Element, got ${typeof value}: ${String(value)}`);
63
+ }
64
+ return value;
65
+ }
66
+ function expect(actual) {
67
+ return {
68
+ toBe(expected) {
69
+ if (actual !== expected) {
70
+ throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
71
+ }
72
+ },
73
+ toEqual(expected) {
74
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
75
+ throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
76
+ }
77
+ },
78
+ toContain(sub) {
79
+ if (!String(actual).includes(sub)) {
80
+ throw new Error(`Expected "${String(actual)}" to contain "${sub}"`);
81
+ }
82
+ },
83
+ toBeTruthy() {
84
+ if (!actual)
85
+ throw new Error(`Expected truthy, got ${String(actual)}`);
86
+ },
87
+ toBeFalsy() {
88
+ if (actual)
89
+ throw new Error(`Expected falsy, got ${String(actual)}`);
90
+ },
91
+ toBeNull() {
92
+ if (actual !== null)
93
+ throw new Error(`Expected null, got ${String(actual)}`);
94
+ },
95
+ toBeDefined() {
96
+ if (actual === undefined || actual === null) {
97
+ throw new Error(`Expected value to be defined, got ${String(actual)}`);
98
+ }
99
+ },
100
+ toBeUndefined() {
101
+ if (actual !== undefined) {
102
+ throw new Error(`Expected undefined, got ${JSON.stringify(actual)}`);
103
+ }
104
+ },
105
+ toBeGreaterThan(expected) {
106
+ if (typeof actual !== "number" || actual <= expected) {
107
+ throw new Error(`Expected ${String(actual)} to be greater than ${expected}`);
108
+ }
109
+ },
110
+ toHaveLength(expected) {
111
+ const obj = actual;
112
+ const len = obj && typeof obj === "object" && "length" in obj ? Number(obj.length) : -1;
113
+ if (len !== expected)
114
+ throw new Error(`Expected length ${expected}, got ${len}`);
115
+ },
116
+ toExist() {
117
+ if (actual == null)
118
+ throw new Error(`Expected value to exist, got ${String(actual)}`);
119
+ },
120
+ toHaveTextContent(expected) {
121
+ const el = assertElement(actual);
122
+ if (!el.textContent?.includes(expected)) {
123
+ throw new Error(`Expected text content to include ${JSON.stringify(expected)}, got ${JSON.stringify(el.textContent)}`);
124
+ }
125
+ },
126
+ toBeChecked() {
127
+ const el = assertElement(actual);
128
+ if (!(el instanceof HTMLInputElement) || !el.checked) {
129
+ throw new Error("Expected element to be checked");
130
+ }
131
+ },
132
+ toBeDisabled() {
133
+ const el = assertElement(actual);
134
+ if (!el.hasAttribute("disabled") && el.getAttribute("aria-disabled") !== "true") {
135
+ throw new Error("Expected element to be disabled");
136
+ }
137
+ },
138
+ toHaveValue(expected) {
139
+ const el = assertElement(actual);
140
+ const inputEl = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el : null;
141
+ if (!inputEl || inputEl.value !== expected) {
142
+ throw new Error(`Expected value ${JSON.stringify(expected)}, got ${JSON.stringify(inputEl?.value ?? "(not an input)")}`);
143
+ }
144
+ },
145
+ toHaveAttribute(name, value) {
146
+ const el = assertElement(actual);
147
+ if (!el.hasAttribute(name)) {
148
+ throw new Error(`Expected element to have attribute "${name}"`);
149
+ }
150
+ if (value !== undefined && el.getAttribute(name) !== value) {
151
+ throw new Error(`Expected attribute "${name}" to be ${JSON.stringify(value)}, got ${JSON.stringify(el.getAttribute(name))}`);
152
+ }
153
+ },
154
+ not: {
155
+ toBe(expected) {
156
+ if (actual === expected) {
157
+ throw new Error(`Expected value NOT to be ${JSON.stringify(expected)}`);
158
+ }
159
+ },
160
+ toEqual(expected) {
161
+ if (JSON.stringify(actual) === JSON.stringify(expected)) {
162
+ throw new Error(`Expected value NOT to equal ${JSON.stringify(expected)}`);
163
+ }
164
+ },
165
+ toContain(sub) {
166
+ if (String(actual).includes(sub)) {
167
+ throw new Error(`Expected "${String(actual)}" NOT to contain "${sub}"`);
168
+ }
169
+ },
170
+ toBeNull() {
171
+ if (actual === null)
172
+ throw new Error("Expected value NOT to be null");
173
+ },
174
+ toExist() {
175
+ if (actual != null)
176
+ throw new Error(`Expected value NOT to exist, got ${String(actual)}`);
177
+ },
178
+ toBeChecked() {
179
+ const el = assertElement(actual);
180
+ if (el instanceof HTMLInputElement && el.checked) {
181
+ throw new Error("Expected element NOT to be checked");
182
+ }
183
+ },
184
+ toBeDisabled() {
185
+ const el = assertElement(actual);
186
+ if (el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true") {
187
+ throw new Error("Expected element NOT to be disabled");
188
+ }
189
+ },
190
+ toHaveAttribute(name) {
191
+ const el = assertElement(actual);
192
+ if (el.hasAttribute(name)) {
193
+ throw new Error(`Expected element NOT to have attribute "${name}"`);
194
+ }
195
+ }
196
+ }
197
+ };
198
+ }
199
+ function flush(ms = 50) {
200
+ return new Promise((r) => setTimeout(r, ms));
201
+ }
202
+ function cleanup(container) {
203
+ container.innerHTML = "";
204
+ }
205
+ async function done() {
206
+ for (const suite of suites) {
207
+ for (const t of suite.tests) {
208
+ const fullName = `${suite.name} > ${t.name}`;
209
+ try {
210
+ await t.fn();
211
+ results.push({ name: fullName, passed: true });
212
+ } catch (err) {
213
+ results.push({
214
+ name: fullName,
215
+ passed: false,
216
+ error: err instanceof Error ? err.message : String(err)
217
+ });
218
+ }
219
+ }
220
+ }
221
+ window["__testResults"] = results;
222
+ window["__done"] = true;
223
+ }
224
+ async function waitFor(predicate, timeoutMs = 5000, intervalMs = 25) {
225
+ const deadline = Date.now() + timeoutMs;
226
+ while (Date.now() < deadline) {
227
+ if (await predicate())
228
+ return;
229
+ await new Promise((r) => setTimeout(r, intervalMs));
230
+ }
231
+ throw new Error(`waitFor timed out after ${timeoutMs}ms`);
232
+ }
233
+ export {
234
+ waitFor,
235
+ test,
236
+ flush,
237
+ expect,
238
+ done,
239
+ describe,
240
+ cleanup
241
+ };
242
+
243
+ //# debugId=EC34BC0B21B9981964756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../tools/test/src/browser/harness.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Browser-side test harness for Polly applications.\n *\n * Provides describe/test/expect/done that run inside a Puppeteer-launched\n * browser tab and record results on window.__testResults for the Node-side\n * runner to collect. Matchers cover both value assertions and DOM element\n * assertions so that Preact component tests and WebRTC adapter tests use\n * the same harness.\n *\n * @example\n * ```typescript\n * import { describe, test, expect, done, flush, cleanup } from \"@fairfox/polly/test/browser\";\n *\n * const app = document.getElementById(\"app\")!;\n *\n * describe(\"my feature\", () => {\n * test(\"renders correctly\", async () => {\n * render(<MyComponent />, app);\n * await flush();\n * expect(app.querySelector(\"h1\")).toHaveTextContent(\"Hello\");\n * cleanup(app);\n * });\n * });\n *\n * done();\n * ```\n */\n\ninterface TestResult {\n name: string;\n passed: boolean;\n error?: string;\n}\n\nconst results: TestResult[] = [];\nconst suites: Array<{\n name: string;\n tests: Array<{ name: string; fn: () => Promise<void> | void }>;\n}> = [];\n\nexport function describe(name: string, fn: () => void): void {\n suites.push({ name, tests: [] });\n fn();\n}\n\nexport function test(name: string, fn: () => Promise<void> | void): void {\n const suite = suites[suites.length - 1];\n if (suite) {\n suite.tests.push({ name, fn });\n }\n}\n\nfunction assertElement(value: unknown): Element {\n if (!(value instanceof Element)) {\n throw new Error(`Expected an Element, got ${typeof value}: ${String(value)}`);\n }\n return value;\n}\n\nexport function expect<T>(actual: T) {\n return {\n // ─── Value matchers ───────────────────────────────────────────────\n toBe(expected: T) {\n if (actual !== expected) {\n throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);\n }\n },\n toEqual(expected: T) {\n if (JSON.stringify(actual) !== JSON.stringify(expected)) {\n throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);\n }\n },\n toContain(sub: string) {\n if (!String(actual).includes(sub)) {\n throw new Error(`Expected \"${String(actual)}\" to contain \"${sub}\"`);\n }\n },\n toBeTruthy() {\n if (!actual) throw new Error(`Expected truthy, got ${String(actual)}`);\n },\n toBeFalsy() {\n if (actual) throw new Error(`Expected falsy, got ${String(actual)}`);\n },\n toBeNull() {\n if (actual !== null) throw new Error(`Expected null, got ${String(actual)}`);\n },\n toBeDefined() {\n if (actual === undefined || actual === null) {\n throw new Error(`Expected value to be defined, got ${String(actual)}`);\n }\n },\n toBeUndefined() {\n if (actual !== undefined) {\n throw new Error(`Expected undefined, got ${JSON.stringify(actual)}`);\n }\n },\n toBeGreaterThan(expected: number) {\n if (typeof actual !== \"number\" || actual <= expected) {\n throw new Error(`Expected ${String(actual)} to be greater than ${expected}`);\n }\n },\n toHaveLength(expected: number) {\n const obj = actual;\n const len = obj && typeof obj === \"object\" && \"length\" in obj ? Number(obj.length) : -1;\n if (len !== expected) throw new Error(`Expected length ${expected}, got ${len}`);\n },\n toExist() {\n if (actual == null) throw new Error(`Expected value to exist, got ${String(actual)}`);\n },\n\n // ─── DOM element matchers ─────────────────────────────────────────\n toHaveTextContent(expected: string) {\n const el = assertElement(actual);\n if (!el.textContent?.includes(expected)) {\n throw new Error(\n `Expected text content to include ${JSON.stringify(expected)}, got ${JSON.stringify(el.textContent)}`\n );\n }\n },\n toBeChecked() {\n const el = assertElement(actual);\n if (!(el instanceof HTMLInputElement) || !el.checked) {\n throw new Error(\"Expected element to be checked\");\n }\n },\n toBeDisabled() {\n const el = assertElement(actual);\n if (!el.hasAttribute(\"disabled\") && el.getAttribute(\"aria-disabled\") !== \"true\") {\n throw new Error(\"Expected element to be disabled\");\n }\n },\n toHaveValue(expected: string) {\n const el = assertElement(actual);\n const inputEl =\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el : null;\n if (!inputEl || inputEl.value !== expected) {\n throw new Error(\n `Expected value ${JSON.stringify(expected)}, got ${JSON.stringify(inputEl?.value ?? \"(not an input)\")}`\n );\n }\n },\n toHaveAttribute(name: string, value?: string) {\n const el = assertElement(actual);\n if (!el.hasAttribute(name)) {\n throw new Error(`Expected element to have attribute \"${name}\"`);\n }\n if (value !== undefined && el.getAttribute(name) !== value) {\n throw new Error(\n `Expected attribute \"${name}\" to be ${JSON.stringify(value)}, got ${JSON.stringify(el.getAttribute(name))}`\n );\n }\n },\n\n // ─── .not variants ────────────────────────────────────────────────\n not: {\n toBe(expected: T) {\n if (actual === expected) {\n throw new Error(`Expected value NOT to be ${JSON.stringify(expected)}`);\n }\n },\n toEqual(expected: T) {\n if (JSON.stringify(actual) === JSON.stringify(expected)) {\n throw new Error(`Expected value NOT to equal ${JSON.stringify(expected)}`);\n }\n },\n toContain(sub: string) {\n if (String(actual).includes(sub)) {\n throw new Error(`Expected \"${String(actual)}\" NOT to contain \"${sub}\"`);\n }\n },\n toBeNull() {\n if (actual === null) throw new Error(\"Expected value NOT to be null\");\n },\n toExist() {\n if (actual != null) throw new Error(`Expected value NOT to exist, got ${String(actual)}`);\n },\n toBeChecked() {\n const el = assertElement(actual);\n if (el instanceof HTMLInputElement && el.checked) {\n throw new Error(\"Expected element NOT to be checked\");\n }\n },\n toBeDisabled() {\n const el = assertElement(actual);\n if (el.hasAttribute(\"disabled\") || el.getAttribute(\"aria-disabled\") === \"true\") {\n throw new Error(\"Expected element NOT to be disabled\");\n }\n },\n toHaveAttribute(name: string) {\n const el = assertElement(actual);\n if (el.hasAttribute(name)) {\n throw new Error(`Expected element NOT to have attribute \"${name}\"`);\n }\n },\n },\n };\n}\n\n/**\n * Flush microtasks and pending DOM updates. Call after signal assignments\n * or render calls to give the reactive system and the browser a chance to\n * settle before asserting on the result.\n */\nexport function flush(ms = 50): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * Clear a container's rendered content. Call at the end of each test to\n * prevent state leaking between tests. If you use Preact's render(), pass\n * the same container; the function calls render(null, container) if Preact\n * is available, otherwise sets innerHTML to \"\".\n */\nexport function cleanup(container: Element): void {\n container.innerHTML = \"\";\n}\n\n/**\n * Run all registered tests and write results to window.__testResults.\n * Call this at the end of every .browser.ts test file.\n */\nexport async function done(): Promise<void> {\n for (const suite of suites) {\n for (const t of suite.tests) {\n const fullName = `${suite.name} > ${t.name}`;\n try {\n await t.fn();\n results.push({ name: fullName, passed: true });\n } catch (err) {\n results.push({\n name: fullName,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n (window as unknown as Record<string, unknown>)[\"__testResults\"] = results;\n (window as unknown as Record<string, unknown>)[\"__done\"] = true;\n}\n\n/**\n * Wait until a predicate returns true, polling every intervalMs. Rejects\n * after timeoutMs.\n */\nexport async function waitFor(\n predicate: () => boolean | Promise<boolean>,\n timeoutMs = 5000,\n intervalMs = 25\n): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await predicate()) return;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n throw new Error(`waitFor timed out after ${timeoutMs}ms`);\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "EC34BC0B21B9981964756E2164756E21",
9
+ "names": []
10
+ }