@fairfox/polly 0.75.1 → 0.75.2
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/src/polly-ui/index.css +8 -14
- package/dist/src/polly-ui/styles.css +8 -14
- package/dist/src/polly-ui/theme.css +5 -6
- package/dist/tools/test/src/browser/harness.d.ts +9 -1
- package/dist/tools/test/src/browser/index.d.ts +4 -3
- package/dist/tools/test/src/browser/index.js +6 -3
- package/dist/tools/test/src/browser/index.js.map +3 -3
- package/dist/tools/test/src/browser/run.js +17 -38
- package/dist/tools/test/src/browser/run.js.map +4 -4
- package/dist/tools/test/src/browser/runner-core.d.ts +9 -13
- package/package.json +1 -1
|
@@ -575,9 +575,6 @@
|
|
|
575
575
|
}
|
|
576
576
|
|
|
577
577
|
.summary_sEhnPw {
|
|
578
|
-
display: flex;
|
|
579
|
-
align-items: center;
|
|
580
|
-
gap: var(--polly-space-sm);
|
|
581
578
|
padding: var(--polly-space-sm) var(--polly-space-md);
|
|
582
579
|
cursor: pointer;
|
|
583
580
|
font-family: inherit;
|
|
@@ -593,19 +590,16 @@
|
|
|
593
590
|
display: none;
|
|
594
591
|
}
|
|
595
592
|
|
|
596
|
-
.summary_sEhnPw:
|
|
597
|
-
content:
|
|
598
|
-
display: block;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
transition: transform var(--polly-motion-fast)
|
|
602
|
-
flex: none;
|
|
603
|
-
margin-inline-start: auto;
|
|
604
|
-
line-height: 1;
|
|
593
|
+
.summary_sEhnPw:before {
|
|
594
|
+
content: "▶";
|
|
595
|
+
display: inline-block;
|
|
596
|
+
margin-right: var(--polly-space-sm);
|
|
597
|
+
font-size: var(--polly-text-xs);
|
|
598
|
+
transition: transform var(--polly-motion-fast);
|
|
605
599
|
}
|
|
606
600
|
|
|
607
|
-
.collapsible_sEhnPw[open] > .summary_sEhnPw:
|
|
608
|
-
transform: rotate(
|
|
601
|
+
.collapsible_sEhnPw[open] > .summary_sEhnPw:before {
|
|
602
|
+
transform: rotate(90deg);
|
|
609
603
|
}
|
|
610
604
|
|
|
611
605
|
.summary_sEhnPw:hover {
|
|
@@ -764,9 +764,6 @@
|
|
|
764
764
|
}
|
|
765
765
|
|
|
766
766
|
.summary_sEhnPw {
|
|
767
|
-
display: flex;
|
|
768
|
-
align-items: center;
|
|
769
|
-
gap: var(--polly-space-sm);
|
|
770
767
|
padding: var(--polly-space-sm) var(--polly-space-md);
|
|
771
768
|
cursor: pointer;
|
|
772
769
|
font-family: inherit;
|
|
@@ -782,19 +779,16 @@
|
|
|
782
779
|
display: none;
|
|
783
780
|
}
|
|
784
781
|
|
|
785
|
-
.summary_sEhnPw:
|
|
786
|
-
content:
|
|
787
|
-
display: block;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
transition: transform var(--polly-motion-fast)
|
|
791
|
-
flex: none;
|
|
792
|
-
margin-inline-start: auto;
|
|
793
|
-
line-height: 1;
|
|
782
|
+
.summary_sEhnPw:before {
|
|
783
|
+
content: "▶";
|
|
784
|
+
display: inline-block;
|
|
785
|
+
margin-right: var(--polly-space-sm);
|
|
786
|
+
font-size: var(--polly-text-xs);
|
|
787
|
+
transition: transform var(--polly-motion-fast);
|
|
794
788
|
}
|
|
795
789
|
|
|
796
|
-
.collapsible_sEhnPw[open] > .summary_sEhnPw:
|
|
797
|
-
transform: rotate(
|
|
790
|
+
.collapsible_sEhnPw[open] > .summary_sEhnPw:before {
|
|
791
|
+
transform: rotate(90deg);
|
|
798
792
|
}
|
|
799
793
|
|
|
800
794
|
.summary_sEhnPw:hover {
|
|
@@ -80,13 +80,12 @@
|
|
|
80
80
|
--polly-control-height-md: 2.25rem;
|
|
81
81
|
--polly-control-height-lg: 2.75rem;
|
|
82
82
|
|
|
83
|
-
/* Disclosure caret
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* turn instead of a half turn (polly#137). */
|
|
83
|
+
/* Disclosure caret for dropdown-style triggers (Select, ActionSelect).
|
|
84
|
+
* Collapsible runs its own left-side marker — that's a different
|
|
85
|
+
* affordance from a dropdown's right-side ▾ and shouldn't track
|
|
86
|
+
* these tokens (polly#137). */
|
|
88
87
|
--polly-caret-glyph: "\25BE";
|
|
89
|
-
--polly-caret-size: var(--polly-text-
|
|
88
|
+
--polly-caret-size: var(--polly-text-xl);
|
|
90
89
|
--polly-caret-color: var(--polly-text-muted);
|
|
91
90
|
|
|
92
91
|
/* Motion */
|
|
@@ -69,7 +69,15 @@ export declare function flush(ms?: number): Promise<void>;
|
|
|
69
69
|
*/
|
|
70
70
|
export declare function cleanup(container: Element): void;
|
|
71
71
|
/**
|
|
72
|
-
* Run all registered tests and
|
|
72
|
+
* Run all registered tests and report the tally to the Node-side runner.
|
|
73
|
+
*
|
|
74
|
+
* The runner injects `window.__pollyReport` (via Puppeteer's
|
|
75
|
+
* `page.exposeFunction`) before navigating, so reporting is a single
|
|
76
|
+
* push from the page to the runner — no polling, no waiting on a long-
|
|
77
|
+
* running CDP call from the Node side. Polled `page.evaluate` was the
|
|
78
|
+
* root cause of the intermittent `Runtime.callFunctionOn` stalls
|
|
79
|
+
* tracked in polly#138.
|
|
80
|
+
*
|
|
73
81
|
* Call this at the end of every .browser.ts test file.
|
|
74
82
|
*/
|
|
75
83
|
export declare function done(): Promise<void>;
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* applications.
|
|
4
4
|
*
|
|
5
5
|
* Provides a lightweight describe/test/expect harness that runs inside a
|
|
6
|
-
* Puppeteer-launched browser tab. Results are
|
|
7
|
-
*
|
|
8
|
-
* module in their *.browser.ts
|
|
6
|
+
* Puppeteer-launched browser tab. Results are pushed back to the companion
|
|
7
|
+
* runner (run.ts) via a function the runner exposes on `window.__pollyReport`
|
|
8
|
+
* before navigating. Applications import this module in their *.browser.ts
|
|
9
|
+
* test files and call done() at the end.
|
|
9
10
|
*
|
|
10
11
|
* @example
|
|
11
12
|
* ```typescript
|
|
@@ -243,8 +243,11 @@ async function done() {
|
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
|
-
window["
|
|
247
|
-
|
|
246
|
+
const report = window["__pollyReport"];
|
|
247
|
+
if (typeof report !== "function") {
|
|
248
|
+
throw new Error("harness.done(): window.__pollyReport is not defined. " + "This harness must be driven by the Polly browser test runner.");
|
|
249
|
+
}
|
|
250
|
+
report(results);
|
|
248
251
|
}
|
|
249
252
|
async function waitFor(predicate, timeoutMs = 5000, intervalMs = 25) {
|
|
250
253
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -265,4 +268,4 @@ export {
|
|
|
265
268
|
cleanup
|
|
266
269
|
};
|
|
267
270
|
|
|
268
|
-
//# debugId=
|
|
271
|
+
//# debugId=F2832DFB58875E3964756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../tools/test/src/browser/harness.ts"],
|
|
4
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
|
|
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 report the tally to the Node-side runner.\n *\n * The runner injects `window.__pollyReport` (via Puppeteer's\n * `page.exposeFunction`) before navigating, so reporting is a single\n * push from the page to the runner — no polling, no waiting on a long-\n * running CDP call from the Node side. Polled `page.evaluate` was the\n * root cause of the intermittent `Runtime.callFunctionOn` stalls\n * tracked in polly#138.\n *\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 const report = (window as unknown as Record<string, unknown>)[\"__pollyReport\"];\n if (typeof report !== \"function\") {\n throw new Error(\n \"harness.done(): window.__pollyReport is not defined. \" +\n \"This harness must be driven by the Polly browser test runner.\"\n );\n }\n (report as (r: TestResult[]) => void)(results);\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
6
|
],
|
|
7
|
-
"mappings": "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
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "F2832DFB58875E3964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -147,9 +147,6 @@ function signalingServer(options = {}) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// tools/test/src/browser/runner-core.ts
|
|
150
|
-
function isProtocolError(err) {
|
|
151
|
-
return err instanceof Error && err.name === "ProtocolError";
|
|
152
|
-
}
|
|
153
150
|
function errMessage(err) {
|
|
154
151
|
return err instanceof Error ? err.message : String(err);
|
|
155
152
|
}
|
|
@@ -165,18 +162,8 @@ async function runSuite(testFiles, runFile, options = {}) {
|
|
|
165
162
|
try {
|
|
166
163
|
result = await runFile(testFile);
|
|
167
164
|
} catch (err) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
result = await runFile(testFile);
|
|
172
|
-
} catch (retryErr) {
|
|
173
|
-
log(` ❌ retry failed: ${errMessage(retryErr)}`);
|
|
174
|
-
result = { passed: 0, failed: 1 };
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
log(` ❌ ${errMessage(err)}`);
|
|
178
|
-
result = { passed: 0, failed: 1 };
|
|
179
|
-
}
|
|
165
|
+
log(` ❌ ${errMessage(err)}`);
|
|
166
|
+
result = { passed: 0, failed: 1 };
|
|
180
167
|
}
|
|
181
168
|
totalPassed += result.passed;
|
|
182
169
|
totalFailed += result.failed;
|
|
@@ -216,8 +203,7 @@ var signalingApp = new Elysia2().use(signalingServer({ path: "/polly/signaling"
|
|
|
216
203
|
console.log(`[browser-runner] signaling server on ws://127.0.0.1:${signalingPort}/polly/signaling`);
|
|
217
204
|
var browser = await puppeteer.launch({
|
|
218
205
|
headless,
|
|
219
|
-
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
220
|
-
protocolTimeout: 30000
|
|
206
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
221
207
|
});
|
|
222
208
|
async function runFile(testFile) {
|
|
223
209
|
const buildResult = await Bun.build({
|
|
@@ -256,34 +242,27 @@ async function runFile(testFile) {
|
|
|
256
242
|
});
|
|
257
243
|
let page;
|
|
258
244
|
try {
|
|
259
|
-
|
|
260
|
-
page
|
|
245
|
+
const newPage = await browser.newPage();
|
|
246
|
+
page = newPage;
|
|
247
|
+
newPage.on("console", (msg) => {
|
|
261
248
|
const text = msg.text();
|
|
262
249
|
if (text.includes("[test]")) {
|
|
263
250
|
console.log(` ${text}`);
|
|
264
251
|
}
|
|
265
252
|
});
|
|
266
|
-
|
|
267
|
-
|
|
253
|
+
const outcome = new Promise((resolve2, reject) => {
|
|
254
|
+
newPage.exposeFunction("__pollyReport", (results2) => {
|
|
255
|
+
resolve2(results2);
|
|
256
|
+
}).catch(reject);
|
|
257
|
+
newPage.on("pageerror", (err) => {
|
|
258
|
+
reject(err instanceof Error ? err : new Error(errMessage(err)));
|
|
259
|
+
});
|
|
268
260
|
});
|
|
269
|
-
await
|
|
270
|
-
const
|
|
271
|
-
const deadline = Date.now() + timeout;
|
|
272
|
-
let finished = false;
|
|
273
|
-
while (Date.now() < deadline) {
|
|
274
|
-
finished = await page.evaluate(() => window["__done"] === true);
|
|
275
|
-
if (finished)
|
|
276
|
-
break;
|
|
277
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
278
|
-
}
|
|
279
|
-
if (!finished) {
|
|
280
|
-
console.log(` ❌ timed out after ${timeout}ms`);
|
|
281
|
-
return { passed: 0, failed: 1 };
|
|
282
|
-
}
|
|
283
|
-
const results = await page.evaluate(() => window["__testResults"]);
|
|
261
|
+
await newPage.goto(`http://127.0.0.1:${server.port}/`, { waitUntil: "domcontentloaded" });
|
|
262
|
+
const results = await outcome;
|
|
284
263
|
let passed = 0;
|
|
285
264
|
let failed = 0;
|
|
286
|
-
for (const r of results
|
|
265
|
+
for (const r of results) {
|
|
287
266
|
if (r.passed) {
|
|
288
267
|
console.log(` ✅ ${r.name}`);
|
|
289
268
|
passed += 1;
|
|
@@ -309,4 +288,4 @@ console.log(`
|
|
|
309
288
|
[browser-runner] ${totalPassed} passed, ${totalFailed} failed`);
|
|
310
289
|
process.exit(totalFailed > 0 ? 1 : 0);
|
|
311
290
|
|
|
312
|
-
//# debugId=
|
|
291
|
+
//# debugId=5455F136F17AAB2C64756E2164756E21
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../tools/test/src/browser/run.ts", "../src/elysia/signaling-server-plugin.ts", "../tools/test/src/browser/runner-core.ts"],
|
|
4
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, { type Page } from \"puppeteer\";\nimport { signalingServer } from \"../../../../src/elysia/signaling-server-plugin\";\nimport { errMessage, type FileTally, runSuite } from \"./runner-core\";\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//\n// protocolTimeout
|
|
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, { type Page } from \"puppeteer\";\nimport { signalingServer } from \"../../../../src/elysia/signaling-server-plugin\";\nimport { errMessage, type FileTally, runSuite } from \"./runner-core\";\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//\n// No `protocolTimeout` override: results arrive via `page.exposeFunction`\n// (a push from the page over CDP `Runtime.bindingCalled` events), not via\n// polled `page.evaluate` calls. Without a long-running `Runtime.callFunctionOn`\n// on the hot path there is no protocol round-trip for a busy renderer to\n// stall, so the timeout the previous polling design had to guard against\n// is no longer reachable (polly#138).\n\nconst browser = await puppeteer.launch({\n headless,\n args: [\"--no-sandbox\", \"--disable-setuid-sandbox\"],\n});\n\n/**\n * Build, serve, and run one test file on a fresh page. Returns its\n * pass/fail tally. Build failures are reported here (not thrown);\n * a page-level uncaught error propagates so the suite records the\n * file as failed. The page and server are always cleaned up first.\n */\ninterface TestResult {\n name: string;\n passed: boolean;\n error?: string;\n}\nasync function runFile(testFile: string): Promise<FileTally> {\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 return { passed: 0, failed: 1 };\n }\n\n const jsText = await buildResult.outputs[0]?.text();\n if (!jsText) {\n console.log(\" ❌ build produced no output\");\n return { passed: 0, failed: 1 };\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 let page: Page | undefined;\n try {\n const newPage = await browser.newPage();\n page = newPage;\n newPage.on(\"console\", (msg) => {\n const text = msg.text();\n if (text.includes(\"[test]\")) {\n console.log(` ${text}`);\n }\n });\n\n // Push-based reporting (polly#138): the page calls back into Node via\n // `__pollyReport(results)` when its in-page suite has finished. We\n // bind that function and the page-error channel to a single Promise\n // before navigating, then `await` it — no `page.evaluate` polling\n // and no CDP timeout for a busy renderer to trip.\n const outcome = new Promise<TestResult[]>((resolve, reject) => {\n newPage\n .exposeFunction(\"__pollyReport\", (results: TestResult[]) => {\n resolve(results);\n })\n .catch(reject);\n newPage.on(\"pageerror\", (err: unknown) => {\n reject(err instanceof Error ? err : new Error(errMessage(err)));\n });\n });\n\n await newPage.goto(`http://127.0.0.1:${server.port}/`, { waitUntil: \"domcontentloaded\" });\n\n const results = await outcome;\n\n let passed = 0;\n let failed = 0;\n for (const r of results) {\n if (r.passed) {\n console.log(` ✅ ${r.name}`);\n passed += 1;\n } else {\n console.log(` ❌ ${r.name}: ${r.error}`);\n failed += 1;\n }\n }\n return { passed, failed };\n } finally {\n if (page) {\n await page.close().catch(() => {\n // ignore — page may already be gone after an uncaught error\n });\n }\n server.stop();\n }\n}\n\nconst { passed: totalPassed, failed: totalFailed } = await runSuite(testFiles, runFile, {\n label: (testFile) => testFile.replace(`${testDir}/`, \"\"),\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
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 client-to-server\n * requests (join, signal), the error envelope, and the server-to-client\n * discovery frames (peers-present, peer-joined, peer-left) that let\n * peers learn about each other without polling. */\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 | {\n /** Sent to a newcomer immediately after it joins, listing every\n * peer that was already joined at that moment. Empty for a lone\n * newcomer. */\n type: \"peers-present\";\n peerIds: string[];\n }\n | {\n /** Broadcast to every incumbent when a new peer joins. */\n type: \"peer-joined\";\n peerId: string;\n }\n | {\n /** Broadcast to every remaining incumbent when a joined peer\n * closes its socket. Never emitted for a connection that never\n * sent a join frame. */\n type: \"peer-left\";\n peerId: string;\n };\n\n/** A frame whose `type` is outside the built-in signalling vocabulary.\n * Consumers who pass an {@link SignalingServerOptions.onCustomFrame}\n * handler receive these on the server side; everything else — including\n * routing them to a specific peer, storing a session, or rejecting the\n * frame — is the consumer's call. Polly does not touch the body. */\nexport interface CustomSignalingFrame {\n type: string;\n [key: string]: unknown;\n}\n\n/** Minimal surface the custom-frame handler receives in place of the\n * Elysia-specific `ws` object so the plugin stays portable. Exposes the\n * `data` bag (used to stash the authenticated peerId under the existing\n * join handshake) and a `send` method. */\nexport interface CustomFrameSocket {\n data: Record<string, unknown>;\n send: (msg: unknown) => void;\n}\n\nexport interface SignalingServerOptions {\n /** WebSocket route path. Defaults to \"/polly/signaling\". */\n path?: string;\n /** Optional hook for frames whose `type` is outside the built-in\n * vocabulary. The plugin invokes it in place of returning a\n * `malformed` error, so consumers can layer their own application\n * protocol (pairing return tokens, presence pings, etc.) on the\n * existing socket. The `peerId` argument is the sender's\n * authenticated peer id from their prior `join` frame, or\n * `undefined` if they haven't joined yet. */\n onCustomFrame?: (\n socket: CustomFrameSocket,\n frame: CustomSignalingFrame,\n peerId: string | undefined\n ) => void;\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 const onCustomFrame = options.onCustomFrame;\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 const newcomer = ws as unknown as { send: (m: unknown) => void };\n // Collect the peers that were already joined so we can (a) tell the\n // newcomer who is present and (b) tell each of them about the\n // newcomer. A rejoin with the same peerId replaces the prior entry\n // but is otherwise treated as a fresh arrival.\n const incumbents: Array<{ peerId: string; socket: { send: (m: unknown) => void } }> = [];\n for (const [existingPeerId, existingSocket] of peerSockets) {\n if (existingPeerId === peerId) continue;\n incumbents.push({ peerId: existingPeerId, socket: existingSocket });\n }\n peerSockets.set(peerId, newcomer);\n const wsWithData = ws as unknown as { data: Record<string, unknown> };\n wsWithData.data.peerId = peerId;\n\n newcomer.send({\n type: \"peers-present\",\n peerIds: incumbents.map((i) => i.peerId),\n } as unknown as SignalingMessage);\n\n for (const incumbent of incumbents) {\n try {\n incumbent.socket.send({ type: \"peer-joined\", peerId } as unknown as SignalingMessage);\n } catch {\n // If a send fails we leave the stale socket to its own close\n // handler to evict. Dropping here would open a window where\n // the next signal to this peer still thinks it's alive.\n }\n }\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 // Unknown types route to the consumer's custom-frame hook when\n // one is configured. Without a hook they still fall through to\n // the `malformed` error — same behaviour as before this branch\n // existed.\n if (onCustomFrame !== undefined) {\n const wsWithData = ws as unknown as CustomFrameSocket;\n const senderId = wsWithData.data[\"peerId\"];\n const peerId = typeof senderId === \"string\" ? senderId : undefined;\n onCustomFrame(wsWithData, msg as unknown as CustomSignalingFrame, peerId);\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 // Connection that never sent a join — nothing to broadcast and\n // nothing to evict. A bystander coming and going leaves no trace.\n return;\n }\n // Only evict if the map still points at *this* socket. A stale\n // close after the same peerId rejoined on a new socket should not\n // take the fresh entry with it. The comparison uses the `data` bag\n // Elysia attaches to each connection because it is preserved across\n // message and close callbacks, unlike the `ws` wrapper object which\n // Elysia may or may not reuse.\n const mapped = peerSockets.get(peerId);\n const wsData = (ws as unknown as { data: Record<string, unknown> }).data;\n const mappedData = (mapped as unknown as { data?: Record<string, unknown> } | undefined)\n ?.data;\n if (mapped === undefined || mappedData !== wsData) {\n return;\n }\n peerSockets.delete(peerId);\n for (const [_incumbentId, incumbentSocket] of peerSockets) {\n try {\n incumbentSocket.send({ type: \"peer-left\", peerId } as unknown as SignalingMessage);\n } catch {\n // Incumbent socket is gone; its own close handler will tidy.\n }\n }\n },\n });\n}\n",
|
|
7
|
-
"/**\n * Suite orchestration for the browser test runner.\n *\n * Extracted from run.ts so the resilience guarantee — a
|
|
7
|
+
"/**\n * Suite orchestration for the browser test runner.\n *\n * Extracted from run.ts so the resilience guarantee — a failure in one\n * file never aborts the whole suite — is a pure function that can be\n * unit-tested without launching a browser. run.ts supplies the real\n * per-file `runFile`; the test suite supplies a fake one.\n */\n\n/** Pass/fail tally for one test file or for a whole suite. */\nexport interface FileTally {\n passed: number;\n failed: number;\n}\n\nexport function errMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Run every test file, isolating each file's failure.\n *\n * - Any thrown error from `runFile` fails only that file and is logged.\n * - A file that returns a tally with `failed > 0` (a genuine red test)\n * is reported as-is.\n *\n * The loop never rejects: a failure in one file can never abort the\n * suite, so the remaining files always execute and report.\n */\nexport async function runSuite(\n testFiles: string[],\n runFile: (testFile: string) => Promise<FileTally>,\n options: {\n label?: (testFile: string) => string;\n log?: (msg: string) => void;\n } = {}\n): Promise<FileTally> {\n const label = options.label ?? ((f) => f);\n const log = options.log ?? console.log;\n\n let totalPassed = 0;\n let totalFailed = 0;\n\n for (const testFile of testFiles) {\n log(`\\n[browser-runner] running ${label(testFile)}`);\n\n let result: FileTally;\n try {\n result = await runFile(testFile);\n } catch (err) {\n log(` ❌ ${errMessage(err)}`);\n result = { passed: 0, failed: 1 };\n }\n\n totalPassed += result.passed;\n totalFailed += result.failed;\n }\n\n return { passed: totalPassed, failed: totalFailed };\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "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
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "5455F136F17AAB2C64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Suite orchestration for the browser test runner.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from run.ts so the resilience guarantee — a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Extracted from run.ts so the resilience guarantee — a failure in one
|
|
5
|
+
* file never aborts the whole suite — is a pure function that can be
|
|
6
|
+
* unit-tested without launching a browser. run.ts supplies the real
|
|
7
|
+
* per-file `runFile`; the test suite supplies a fake one.
|
|
8
8
|
*/
|
|
9
9
|
/** Pass/fail tally for one test file or for a whole suite. */
|
|
10
10
|
export interface FileTally {
|
|
11
11
|
passed: number;
|
|
12
12
|
failed: number;
|
|
13
13
|
}
|
|
14
|
-
/** A transient CDP timeout (renderer stall) — retryable, unlike a red test. */
|
|
15
|
-
export declare function isProtocolError(err: unknown): boolean;
|
|
16
14
|
export declare function errMessage(err: unknown): string;
|
|
17
15
|
/**
|
|
18
16
|
* Run every test file, isolating each file's failure.
|
|
19
17
|
*
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
* - A file that returns a tally with `failed > 0` (a genuine red test) is
|
|
24
|
-
* reported as-is and never retried.
|
|
18
|
+
* - Any thrown error from `runFile` fails only that file and is logged.
|
|
19
|
+
* - A file that returns a tally with `failed > 0` (a genuine red test)
|
|
20
|
+
* is reported as-is.
|
|
25
21
|
*
|
|
26
|
-
* The loop never rejects: a
|
|
27
|
-
* so remaining files always execute and report.
|
|
22
|
+
* The loop never rejects: a failure in one file can never abort the
|
|
23
|
+
* suite, so the remaining files always execute and report.
|
|
28
24
|
*/
|
|
29
25
|
export declare function runSuite(testFiles: string[], runFile: (testFile: string) => Promise<FileTally>, options?: {
|
|
30
26
|
label?: (testFile: string) => string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fairfox/polly",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Multi-execution-context framework with reactive state and cross-context messaging for Chrome extensions, PWAs, and worker-based applications",
|