Skip to content

Commit

Permalink
fix: treat absolute/same-origin/different-basename <Link to> values a…
Browse files Browse the repository at this point in the history
…s external (#10135)
  • Loading branch information
brophdawg11 authored Feb 23, 2023
1 parent 1d2417b commit d43e1ab
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/big-olives-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router-dom": patch
---

Treat absolute/same-origin/different-basename <Link to> values as external
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"none": "15 kB"
},
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
"none": "11.5 kB"
"none": "11.6 kB"
},
"packages/react-router-dom/dist/umd/react-router-dom.production.min.js": {
"none": "17.5 kB"
Expand Down
73 changes: 72 additions & 1 deletion packages/react-router-dom/__tests__/link-click-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ describe("A <Link> click", () => {
<div>
<h1>Home</h1>
<Link
reloadDocument
to="https://remix.run"
onClick={(e) => {
handlerCalled = true;
Expand Down Expand Up @@ -171,6 +170,78 @@ describe("A <Link> click", () => {
});
});

describe("when a same-origin/different-basename absolute URL is specified", () => {
it("does not prevent default", () => {
function Home() {
return (
<div>
<h1>Home</h1>
<Link to="http://localhost/not/base">About</Link>
</div>
);
}

act(() => {
ReactDOM.createRoot(node).render(
<MemoryRouter initialEntries={["/base/home"]} basename="/base">
<Routes>
<Route path="home" element={<Home />} />
</Routes>
</MemoryRouter>
);
});

let anchor = node.querySelector("a");
expect(anchor).not.toBeNull();

let event: MouseEvent;
act(() => {
event = click(anchor);
});

expect(event.defaultPrevented).toBe(false);
});

it("calls provided listener", () => {
let handlerCalled;
let defaultPrevented;

function Home() {
return (
<div>
<h1>Home</h1>
<Link
to="http://localhost/not/base"
onClick={(e) => {
handlerCalled = true;
defaultPrevented = e.defaultPrevented;
}}
>
About
</Link>
</div>
);
}

act(() => {
ReactDOM.createRoot(node).render(
<MemoryRouter initialEntries={["/base/home"]} basename="/base">
<Routes>
<Route path="home" element={<Home />} />
</Routes>
</MemoryRouter>
);
});

act(() => {
click(node.querySelector("a"));
});

expect(handlerCalled).toBe(true);
expect(defaultPrevented).toBe(false);
});
});

describe("when reloadDocument is specified", () => {
it("does not prevent default", () => {
function Home() {
Expand Down
43 changes: 43 additions & 0 deletions packages/react-router-dom/__tests__/link-push-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,47 @@ describe("Link push and replace", () => {
`);
});
});

describe("to an absolute same-origin/same-basename URL, when it is clicked", () => {
it("performs a push", () => {
function Home() {
return (
<div>
<h1>Home</h1>
<Link to="http://localhost/base/about">About</Link>
</div>
);
}

let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/base/home"]} basename="/base">
<Routes>
<Route path="home" element={<Home />} />
<Route path="about" element={<ShowNavigationType />} />
</Routes>
</MemoryRouter>
);
});

let anchor = renderer.root.findByType("a");

TestRenderer.act(() => {
anchor.props.onClick(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
})
);
});

expect(renderer.toJSON()).toMatchInlineSnapshot(`
<p>
PUSH
</p>
`);
});
});
});
11 changes: 8 additions & 3 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
createHashHistory,
UNSAFE_invariant as invariant,
joinPaths,
stripBasename,
ErrorResponse,
} from "@remix-run/router";

Expand Down Expand Up @@ -420,6 +421,8 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
},
ref
) {
let { basename } = React.useContext(NavigationContext);

// Rendered into <a href> for absolute URLs
let absoluteHref;
let isExternal = false;
Expand All @@ -434,9 +437,11 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
let targetUrl = to.startsWith("//")
? new URL(currentUrl.protocol + to)
: new URL(to);
if (targetUrl.origin === currentUrl.origin) {
// Strip the protocol/origin for same-origin absolute URLs
to = targetUrl.pathname + targetUrl.search + targetUrl.hash;
let path = stripBasename(targetUrl.pathname, basename);

if (targetUrl.origin === currentUrl.origin && path != null) {
// Strip the protocol/origin/basename for same-origin absolute URLs
to = path + targetUrl.search + targetUrl.hash;
} else {
isExternal = true;
}
Expand Down

0 comments on commit d43e1ab

Please sign in to comment.