Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/wouter-preact/src/react-deps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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())) {
Expand All @@ -54,7 +54,7 @@ export function useSyncExternalStore(subscribe, getSnapshot, getSSRSnapshot) {
forceUpdate({ _instance });
}
});
}, [subscribe]);
}, [subscribe]); // eslint-disable-line react-hooks/exhaustive-deps

return value;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/wouter/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
14 changes: 6 additions & 8 deletions packages/wouter/test/history-patch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
20 changes: 9 additions & 11 deletions packages/wouter/test/link.test.tsx
Original file line number Diff line number Diff line change
@@ -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("<Link />", () => {
test("renders a link with proper attributes", () => {
const { getByText } = render(
Expand Down Expand Up @@ -102,22 +100,22 @@ describe("<Link />", () => {
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(
<Link href="/users" data-testid="link" onClick={clickHandler}>
<Link
href="/users"
data-testid="link"
onClick={(e) => e.preventDefault()}
>
click
</Link>
);

fireEvent.click(getByTestId("link"));
expect(location.pathname).not.toBe("/users");
expect(location.pathname).toBe("/");
});

test("accepts an `onClick` prop, fired before the navigation", () => {
Expand Down
32 changes: 10 additions & 22 deletions packages/wouter/test/memory-location.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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", () => {
Expand All @@ -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"));
Expand All @@ -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", () => {
Expand Down Expand Up @@ -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"));

Expand All @@ -158,6 +148,4 @@ test("should have reset method that reset hook location", () => {
expect(history).toStrictEqual(["/test"]);

expect(result.current[0]).toBe("/test");

unmount();
});
43 changes: 6 additions & 37 deletions packages/wouter/test/redirect.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<Redirect to="/users" />);
const { container } = render(<Redirect to="/users" />);
expect(container.childNodes.length).toBe(0);
unmount();
});

test("results in change of current location", () => {
const { unmount } = render(<Redirect to="/users" />);
render(<Redirect to="/users" />);

expect(location.pathname).toBe("/users");
unmount();
});

test("supports `base` routers with relative path", () => {
const { unmount } = render(
render(
<Router base="/app">
<Redirect to="/nested" />
</Router>
);

expect(location.pathname).toBe("/app/nested");
unmount();
});

test("supports `base` routers with absolute path", () => {
const { unmount } = render(
render(
<Router base="/app">
<Redirect to="~/absolute" />
</Router>
);

expect(location.pathname).toBe("/absolute");
unmount();
});

test("supports replace navigation", () => {
const histBefore = history.length;

const { unmount } = render(<Redirect to="/users" replace />);
render(<Redirect to="/users" replace />);

expect(location.pathname).toBe("/users");
expect(history.length).toBe(histBefore);
unmount();
});

test("supports history state", () => {
const testState = { hello: "world" };
const { unmount } = render(<Redirect to="/users" state={testState} />);
render(<Redirect to="/users" state={testState} />);

expect(location.pathname).toBe("/users");
expect(history.state).toStrictEqual(testState);
unmount();
});

test("useLayoutEffect should return nothing", () => {
const { unmount } = render(
// @ts-expect-error
<Router hook={customHookWithReturn()}>
<Redirect to="/users" replace />
</Router>
);

expect(location.pathname).toBe("/users");
unmount();
});
11 changes: 3 additions & 8 deletions packages/wouter/test/route.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<Router hook={memoryLocation({ path: initialPath }).hook}>{jsx}</Router>
Expand Down Expand Up @@ -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(
<Router base="/app">
<Route path="/nested">
<h1>Nested</h1>
Expand All @@ -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", () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/wouter/test/setup.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -26,3 +27,10 @@ export const withoutLocation = <T>(fn: () => T): T => {
globalThis.location = original;
}
};

beforeEach(() => {
history.go(-history.length + 1);
history.replaceState(null, "", "/");
});

afterEach(cleanup);
Loading
Loading