@cosmicdrift/kumiko-testing 0.336.0 → 0.336.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/changes.json +12 -0
- package/src/e2e/screenshots.ts +19 -12
- package/src/preload/dom.ts +16 -12
- package/src/scaffold.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-testing",
|
|
3
|
-
"version": "0.336.
|
|
3
|
+
"version": "0.336.1",
|
|
4
4
|
"description": "Test template for Kumiko apps: per-flow seedTenant, app test stack, canonical test preloads, bunfig generator and integration runner.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"kumiko-testing": "./bin/kumiko-testing.ts"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@cosmicdrift/kumiko-bundled-features": "0.336.
|
|
79
|
-
"@cosmicdrift/kumiko-dev-server": "0.336.
|
|
80
|
-
"@cosmicdrift/kumiko-framework": "0.336.
|
|
78
|
+
"@cosmicdrift/kumiko-bundled-features": "0.336.1",
|
|
79
|
+
"@cosmicdrift/kumiko-dev-server": "0.336.1",
|
|
80
|
+
"@cosmicdrift/kumiko-framework": "0.336.1",
|
|
81
81
|
"zod": "^4.4.3"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.336.1",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "DOM preload bounds the printed output of SVG elements as well",
|
|
6
|
+
"detail": "The DOM preload's bounded print of nodes now covers SVG elements too. Before, printing an `<svg>` in a failed assertion dumped the whole happy-dom object graph (about 10 MB)."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.336.1",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Scaffolded test:real only matches *.real.test.ts files",
|
|
12
|
+
"detail": "The scaffolded `test:real` script now filters on `.real.test.ts` instead of `real.test.ts`, so files such as `unreal.test.ts` no longer run under the real-provider environment."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.335.0",
|
|
4
16
|
"type": "fix",
|
package/src/e2e/screenshots.ts
CHANGED
|
@@ -216,6 +216,23 @@ async function restoreIdentitiesOnPage(
|
|
|
216
216
|
if (mappings.length > 0) await page.evaluate(restoreIdentitiesInDocument);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// Order matters: beforeCapture first (it may hide/mask), identities after, so
|
|
220
|
+
// the replaced text is what the screenshot sees. Exported for unit tests.
|
|
221
|
+
export async function captureScenario(
|
|
222
|
+
page: Page,
|
|
223
|
+
scenario: Pick<Scenario, "beforeCapture" | "fullPage" | "captureStyle">,
|
|
224
|
+
mappings: readonly PresentIdentity[],
|
|
225
|
+
screenshotOptions: Omit<NonNullable<Parameters<Page["screenshot"]>[0]>, "fullPage" | "style">,
|
|
226
|
+
): Promise<Buffer> {
|
|
227
|
+
if (scenario.beforeCapture) await scenario.beforeCapture(page);
|
|
228
|
+
await presentIdentitiesOnPage(page, mappings);
|
|
229
|
+
return page.screenshot({
|
|
230
|
+
...screenshotOptions,
|
|
231
|
+
fullPage: scenario.fullPage ?? false,
|
|
232
|
+
...(scenario.captureStyle !== undefined && { style: scenario.captureStyle }),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
219
236
|
function collectPresentIdentities(): {
|
|
220
237
|
readonly mappings: readonly PresentIdentity[];
|
|
221
238
|
readonly register: (mappings: readonly PresentIdentity[]) => void;
|
|
@@ -303,14 +320,8 @@ export function runScreenshots(scenarios: readonly Scenario[], opts: FlatOptions
|
|
|
303
320
|
seedTenant,
|
|
304
321
|
presentIdentities: identities.register,
|
|
305
322
|
});
|
|
306
|
-
if (s.beforeCapture) await s.beforeCapture(page);
|
|
307
|
-
await presentIdentitiesOnPage(page, identities.mappings);
|
|
308
323
|
const path = `${outDir}/${s.name}.png`;
|
|
309
|
-
await page.
|
|
310
|
-
path,
|
|
311
|
-
fullPage: s.fullPage ?? false,
|
|
312
|
-
...(s.captureStyle !== undefined && { style: s.captureStyle }),
|
|
313
|
-
});
|
|
324
|
+
await captureScenario(page, s, identities.mappings, { path });
|
|
314
325
|
expect.soft(statSync(path).size).toBeGreaterThan(MIN_BYTES);
|
|
315
326
|
},
|
|
316
327
|
);
|
|
@@ -570,19 +581,15 @@ export function runMatrix<T extends string>(
|
|
|
570
581
|
for (const vp of plan.viewports) {
|
|
571
582
|
if (plan.mode === "desktop") await page.setViewportSize(viewportSizes[vp]);
|
|
572
583
|
await waitForSettledPage(page, inFlightDataRequests);
|
|
573
|
-
if (s.beforeCapture) await s.beforeCapture(page);
|
|
574
|
-
await presentIdentitiesOnPage(page, identities.mappings);
|
|
575
584
|
const dir = `${projectBaseDir}/${s.name}/${locale}/${theme}`;
|
|
576
585
|
mkdirSync(dir, { recursive: true });
|
|
577
586
|
const path = `${dir}/${vp}.png`;
|
|
578
587
|
// animations: "disabled" jumps to end-state at the engine level — immune to CSS specificity, unlike an addStyleTag injection.
|
|
579
588
|
// screenshot() returns the same bytes it writes to `path` — reuse
|
|
580
589
|
// them for both checks instead of a statSync/read-back roundtrip.
|
|
581
|
-
const buffer = await page.
|
|
590
|
+
const buffer = await captureScenario(page, s, identities.mappings, {
|
|
582
591
|
path,
|
|
583
|
-
fullPage: s.fullPage ?? false,
|
|
584
592
|
animations: "disabled",
|
|
585
|
-
...(s.captureStyle !== undefined && { style: s.captureStyle }),
|
|
586
593
|
});
|
|
587
594
|
expect.soft(buffer.length).toBeGreaterThan(MIN_BYTES);
|
|
588
595
|
digests.push({
|
package/src/preload/dom.ts
CHANGED
|
@@ -65,15 +65,19 @@ if (typeof globalThis.HTMLElement !== "undefined") {
|
|
|
65
65
|
if (proto["releasePointerCapture"] === undefined)
|
|
66
66
|
proto["releasePointerCapture"] = () => undefined;
|
|
67
67
|
if (proto["scrollIntoView"] === undefined) proto["scrollIntoView"] = () => undefined;
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
// Without this, printing a happy-dom node walks its whole object graph:
|
|
71
|
+
// ownerDocument plus every React fiber property, which is 15 MB of string
|
|
72
|
+
// for a two-element tree and 0.5-1.7 s per call. A failed assertion inside
|
|
73
|
+
// waitFor pays that on every poll and blocks the loop long enough to starve
|
|
74
|
+
// React's commit and waitFor's own timeout. On Element, not
|
|
75
|
+
// HTMLElement: SVGElement does not inherit from HTMLElement.
|
|
76
|
+
if (typeof globalThis.Element !== "undefined") {
|
|
77
|
+
const elementProto = globalThis.Element.prototype as unknown as Record<string | symbol, unknown>;
|
|
74
78
|
const inspect = Symbol.for("nodejs.util.inspect.custom");
|
|
75
|
-
if (
|
|
76
|
-
|
|
79
|
+
if (elementProto[inspect] === undefined) {
|
|
80
|
+
elementProto[inspect] = function (this: Element): string {
|
|
77
81
|
const html = this.outerHTML;
|
|
78
82
|
return html.length > HTML_PRINT_LIMIT ? `${html.slice(0, HTML_PRINT_LIMIT)}…` : html;
|
|
79
83
|
};
|
|
@@ -84,15 +88,15 @@ if (typeof globalThis.HTMLElement !== "undefined") {
|
|
|
84
88
|
// test files in one process, so without afterEach, React components from
|
|
85
89
|
// file N stay mounted in file N+1's DOM.
|
|
86
90
|
//
|
|
87
|
-
// FIVE leak sources:
|
|
91
|
+
// FIVE leak sources (b is NOT cleaned here):
|
|
88
92
|
//
|
|
89
93
|
// a) testing-library/react container: cleanup() unmounts and removes
|
|
90
94
|
// every container node render() created.
|
|
91
95
|
//
|
|
92
|
-
// b)
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
+
// b) Containers not created via testing-library/react (e.g. #root via
|
|
97
|
+
// ReactDOM.createRoot + renderShell) are not cleaned here: the test
|
|
98
|
+
// file must unmount them itself, since body.replaceChildren() would
|
|
99
|
+
// trigger React effects outside act().
|
|
96
100
|
//
|
|
97
101
|
// c) Radix DismissableLayer sets body.style.pointerEvents='none' when
|
|
98
102
|
// opening a Dialog/Popover/Dropdown.
|
package/src/scaffold.ts
CHANGED
|
@@ -194,9 +194,10 @@ export function renderTestSetup(input: RenderTestSetupInput): ScaffoldTestSetup
|
|
|
194
194
|
// verified against bun 1.4), so it can't express "match only *.real.test.ts"
|
|
195
195
|
// on its own — every *.test.ts file matches bun's default test glob too,
|
|
196
196
|
// real.test.ts included. The positional filter narrows the run to files
|
|
197
|
-
// whose path contains "real.test.ts"
|
|
197
|
+
// whose path contains ".real.test.ts" (a bare "real.test.ts" would also
|
|
198
|
+
// match unreal.test.ts), so an unfiltered test:real never
|
|
198
199
|
// picks up the unit suite.
|
|
199
|
-
"test:real": `${REAL_PROVIDERS_ENV}=1 bun --config=${BUNFIG_FILES.real} test --timeout=${TEST_TIMEOUT_MS.real} real.test.ts`,
|
|
200
|
+
"test:real": `${REAL_PROVIDERS_ENV}=1 bun --config=${BUNFIG_FILES.real} test --timeout=${TEST_TIMEOUT_MS.real} .real.test.ts`,
|
|
200
201
|
"test:bunfig": "bun kumiko-testing bunfig --hoisted",
|
|
201
202
|
e2e: "bunx --bun playwright test",
|
|
202
203
|
"e2e:real": `${REAL_PROVIDERS_ENV}=1 bunx --bun playwright test`,
|