mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 12:52:25 +03:00
* fix(dashboard): strip browser-extension attrs before hydration Browser extensions (Bitdefender's bis_skin_checked, Grammarly's data-gr-ext-installed, LanguageTool's data-lt-installed) inject attributes into the DOM after SSR but before React hydrates, causing "attributes didn't match" hydration errors in the dev console. The <html> and <body> tags already have suppressHydrationWarning, but React only applies it one level deep — it doesn't propagate to Next.js internal elements like the <div hidden> metadata boundary where the mismatch actually surfaces. Add a synchronous pre-hydration cleanup script in <head> that: 1. Strips known extension attributes from document.documentElement 2. Observes for late injections via MutationObserver 3. Auto-disconnects after 5s (well past typical hydration) Verified: curl /login confirms the script is present in the served HTML with all target attributes (bis_skin_checked, data-google-query-id, data-gr-ext-installed, data-lt-installed) and the MutationObserver. Typecheck and lint clean. * test(dashboard): guard the pre-hydration extension-attr strip script Add a regression test mirroring the existing tests/unit/dashboard/crypto-randomuuid-polyfill.test.ts pattern: readFileSync src/app/layout.tsx and assert the full known browser-extension attribute list (bis_skin_checked, data-google-query-id, data-new-gr-c-s-check-loaded, data-gr-ext-installed, data-lt-installed, data-lt-tmp-id), the MutationObserver wiring (attributeFilter + 5s auto-disconnect), and the synchronous initial strip against document.documentElement all stay present in layout.tsx. Verified fail-then-pass: the assertions fail against the pre-fix tree (no such script present) and pass once the pre-hydration script is present, so a future layout.tsx refactor can no longer silently drop this script and reintroduce the hydration-mismatch warnings extension users were seeing. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
73 lines
2.9 KiB
TypeScript
73 lines
2.9 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve, join } from "node:path";
|
|
|
|
// Regression guard for the browser-extension hydration-mismatch fix: extensions
|
|
// like Bitdefender (bis_skin_checked), Google Search (data-google-query-id),
|
|
// Grammarly (data-new-gr-c-s-check-loaded / data-gr-ext-installed) and
|
|
// LanguageTool (data-lt-installed / data-lt-tmp-id) inject attributes into the
|
|
// DOM after SSR but before React hydrates. React's suppressHydrationWarning on
|
|
// <html>/<body> only applies one level deep, so the mismatch still surfaces on
|
|
// Next.js's internal elements. The fix adds a synchronous pre-hydration <script>
|
|
// in src/app/layout.tsx that strips the known attributes from
|
|
// document.documentElement and keeps stripping late injections via a
|
|
// MutationObserver that auto-disconnects after 5s. These assertions fail on the
|
|
// pre-fix tree (no such script present) and stay green afterwards, preventing a
|
|
// future layout.tsx refactor from silently dropping this script.
|
|
|
|
const cwd = process.cwd();
|
|
const layoutPath = resolve(join(cwd, "src/app/layout.tsx"));
|
|
|
|
const EXPECTED_ATTRS = [
|
|
"bis_skin_checked",
|
|
"data-google-query-id",
|
|
"data-new-gr-c-s-check-loaded",
|
|
"data-gr-ext-installed",
|
|
"data-lt-installed",
|
|
"data-lt-tmp-id",
|
|
];
|
|
|
|
test("layout.tsx strips the full known browser-extension attribute list before hydration", () => {
|
|
const layout = readFileSync(layoutPath, "utf8");
|
|
for (const attr of EXPECTED_ATTRS) {
|
|
assert.ok(
|
|
layout.includes(`"${attr}"`),
|
|
`layout.tsx must list "${attr}" among the pre-hydration extension attributes to strip`
|
|
);
|
|
}
|
|
assert.match(
|
|
layout,
|
|
/removeAttribute\s*\(\s*ATTRS\s*\[\s*i\s*\]\s*\)/,
|
|
"layout.tsx must call removeAttribute for each known extension attribute"
|
|
);
|
|
});
|
|
|
|
test("layout.tsx observes late attribute injections via MutationObserver and auto-disconnects", () => {
|
|
const layout = readFileSync(layoutPath, "utf8");
|
|
assert.match(
|
|
layout,
|
|
/new MutationObserver\s*\(/,
|
|
"layout.tsx must set up a MutationObserver to catch extension attributes injected after the initial strip"
|
|
);
|
|
assert.match(
|
|
layout,
|
|
/attributeFilter\s*:\s*ATTRS/,
|
|
"the MutationObserver must filter on the same known extension ATTRS list"
|
|
);
|
|
assert.match(
|
|
layout,
|
|
/setTimeout\s*\(\s*function\s*\(\s*\)\s*\{\s*obs\.disconnect\(\)\s*;?\s*\}\s*,\s*5000\s*\)/,
|
|
"the MutationObserver must auto-disconnect after 5s (well past typical hydration) to avoid a long-lived observer"
|
|
);
|
|
});
|
|
|
|
test("layout.tsx strips extension attributes from document.documentElement synchronously (before React hydrates)", () => {
|
|
const layout = readFileSync(layoutPath, "utf8");
|
|
assert.match(
|
|
layout,
|
|
/strip\s*\(\s*document\.documentElement\s*\)/,
|
|
"layout.tsx must run the strip synchronously against document.documentElement on initial script execution"
|
|
);
|
|
});
|