diff --git a/packages/wouter-preact/src/react-deps.js b/packages/wouter-preact/src/react-deps.js index 3753c661..25de4db5 100644 --- a/packages/wouter-preact/src/react-deps.js +++ b/packages/wouter-preact/src/react-deps.js @@ -42,7 +42,7 @@ export function useSyncExternalStore(subscribe, getSnapshot, getSSRSnapshot) { if (!is(_instance._value, getSnapshot())) { forceUpdate({ _instance }); } - }, [subscribe, value, getSnapshot]); + }, [subscribe, value, getSnapshot]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (!is(_instance._value, _instance._getSnapshot())) { @@ -54,7 +54,7 @@ export function useSyncExternalStore(subscribe, getSnapshot, getSSRSnapshot) { forceUpdate({ _instance }); } }); - }, [subscribe]); + }, [subscribe]); // eslint-disable-line react-hooks/exhaustive-deps return value; } diff --git a/packages/wouter/src/index.js b/packages/wouter/src/index.js index 957a7be6..98f16376 100644 --- a/packages/wouter/src/index.js +++ b/packages/wouter/src/index.js @@ -231,7 +231,10 @@ export function useSearchParams() { tempSearchParams = new URLSearchParams( typeof nextInit === "function" ? nextInit(tempSearchParams) : nextInit ); - navigate(location + "?" + tempSearchParams, options); + navigate( + location + (tempSearchParams.size ? "?" + tempSearchParams : ""), + options + ); }); return [searchParams, setSearchParams]; diff --git a/packages/wouter/test/history-patch.test.ts b/packages/wouter/test/history-patch.test.ts index 5ec4e20d..4884e5e0 100644 --- a/packages/wouter/test/history-patch.test.ts +++ b/packages/wouter/test/history-patch.test.ts @@ -11,22 +11,20 @@ describe("history patch", () => { }); test("history should be patched once", () => { - const fn = mock(); - const { result, unmount } = renderHook(() => reactHook()); + const pushStateFn = mock(); + const { result } = renderHook(() => reactHook()); - addEventListener("pushState", (e) => { - fn(); - }); + addEventListener("pushState", pushStateFn); expect(result.current[0]).toBe("/"); - expect(fn).toHaveBeenCalledTimes(0); + expect(pushStateFn).toHaveBeenCalledTimes(0); act(() => result.current[1]("/hello")); act(() => result.current[1]("/world")); expect(result.current[0]).toBe("/world"); - expect(fn).toHaveBeenCalledTimes(2); + expect(pushStateFn).toHaveBeenCalledTimes(2); - unmount(); + removeEventListener("pushstate", pushStateFn); }); }); diff --git a/packages/wouter/test/link.test.tsx b/packages/wouter/test/link.test.tsx index 326d594c..74adf677 100644 --- a/packages/wouter/test/link.test.tsx +++ b/packages/wouter/test/link.test.tsx @@ -1,12 +1,10 @@ import { type MouseEventHandler } from "react"; -import { test, expect, afterEach, mock, describe } from "bun:test"; -import { render, cleanup, fireEvent, act } from "@testing-library/react"; +import { test, expect, mock, describe } from "bun:test"; +import { render, fireEvent, act } from "@testing-library/react"; import { Router, Link } from "../src/index.js"; import { memoryLocation } from "../src/memory-location.js"; -afterEach(cleanup); - describe("", () => { test("renders a link with proper attributes", () => { const { getByText } = render( @@ -102,22 +100,22 @@ describe("", () => { clickEvt.preventDefault(); fireEvent(getByTestId("link"), clickEvt); - expect(location.pathname).not.toBe("/users"); + expect(location.pathname).toBe("/"); }); test("ignores the navigation when event is cancelled", () => { - const clickHandler: MouseEventHandler = (e) => { - e.preventDefault(); - }; - const { getByTestId } = render( - + e.preventDefault()} + > click ); fireEvent.click(getByTestId("link")); - expect(location.pathname).not.toBe("/users"); + expect(location.pathname).toBe("/"); }); test("accepts an `onClick` prop, fired before the navigation", () => { diff --git a/packages/wouter/test/memory-location.test.ts b/packages/wouter/test/memory-location.test.ts index 3010bdfa..41a41b0c 100644 --- a/packages/wouter/test/memory-location.test.ts +++ b/packages/wouter/test/memory-location.test.ts @@ -5,32 +5,29 @@ import { memoryLocation } from "../src/memory-location.js"; test("returns a hook that is compatible with location spec", () => { const { hook } = memoryLocation(); - const { result, unmount } = renderHook(() => hook()); + const { result } = renderHook(() => hook()); const [value, update] = result.current; expect(typeof value).toBe("string"); expect(typeof update).toBe("function"); - unmount(); }); test("should support initial path", () => { const { hook } = memoryLocation({ path: "/test-case" }); - const { result, unmount } = renderHook(() => hook()); + const { result } = renderHook(() => hook()); const [value] = result.current; expect(value).toBe("/test-case"); - unmount(); }); test("should support initial path with query", () => { const { searchHook } = memoryLocation({ path: "/test-case?foo=bar" }); - const { result, unmount } = renderHook(() => searchHook()); + const { result } = renderHook(() => searchHook()); const value = result.current; expect(value).toBe("foo=bar"); - unmount(); }); test("should support search path as parameter", () => { @@ -39,55 +36,50 @@ test("should support search path as parameter", () => { searchPath: "key=value", }); - const { result, unmount } = renderHook(() => searchHook()); + const { result } = renderHook(() => searchHook()); const value = result.current; expect(value).toBe("foo=bar&key=value"); - unmount(); }); test('should return location hook that has initial path "/" by default', () => { const { hook } = memoryLocation(); - const { result, unmount } = renderHook(() => hook()); + const { result } = renderHook(() => hook()); const [value] = result.current; expect(value).toBe("/"); - unmount(); }); test('should return search hook that has initial query "" by default', () => { const { searchHook } = memoryLocation(); - const { result, unmount } = renderHook(() => searchHook()); + const { result } = renderHook(() => searchHook()); const value = result.current; expect(value).toBe(""); - unmount(); }); test("should return standalone `navigate` method", () => { const { hook, navigate } = memoryLocation(); - const { result, unmount } = renderHook(() => hook()); + const { result } = renderHook(() => hook()); act(() => navigate("/standalone")); const [value] = result.current; expect(value).toBe("/standalone"); - unmount(); }); test("should return location hook that supports navigation", () => { const { hook } = memoryLocation(); - const { result, unmount } = renderHook(() => hook()); + const { result } = renderHook(() => hook()); act(() => result.current[1]("/location")); const [value] = result.current; expect(value).toBe("/location"); - unmount(); }); test("should record all history when `record` option is provided", () => { @@ -97,7 +89,7 @@ test("should record all history when `record` option is provided", () => { navigate: standalone, } = memoryLocation({ record: true, path: "/test" }); - const { result, unmount } = renderHook(() => hook()); + const { result } = renderHook(() => hook()); act(() => standalone("/standalone")); act(() => result.current[1]("/location")); @@ -113,8 +105,6 @@ test("should record all history when `record` option is provided", () => { act(() => result.current[1]("/location", { replace: true })); expect(history).toStrictEqual(["/test", "/standalone", "/location"]); - - unmount(); }); test("should not have history when `record` option is falsy", () => { @@ -145,7 +135,7 @@ test("should have reset method that reset hook location", () => { record: true, path: "/test", }); - const { result, unmount } = renderHook(() => hook()); + const { result } = renderHook(() => hook()); act(() => navigate("/location")); @@ -158,6 +148,4 @@ test("should have reset method that reset hook location", () => { expect(history).toStrictEqual(["/test"]); expect(result.current[0]).toBe("/test"); - - unmount(); }); diff --git a/packages/wouter/test/redirect.test.tsx b/packages/wouter/test/redirect.test.tsx index 9f1f1066..b607ef98 100644 --- a/packages/wouter/test/redirect.test.tsx +++ b/packages/wouter/test/redirect.test.tsx @@ -1,83 +1,52 @@ import { test, expect } from "bun:test"; import { render } from "@testing-library/react"; -import { useState } from "react"; import { Redirect, Router } from "../src/index.js"; -export const customHookWithReturn = - (initialPath = "/") => - () => { - const [path, updatePath] = useState(initialPath); - const navigate = (path: string) => { - updatePath(path); - return "foo"; - }; - - return [path, navigate]; - }; - test("renders nothing", () => { - const { container, unmount } = render(); + const { container } = render(); expect(container.childNodes.length).toBe(0); - unmount(); }); test("results in change of current location", () => { - const { unmount } = render(); + render(); expect(location.pathname).toBe("/users"); - unmount(); }); test("supports `base` routers with relative path", () => { - const { unmount } = render( + render( ); expect(location.pathname).toBe("/app/nested"); - unmount(); }); test("supports `base` routers with absolute path", () => { - const { unmount } = render( + render( ); expect(location.pathname).toBe("/absolute"); - unmount(); }); test("supports replace navigation", () => { const histBefore = history.length; - const { unmount } = render(); + render(); expect(location.pathname).toBe("/users"); expect(history.length).toBe(histBefore); - unmount(); }); test("supports history state", () => { const testState = { hello: "world" }; - const { unmount } = render(); + render(); expect(location.pathname).toBe("/users"); expect(history.state).toStrictEqual(testState); - unmount(); -}); - -test("useLayoutEffect should return nothing", () => { - const { unmount } = render( - // @ts-expect-error - - - - ); - - expect(location.pathname).toBe("/users"); - unmount(); }); diff --git a/packages/wouter/test/route.test.tsx b/packages/wouter/test/route.test.tsx index 1d17ad2d..17e39694 100644 --- a/packages/wouter/test/route.test.tsx +++ b/packages/wouter/test/route.test.tsx @@ -1,13 +1,10 @@ -import { it, expect, afterEach } from "bun:test"; -import { render, act, cleanup } from "@testing-library/react"; +import { it, expect } from "bun:test"; +import { render, act } from "@testing-library/react"; import { Router, Route } from "../src/index.js"; import { memoryLocation } from "../src/memory-location.js"; import { ReactElement } from "react"; -// Clean up after each test to avoid DOM pollution -afterEach(cleanup); - const testRouteRender = (initialPath: string, jsx: ReactElement) => { return render( {jsx} @@ -87,7 +84,7 @@ it("supports `component` prop similar to React-Router", () => { }); it("supports `base` routers with relative path", () => { - const { container, unmount } = render( + const { container } = render(

