Skip to content

Commit

Permalink
feat: add absolute paths for routers using base
Browse files Browse the repository at this point in the history
  • Loading branch information
jvdsande authored and molefrog committed Dec 14, 2020
1 parent 77e20ef commit 864a910
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 14 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Wouter provides a simple API that many developers and library authors appreciate
- [TypeScript support](#can-i-use-wouter-in-my-typescript-project)
- [Using with Preact](#preact-support)
- [Server-side Rendering (SSR)](#is-there-any-support-for-server-side-rendering-ssr)
- [Routing in less than 350B](#1kb-is-too-much-i-cant-afford-it)
- [Routing in less than 400B](#1kb-is-too-much-i-cant-afford-it)

## Getting Started

Expand Down Expand Up @@ -332,7 +332,7 @@ Read more → [Customizing the location hook](#customizing-the-location-hook).
- **`matcher: (pattern: string, path: string) => [match: boolean, params: object]`** — a custom function used for matching the current location against the user-defined patterns like `/app/users/:id`. Should return a match result and an hash of extracted parameters. It should return `[false, null]` when there is no match.

- **`base: string`** — an optional setting that allows to specify a base path, such as `/app`. All application routes
will be relative to that path.
will be relative to that path. Prefixing a route with `~` will make it absolute, bypassing the base path.

Read more → [Matching Dynamic Segments](#matching-dynamic-segments).

Expand Down Expand Up @@ -618,7 +618,7 @@ const handleRequest = (req, res) => {

We've got some great news for you! If you're a minimalist bundle-size nomad and you need a damn simple
routing in your app, you can just use the [`useLocation` hook](#uselocation-hook-working-with-the-history)
which is only **362 bytes gzipped** and manually match the current location with it:
which is only **380 bytes gzipped** and manually match the current location with it:

```js
import useLocation from "wouter/use-location";
Expand Down
7 changes: 6 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ export const Link = (props) => {
);

// wraps children in `a` if needed
const extraProps = { href: base + href, onClick: handleClick, to: null };
const extraProps = {
// handle nested routers and absolute paths
href: href[0] === "~" ? href.slice(1) : base + href,
onClick: handleClick,
to: null,
};
const jsx = isValidElement(children) ? children : h("a", props);

return cloneElement(jsx, extraProps);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
"size-limit": [
{
"path": "index.js",
"limit": "1337 B"
"limit": "1339 B"
},
{
"path": "use-location.js",
"limit": "377 B"
"limit": "380 B"
}
],
"husky": {
Expand Down
11 changes: 11 additions & 0 deletions test/link.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,14 @@ it("renders `href` with basepath", () => {
const link = getByTestId("link");
expect(link.getAttribute("href")).toBe("/app/dashboard");
});

it("renders `href` with absolute links", () => {
const { getByTestId } = render(
<Router base="/app">
<Link href="~/home" data-testid="link" />
</Router>
);

const link = getByTestId("link");
expect(link.getAttribute("href")).toBe("/home");
});
22 changes: 22 additions & 0 deletions test/redirect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ it("results in change of current location", () => {
unmount();
});

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

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

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

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

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

Expand Down
41 changes: 41 additions & 0 deletions test/route-rendering.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { render, act } from "@testing-library/react";
import TestRenderer from "react-test-renderer";

import { Router, Route } from "../index.js";
Expand Down Expand Up @@ -62,3 +63,43 @@ it("supports `component` prop similar to React-Router", () => {

expect(result.findByType("h2").props.children).toBe("All users");
});

it("supports `base` routers with relative path", () => {
const { container, unmount } = render(
<Router base="/app">
<Route path="/nested">
<h1>Nested</h1>
</Route>
<Route path="~/absolute">
<h2>Absolute</h2>
</Route>
</Router>
);

act(() => history.replaceState(null, "", "/app/nested"));

expect(container.childNodes.length).toBe(1);
expect(container.firstChild.tagName).toBe("H1");

unmount();
});

it("supports `base` routers with absolute path", () => {
const { container, unmount } = render(
<Router base="/app">
<Route path="/nested">
<h1>Nested</h1>
</Route>
<Route path="~/absolute">
<h2>Absolute</h2>
</Route>
</Router>
);

act(() => history.replaceState(null, "", "/absolute"));

expect(container.childNodes.length).toBe(1);
expect(container.firstChild.tagName).toBe("H2");

unmount();
});
14 changes: 8 additions & 6 deletions test/use-location.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,22 @@ describe("`value` first argument", () => {
});

it("basepath should be case-insensitive", () => {
const { result, unmount } = renderHook(() => useLocation({ base: "/App" }));
const { result, unmount } = renderHook(() =>
useLocation({ base: "/MyApp" })
);

act(() => history.pushState(null, "", "/app/dashboard"));
expect(result.current[0]).toBe("/dashboard");
act(() => history.pushState(null, "", "/myAPP/users/JohnDoe"));
expect(result.current[0]).toBe("/users/JohnDoe");
unmount();
});

it("does not modify original location in case of base path", () => {
it("returns an absolute path in case of unmatched base path", () => {
const { result, unmount } = renderHook(() =>
useLocation({ base: "/MyApp" })
);

act(() => history.pushState(null, "", "/myAPP/users/JohnDoe"));
expect(result.current[0]).toBe("/users/JohnDoe");
act(() => history.pushState(null, "", "/MyOtherApp/users/JohnDoe"));
expect(result.current[0]).toBe("~/MyOtherApp/users/JohnDoe");
unmount();
});
});
Expand Down
5 changes: 3 additions & 2 deletions use-location.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export default ({ base = "" } = {}) => {
history[replace ? eventReplaceState : eventPushState](
null,
"",
base + to
// handle nested routers and absolute paths
to[0] === "~" ? to.slice(1) : base + to
),
[base]
);
Expand Down Expand Up @@ -73,4 +74,4 @@ if (typeof history !== "undefined") {
const currentPathname = (base, path = location.pathname) =>
!path.toLowerCase().indexOf(base.toLowerCase())
? path.slice(base.length) || "/"
: path;
: "~" + path;

0 comments on commit 864a910

Please sign in to comment.