-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Breaking: Add spies and proxy for window.history to track its updates…
… to the original window.location
- Loading branch information
1 parent
8ef5de8
commit 628a60d
Showing
12 changed files
with
2,663 additions
and
1,202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,10 @@ | ||
// Default setup side-effects | ||
import "../src"; | ||
|
||
// Enable fetch mock for use in React Router tests | ||
// - Reference: https://reactrouter.com/en/main/routers/picking-a-router#testing | ||
// - `Request` is undefined without this, even in Node.js v20 | ||
import "whatwg-fetch"; | ||
|
||
// Add matchers from Testing Library | ||
import "@testing-library/jest-dom"; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import React from "react"; | ||
import {screen, render, act} from "@testing-library/react"; | ||
import { | ||
BrowserRouter, | ||
createBrowserRouter, | ||
MemoryRouter, | ||
RouterProvider, | ||
Routes, | ||
Route, | ||
Link, | ||
} from "react-router-dom"; | ||
|
||
describe("react-router-dom", () => { | ||
describe("createBrowserRouter() and <RouterProvider>", () => { | ||
it("should change routes alongside window.location", () => { | ||
const router = createBrowserRouter([ | ||
{ | ||
path: "/", | ||
element: <Link to="/e8ab27c6-7e83-4fa8-a52b-b3bab5023ff0">Link from Root to Page</Link>, | ||
}, | ||
{ | ||
path: "/e8ab27c6-7e83-4fa8-a52b-b3bab5023ff0", | ||
element: <Link to="/">Link from Page to Root</Link>, | ||
}, | ||
]); | ||
render(<RouterProvider router={router} />); | ||
act(() => { | ||
screen.getByText("Link from Root to Page").click(); | ||
}); | ||
expect(window.location).toBeAt("/e8ab27c6-7e83-4fa8-a52b-b3bab5023ff0"); | ||
expect(screen.getByText("Link from Page to Root")).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe("<BrowserRouter>", () => { | ||
it("should change routes alongside window.location", () => { | ||
render( | ||
<BrowserRouter> | ||
<Routes> | ||
<Route | ||
path="/" | ||
element={<Link to="/5c99ad6a-c1ed-4c6d-b4a9-9460a6da36f8">Link from Root to Page</Link>} | ||
/> | ||
<Route | ||
path="/5c99ad6a-c1ed-4c6d-b4a9-9460a6da36f8" | ||
element={<Link to="/">Link from Page to Root</Link>} | ||
/> | ||
</Routes> | ||
</BrowserRouter> | ||
); | ||
act(() => { | ||
screen.getByText("Link from Root to Page").click(); | ||
}); | ||
expect(window.location).toBeAt("/5c99ad6a-c1ed-4c6d-b4a9-9460a6da36f8"); | ||
expect(screen.getByText("Link from Page to Root")).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe("<MemoryRouter>", () => { | ||
it("should change routes, but window.location should not update", () => { | ||
render( | ||
<MemoryRouter> | ||
<Routes> | ||
<Route | ||
path="/" | ||
element={<Link to="/1494ea67-a401-43c8-b393-dc8c843f394b">Link from Root to Page</Link>} | ||
/> | ||
<Route | ||
path="/1494ea67-a401-43c8-b393-dc8c843f394b" | ||
element={<Link to="/">Link from Page to Root</Link>} | ||
/> | ||
</Routes> | ||
</MemoryRouter> | ||
); | ||
act(() => { | ||
screen.getByText("Link from Root to Page").click(); | ||
}); | ||
// MemoryRouter doesn't change the `window.location` | ||
// - This time I checked a clean environment without any mocks or other tests | ||
expect(window.location).toBeAt("/"); | ||
// The router should still behave like normal, even though it wouldn't change the location on the browser | ||
expect(screen.getByText("Link from Page to Root")).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
describe("window.history", () => { | ||
describe("replaceState()", () => { | ||
it("should update location mock", () => { | ||
expect(window.location).toBeAt("/"); | ||
window.history.replaceState(null, "", "/0e37bca8-b8e3-49ee-8ac6-57e50c2884d3"); | ||
expect(window.location).toBeAt("/0e37bca8-b8e3-49ee-8ac6-57e50c2884d3"); | ||
}); | ||
}); | ||
|
||
describe("pushState()", () => { | ||
it("should update location mock", () => { | ||
expect(window.location).toBeAt("/"); | ||
window.history.pushState(null, "", "/e8bf1e04-46e9-4f30-abcb-320412243ea2"); | ||
expect(window.location).toBeAt("/e8bf1e04-46e9-4f30-abcb-320412243ea2"); | ||
}); | ||
}); | ||
|
||
// Skipped as JSDOM doesn't handle history traversal well | ||
// - `back()` and `forward()` do not change the location in JSDOM, therefore we're not currently testing for | ||
// handling updating the mock | ||
// - This may change as the JSDOM improves or the potentially if the scope of the project expands to replacing | ||
// JSDOM's mocks with our own that are better suited to single page application testing | ||
// - Related: https://github.com/jsdom/jsdom/issues/1565 | ||
describe.skip("history", () => { | ||
describe("go()", () => { | ||
it("should update location mock", () => { | ||
expect(window.location).toBeAt("/"); | ||
window.history.pushState(null, "", "/04d333bd-4694-49be-aa25-d7e92515eabb"); | ||
expect(window.location).toBeAt("/04d333bd-4694-49be-aa25-d7e92515eabb"); | ||
window.history.pushState(null, "", "/ca1ed895-b590-421e-be9b-84ef2a5aa5fd"); | ||
expect(window.location).toBeAt("/ca1ed895-b590-421e-be9b-84ef2a5aa5fd"); | ||
window.history.go(-1); | ||
expect(window.location).toBeAt("/04d333bd-4694-49be-aa25-d7e92515eabb"); | ||
window.history.go(-1); | ||
expect(window.location).toBeAt("/"); | ||
window.history.go(2); | ||
expect(window.location).toBeAt("/ca1ed895-b590-421e-be9b-84ef2a5aa5fd"); | ||
}); | ||
}); | ||
describe("back() and forward()", () => { | ||
it("should update location mock", () => { | ||
expect(window.location).toBeAt("/"); | ||
window.history.pushState(null, "", "/9330c6b2-153d-411f-ac67-8adb74b614f0"); | ||
expect(window.location).toBeAt("/9330c6b2-153d-411f-ac67-8adb74b614f0"); | ||
window.history.back(); | ||
expect(window.location).toBeAt("/"); | ||
window.history.forward(); | ||
expect(window.location).toBeAt("/9330c6b2-153d-411f-ac67-8adb74b614f0"); | ||
// Forward cannot progress past the most recent state, so this should should do nothing compared to above | ||
window.history.forward(); | ||
expect(window.location).toBeAt("/9330c6b2-153d-411f-ac67-8adb74b614f0"); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** @jest-environment node */ | ||
import {replaceHistory} from "../replace-history"; | ||
|
||
describe("replaceHistory()", () => { | ||
describe("when window not defined", () => { | ||
it("should not change window", () => { | ||
// Should already be run by jest setup, but let's run again for test verbosity and to report any errors | ||
replaceHistory(); | ||
expect(typeof window).toBe("undefined"); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from "./replace-location"; | ||
export * from "./replace-history"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import {originalLocationRef} from "./replace-location"; | ||
|
||
export const replaceHistory = (): void => { | ||
// Do nothing if window is not defined | ||
// - Prevents an error when importing this mock in the setup file when some tests use the node test environment instead of JSDOM | ||
if (typeof window === "undefined") { | ||
return; | ||
} | ||
|
||
const proxiedHistory = new Proxy( | ||
window.history, | ||
{ | ||
get (target, property, receiver) { | ||
const realValue: unknown = Reflect.get(target, property, receiver); | ||
// If the property of window.history is a method, wrap it in a proxy to update the location mock | ||
if (realValue instanceof Function || jest.isMockFunction(realValue)) { | ||
return new Proxy( | ||
realValue, | ||
{ | ||
apply (...args) { | ||
Reflect.apply(...args); | ||
// Update the location mock if the location was updated | ||
if (originalLocationRef.current && window.location.href !== originalLocationRef.current.href) { | ||
window.location.href = originalLocationRef.current.href; | ||
} | ||
}, | ||
} | ||
); | ||
} | ||
return realValue; | ||
}, | ||
} | ||
); | ||
|
||
// Setup Jest spies on the methods for convenience and our matchers | ||
jest.spyOn(proxiedHistory, "replaceState").mockName("window.history.replaceState"); | ||
jest.spyOn(proxiedHistory, "pushState").mockName("window.history.pushState"); | ||
jest.spyOn(proxiedHistory, "go").mockName("window.history.go"); | ||
jest.spyOn(proxiedHistory, "back").mockName("window.history.back"); | ||
jest.spyOn(proxiedHistory, "forward").mockName("window.history.forward"); | ||
|
||
// Add the property to the Window | ||
Object.defineProperty( | ||
window, | ||
"history", | ||
{ | ||
set: undefined, | ||
get () { | ||
return proxiedHistory; | ||
}, | ||
configurable: true, | ||
enumerable: true, | ||
}, | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
import {replaceLocation} from "./hooks"; | ||
import {replaceHistory, replaceLocation} from "./hooks"; | ||
|
||
|
||
// Setup default hooks configuration | ||
beforeEach(replaceLocation); | ||
beforeEach(() => { | ||
replaceLocation(); | ||
replaceHistory(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters