From a1b03e96845d188a52ed1bd7dad5a57809a9ac9c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 15:43:31 +0000 Subject: [PATCH] Reach 100% line coverage without duplicating test suites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The coverage report was double-counting library code: preact.test.tsx copies packages/wouter/src/* into wouter-preact/src/ for the duration of the run, so every source file was measured twice — once fully exercised at its original path, once barely exercised as a copy. Excluding the temporary copies from coverage (react-deps.js, the only real preact-specific source, stays measured) lifts the total from 92.6% to 99.7% without a single new test. The remaining uncovered lines each get a targeted fix: - use-browser-location.js 72-85 (history monkey-patch): the wouter-preact copy loaded first and claimed the one-shot patch, so the original module skipped it. Preloading the module in setup.ts after happy-dom registration makes the original file own the patch again. - paths.js 32 (decodeURI fail-safe): one test navigating to a search string with a malformed escape sequence. - wouter-preact react-deps.js 43 (useSyncExternalStore shim re-render when the store mutates between render and layout effect): one test using a child layout effect, which runs before the parent's. Line coverage is now 100% and enforced via coverageThreshold in bunfig.toml. Bun applies the threshold per file, so it is set to 0.99: every source file must stay at 99%+ lines for bun test --coverage to exit 0. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NA9Qh5EpmZKA9UZASuQfEq --- bunfig.toml | 14 +++++- packages/wouter-preact/test/preact.test.tsx | 50 +++++++++++++++++++++ packages/wouter/test/setup.ts | 6 +++ packages/wouter/test/use-search.test.tsx | 8 ++++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/bunfig.toml b/bunfig.toml index a32da4c..ede45b0 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,4 +1,16 @@ [test] preload = ["./packages/wouter/test/setup.ts"] coverageSkipTestFiles = true -coveragePathIgnorePatterns = ["**/test/**"] +coveragePathIgnorePatterns = [ + "**/test/**", + # temporary copies of packages/wouter/src/* created by preact.test.tsx; + # already measured at their original path (react-deps.js is preact-specific and stays measured) + "packages/wouter-preact/src/index.js", + "packages/wouter-preact/src/paths.js", + "packages/wouter-preact/src/memory-location.js", + "packages/wouter-preact/src/use-browser-location.js", + "packages/wouter-preact/src/use-hash-location.js", + "packages/wouter-preact/src/use-sync-external-store.js", + "packages/wouter-preact/src/use-sync-external-store.native.js", +] +coverageThreshold = { lines = 0.99 } diff --git a/packages/wouter-preact/test/preact.test.tsx b/packages/wouter-preact/test/preact.test.tsx index a02f27d..2679f33 100644 --- a/packages/wouter-preact/test/preact.test.tsx +++ b/packages/wouter-preact/test/preact.test.tsx @@ -157,6 +157,56 @@ describe("Preact support", () => { }); }); +describe("useSyncExternalStore shim", () => { + test("re-renders when the store mutates between render and layout effect", async () => { + // the internal shim is untyped, it mirrors the React useSyncExternalStore signature + const { useSyncExternalStore } = (await import( + // @ts-expect-error + "../src/react-deps.js" + )) as { + useSyncExternalStore: ( + subscribe: (cb: () => void) => () => void, + getSnapshot: () => T, + getSSRSnapshot?: () => T + ) => T; + }; + const { useLayoutEffect } = await import("preact/hooks"); + + let value = "initial"; + const listeners = new Set<() => void>(); + const subscribe = (cb: () => void) => { + listeners.add(cb); + return () => listeners.delete(cb); + }; + + // child layout effects run before the parent's, so this mutation happens + // after the parent has rendered but before its layout effect compares snapshots + const Child = () => { + useLayoutEffect(() => { + value = "mutated"; + }, []); + return null; + }; + + const Parent = () => { + const snapshot = useSyncExternalStore(subscribe, () => value); + return ( +
+ {snapshot} + +
+ ); + }; + + const container = document.body.appendChild(document.createElement("div")); + act(() => { + render(, container); + }); + + expect(container.textContent).toBe("mutated"); + }); +}); + describe("Preact SSR", () => { test.skip("supports SSR (fix: useSyncExternalStore polyfill in Bun)", async () => { const { Router, useLocation } = await loadPreact(); diff --git a/packages/wouter/test/setup.ts b/packages/wouter/test/setup.ts index 731fbd5..9883205 100644 --- a/packages/wouter/test/setup.ts +++ b/packages/wouter/test/setup.ts @@ -13,6 +13,12 @@ GlobalRegistrator.register({ // Extend Bun's expect with jest-dom matchers (expect as any).extend(matchers); +// Load the library first so the history monkey-patch in use-browser-location.js +// is applied (and attributed) to this copy of the module, not the temporary +// wouter-preact copy created by preact.test.tsx. Must run after happy-dom +// registration, hence a dynamic import. +await import("../src/use-browser-location.js"); + /** * Runs a function with `location` temporarily removed from globalThis. * Simulates pure Node.js SSR environment for testing. diff --git a/packages/wouter/test/use-search.test.tsx b/packages/wouter/test/use-search.test.tsx index 0005ffb..1011f6f 100644 --- a/packages/wouter/test/use-search.test.tsx +++ b/packages/wouter/test/use-search.test.tsx @@ -11,6 +11,14 @@ test("returns browser search string", () => { expect(result.current).toEqual("active=true"); }); +test("returns search string as-is when it contains malformed escapes", () => { + history.replaceState(null, "", "/users?q=100%"); + const { result } = renderHook(() => useSearch()); + + // decodeURI throws on "%", sanitizeSearch falls back to the raw string + expect(result.current).toEqual("q=100%"); +}); + test("can be customized in the Router", () => { const customSearchHook = ({ customOption = "unused" }) => "none";