Nested

@@ -102,8 +99,6 @@ it("supports `base` routers with relative path", () => { expect(container.children).toHaveLength(1); expect(container.firstChild).toHaveProperty("tagName", "H1"); - - unmount(); }); it("supports `path` prop with regex", () => { diff --git a/packages/wouter/test/setup.ts b/packages/wouter/test/setup.ts index 8f17b3ba..731fbd5a 100644 --- a/packages/wouter/test/setup.ts +++ b/packages/wouter/test/setup.ts @@ -1,6 +1,7 @@ import { GlobalRegistrator } from "@happy-dom/global-registrator"; -import { expect } from "bun:test"; +import { expect, beforeEach, afterEach } from "bun:test"; import * as matchers from "@testing-library/jest-dom/matchers"; +import { cleanup } from "@testing-library/react"; // Register happy-dom globals (document, window, etc.) GlobalRegistrator.register({ @@ -26,3 +27,10 @@ export const withoutLocation = (fn: () => T): T => { globalThis.location = original; } }; + +beforeEach(() => { + history.go(-history.length + 1); + history.replaceState(null, "", "/"); +}); + +afterEach(cleanup); diff --git a/packages/wouter/test/switch.test.tsx b/packages/wouter/test/switch.test.tsx index cf9315f8..4abedd62 100644 --- a/packages/wouter/test/switch.test.tsx +++ b/packages/wouter/test/switch.test.tsx @@ -1,14 +1,11 @@ -import { it, expect, afterEach } from "bun:test"; +import { it, expect } from "bun:test"; import { Router, Route, Switch } from "../src/index.js"; import { memoryLocation } from "../src/memory-location.js"; -import { render, act, cleanup } from "@testing-library/react"; +import { render, act } from "@testing-library/react"; import { PropsWithChildren, ReactElement, JSX } from "react"; -// Clean up after each test to avoid DOM pollution -afterEach(cleanup); - const raf = () => new Promise((resolve) => requestAnimationFrame(resolve)); const testRouteRender = (initialPath: string, jsx: ReactElement) => { @@ -111,7 +108,7 @@ it("allows to specify which routes to render via `location` prop", () => { it("always ensures the consistency of inner routes rendering", async () => { history.replaceState(null, "", "/foo/bar"); - const { unmount } = render( + render( {(params) => { @@ -127,8 +124,6 @@ it("always ensures the consistency of inner routes rendering", async () => { await raf(); history.pushState(null, "", "/"); }); - - unmount(); }); it("supports catch-all routes with wildcard segments", async () => { diff --git a/packages/wouter/test/use-browser-location.test.tsx b/packages/wouter/test/use-browser-location.test.tsx index 97c01cc9..059d3dea 100644 --- a/packages/wouter/test/use-browser-location.test.tsx +++ b/packages/wouter/test/use-browser-location.test.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import { test, expect, describe, beforeEach, afterEach } from "bun:test"; -import { renderHook, act, waitFor, cleanup } from "@testing-library/react"; +import { test, expect, describe } from "bun:test"; +import { renderHook, act, waitFor } from "@testing-library/react"; import { useBrowserLocation, navigate, @@ -8,39 +8,32 @@ import { useHistoryState, } from "../src/use-browser-location.js"; -afterEach(cleanup); - test("returns a pair [value, update]", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); const [value, update] = result.current; expect(typeof value).toBe("string"); expect(typeof update).toBe("function"); - unmount(); }); describe("`value` first argument", () => { - beforeEach(() => history.replaceState(null, "", "/")); - test("reflects the current pathname", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); expect(result.current[0]).toBe("/"); - unmount(); }); test("reacts to `pushState` / `replaceState`", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); act(() => history.pushState(null, "", "/foo")); expect(result.current[0]).toBe("/foo"); act(() => history.replaceState(null, "", "/bar")); expect(result.current[0]).toBe("/bar"); - unmount(); }); test("supports history.back() navigation", async () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); act(() => history.pushState(null, "", "/foo")); await waitFor(() => expect(result.current[0]).toBe("/foo")); @@ -59,23 +52,17 @@ describe("`value` first argument", () => { }); await waitFor(() => expect(result.current[0]).toBe("/"), { timeout: 1000 }); - unmount(); }); test("supports history state", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); - const { result: state, unmount: unmountState } = renderHook(() => - useHistoryState() - ); + const { result } = renderHook(() => useBrowserLocation()); + const { result: state } = renderHook(() => useHistoryState()); const navigate = result.current[1]; act(() => navigate("/path", { state: { hello: "world" } })); expect(state.current).toStrictEqual({ hello: "world" }); - - unmount(); - unmountState(); }); test("uses fail-safe escaping", () => { @@ -91,8 +78,6 @@ describe("`value` first argument", () => { }); describe("`useSearch` hook", () => { - beforeEach(() => history.replaceState(null, "", "/")); - test("allows to get current search string", () => { const { result: searchResult } = renderHook(() => useSearch()); act(() => navigate("/foo?hello=world&whats=up")); @@ -152,36 +137,33 @@ describe("`useSearch` hook", () => { describe("`update` second parameter", () => { test("rerenders the component", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); const update = result.current[1]; act(() => update("/about")); expect(result.current[0]).toBe("/about"); - unmount(); }); test("changes the current location", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); const update = result.current[1]; act(() => update("/about")); expect(location.pathname).toBe("/about"); - unmount(); }); test("saves a new entry in the History object", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); const update = result.current[1]; const histBefore = history.length; act(() => update("/about")); expect(history.length).toBe(histBefore + 1); - unmount(); }); test("replaces last entry with a new entry in the History object", () => { - const { result, unmount } = renderHook(() => useBrowserLocation()); + const { result } = renderHook(() => useBrowserLocation()); const update = result.current[1]; const histBefore = history.length; @@ -189,19 +171,15 @@ describe("`update` second parameter", () => { expect(history.length).toBe(histBefore); expect(location.pathname).toBe("/foo"); - unmount(); }); test("stays the same reference between re-renders (function ref)", () => { - const { result, rerender, unmount } = renderHook(() => - useBrowserLocation() - ); + const { result, rerender } = renderHook(() => useBrowserLocation()); const updateWas = result.current[1]; rerender(); const updateNow = result.current[1]; expect(updateWas).toBe(updateNow); - unmount(); }); }); diff --git a/packages/wouter/test/use-hash-location.test.tsx b/packages/wouter/test/use-hash-location.test.tsx index 336af81e..3a2e7b17 100644 --- a/packages/wouter/test/use-hash-location.test.tsx +++ b/packages/wouter/test/use-hash-location.test.tsx @@ -1,4 +1,4 @@ -import { test, expect, beforeEach, mock } from "bun:test"; +import { test, expect, mock } from "bun:test"; import { renderHook, render, act } from "@testing-library/react"; import { renderToStaticMarkup } from "react-dom/server"; @@ -8,11 +8,6 @@ import { useHashLocation } from "../src/use-hash-location.js"; import { waitForHashChangeEvent } from "./test-utils"; import { ReactNode, useSyncExternalStore } from "react"; -beforeEach(() => { - history.replaceState(null, "", "/"); - location.hash = ""; -}); - test("gets current location from `location.hash`", () => { location.hash = "/app/users"; const { result } = renderHook(() => useHashLocation()); @@ -195,7 +190,7 @@ test("works even if `hashchange` listeners are called asynchronously ", async () location.hash = "#/a"; }); - const { unmount } = render( + render( @@ -215,7 +210,6 @@ test("works even if `hashchange` listeners are called asynchronously ", async () // paths should not contain "b", because the outer route // does not match, so inner component should not be rendered expect(paths).toEqual(["/a"]); - unmount(); }); test("defines a custom way of rendering link hrefs", () => { @@ -239,14 +233,14 @@ test("handles navigation with data: protocol", async () => { const initialHistoryLength = history.length; await waitForHashChangeEvent(() => { - navigate("/new-path"); + act(() => navigate("/new-path")); }); expect(location.hash).toBe("#/new-path"); expect(history.length).toBe(initialHistoryLength + 1); await waitForHashChangeEvent(() => { - navigate("/another-path", { replace: true }); + act(() => navigate("/another-path", { replace: true })); }); expect(location.hash).toBe("#/another-path"); diff --git a/packages/wouter/test/use-location.test.tsx b/packages/wouter/test/use-location.test.tsx index 3baee573..df4f3265 100644 --- a/packages/wouter/test/use-location.test.tsx +++ b/packages/wouter/test/use-location.test.tsx @@ -43,7 +43,6 @@ describe.each([ navigate: hashNavigation, act: (cb: () => void) => waitForHashChangeEvent(() => act(cb)), clear: () => { - location.hash = ""; history.replaceState(null, "", "/"); }, }, @@ -61,19 +60,18 @@ describe.each([ beforeEach(() => stub.clear()); it("returns a pair [value, update]", () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ hook: stub.hook }), }); const [value, update] = result.current; expect(typeof value).toBe("string"); expect(typeof update).toBe("function"); - unmount(); }); describe("`value` first argument", () => { it("returns `/` when URL contains only a basepath", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ base: "/app", hook: stub.hook, @@ -82,11 +80,10 @@ describe.each([ await stub.act(() => stub.navigate("/app")); expect(result.current[0]).toBe("/"); - unmount(); }); it("basepath should be case-insensitive", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ base: "/MyApp", hook: stub.hook, @@ -95,11 +92,10 @@ describe.each([ await stub.act(() => stub.navigate("/myAPP/users/JohnDoe")); expect(result.current[0]).toBe("/users/JohnDoe"); - unmount(); }); it("returns an absolute path in case of unmatched base path", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ base: "/MyApp", hook: stub.hook, @@ -108,11 +104,10 @@ describe.each([ await stub.act(() => stub.navigate("/MyOtherApp/users/JohnDoe")); expect(result.current[0]).toBe("~/MyOtherApp/users/JohnDoe"); - unmount(); }); it("automatically unescapes specials characters", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ hook: stub.hook, }), @@ -127,11 +122,10 @@ describe.each([ await stub.act(() => stub.navigate("/%D1%88%D0%B5%D0%BB%D0%BB%D1%8B")); expect(result.current[0]).toBe("/шеллы"); - unmount(); }); it("can accept unescaped basepaths", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ base: "/hello мир", // basepath is not escaped hook: stub.hook, @@ -140,12 +134,10 @@ describe.each([ await stub.act(() => stub.navigate("/hello%20%D0%BC%D0%B8%D1%80/rel")); expect(result.current[0]).toBe("/rel"); - - unmount(); }); it("can accept unescaped basepaths", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ base: "/hello%20%D0%BC%D0%B8%D1%80", // basepath is already escaped hook: stub.hook, @@ -154,25 +146,22 @@ describe.each([ await stub.act(() => stub.navigate("/hello мир/rel")); expect(result.current[0]).toBe("/rel"); - - unmount(); }); }); describe("`update` second parameter", () => { it("rerenders the component", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ hook: stub.hook }), }); const update = result.current[1]; await stub.act(() => update("/about")); expect(stub.location()).toBe("/about"); - unmount(); }); it("stays the same reference between re-renders (function ref)", () => { - const { result, rerender, unmount } = renderHook(() => useLocation(), { + const { result, rerender } = renderHook(() => useLocation(), { wrapper: createContainer({ hook: stub.hook }), }); @@ -181,11 +170,10 @@ describe.each([ const updateNow = result.current[1]; expect(updateWas).toBe(updateNow); - unmount(); }); it("supports a basepath", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ base: "/app", hook: stub.hook, @@ -196,11 +184,10 @@ describe.each([ await stub.act(() => update("/dashboard")); expect(stub.location()).toBe("/app/dashboard"); - unmount(); }); it("ignores the '/' basepath", async () => { - const { result, unmount } = renderHook(() => useLocation(), { + const { result } = renderHook(() => useLocation(), { wrapper: createContainer({ base: "/", hook: stub.hook, @@ -211,7 +198,6 @@ describe.each([ await stub.act(() => update("/dashboard")); expect(stub.location()).toBe("/dashboard"); - unmount(); }); }); }); diff --git a/packages/wouter/test/use-params.test.tsx b/packages/wouter/test/use-params.test.tsx index a88be6b3..eb433d21 100644 --- a/packages/wouter/test/use-params.test.tsx +++ b/packages/wouter/test/use-params.test.tsx @@ -1,12 +1,9 @@ -import { act, renderHook, cleanup } from "@testing-library/react"; -import { test, expect, beforeEach, afterEach } from "bun:test"; +import { act, renderHook } from "@testing-library/react"; +import { test, expect } from "bun:test"; import { useParams, Router, Route, Switch } from "../src/index.js"; import { memoryLocation } from "../src/memory-location.js"; -beforeEach(() => history.replaceState(null, "", "/")); -afterEach(cleanup); - test("returns empty object when used outside of ", () => { const { result } = renderHook(() => useParams()); expect(result.current).toEqual({}); diff --git a/packages/wouter/test/use-search-params.test.tsx b/packages/wouter/test/use-search-params.test.tsx index 151e68ee..27a0d2bf 100644 --- a/packages/wouter/test/use-search-params.test.tsx +++ b/packages/wouter/test/use-search-params.test.tsx @@ -1,9 +1,7 @@ import { renderHook, act } from "@testing-library/react"; import { useSearchParams, Router } from "../src/index.js"; import { navigate } from "../src/use-browser-location.js"; -import { it, expect, beforeEach } from "bun:test"; - -beforeEach(() => history.replaceState(null, "", "/")); +import { it, expect } from "bun:test"; it("can return browser search params", () => { history.replaceState(null, "", "/users?active=true"); @@ -60,3 +58,10 @@ it("is safe against parameter injection", () => { expect(result.current[0].get("search")).toBe("foo¶meter_injection=bar"); }); + +it("does not add question mark when search string is empty", () => { + const { result } = renderHook(() => useSearchParams()); + + act(() => result.current[1]({})); + expect(location.href).toBe("https://wouter.dev/"); +}); diff --git a/packages/wouter/test/use-search.test.tsx b/packages/wouter/test/use-search.test.tsx index cacd4577..0005ffbd 100644 --- a/packages/wouter/test/use-search.test.tsx +++ b/packages/wouter/test/use-search.test.tsx @@ -1,11 +1,8 @@ -import { renderHook, act, cleanup } from "@testing-library/react"; +import { renderHook, act } from "@testing-library/react"; import { useSearch, Router } from "../src/index.js"; import { navigate } from "../src/use-browser-location.js"; import { memoryLocation } from "../src/memory-location.js"; -import { test, expect, beforeEach, afterEach } from "bun:test"; - -beforeEach(() => history.replaceState(null, "", "/")); -afterEach(cleanup); +import { test, expect } from "bun:test"; test("returns browser search string", () => { history.replaceState(null, "", "/users?active=true"); diff --git a/packages/wouter/test/view-transitions.test.tsx b/packages/wouter/test/view-transitions.test.tsx index 6c44c8bd..93dc7ebd 100644 --- a/packages/wouter/test/view-transitions.test.tsx +++ b/packages/wouter/test/view-transitions.test.tsx @@ -1,10 +1,13 @@ -import { test, expect, describe, mock, afterEach } from "bun:test"; -import { render, cleanup, fireEvent } from "@testing-library/react"; -import { Router, Link, useLocation, type AroundNavHandler } from "../src/index.js"; +import { test, expect, describe, mock } from "bun:test"; +import { render, fireEvent } from "@testing-library/react"; +import { + Router, + Link, + useLocation, + type AroundNavHandler, +} from "../src/index.js"; import { memoryLocation } from "../src/memory-location.js"; -afterEach(cleanup); - describe("view transitions", () => { test("Link with transition prop triggers aroundNav with transition in options", () => { // 1. Setup: create aroundNav callback that captures calls @@ -66,7 +69,8 @@ describe("view transitions", () => { expect(aroundNav).toHaveBeenCalledTimes(1); - const [, to, options] = (aroundNav as ReturnType).mock.calls[0]; + const [, to, options] = (aroundNav as ReturnType).mock + .calls[0]; expect(to).toBe("/about"); expect(options.transition).toBe(true); });