diff --git a/packages/history/__tests__/TestSequences/PushURL.js b/packages/history/__tests__/TestSequences/PushURL.js new file mode 100644 index 00000000..0f02f0f5 --- /dev/null +++ b/packages/history/__tests__/TestSequences/PushURL.js @@ -0,0 +1,26 @@ +import expect from "expect"; + +import { execSteps } from "./utils.js"; + +export default (history, done) => { + let steps = [ + ({ location }) => { + expect(location).toMatchObject({ + pathname: "/", + }); + + const nextURL = new URL("/home?the=query#the-hash", new URL('https://example.com')); + history.push(nextURL); + }, + ({ action, location }) => { + expect(action).toBe("PUSH"); + expect(location).toMatchObject({ + pathname: "/home", + search: "?the=query", + hash: "#the-hash", + }); + }, + ]; + + execSteps(steps, history, done); +}; diff --git a/packages/history/__tests__/TestSequences/ReplaceURL.js b/packages/history/__tests__/TestSequences/ReplaceURL.js new file mode 100644 index 00000000..bfc4b749 --- /dev/null +++ b/packages/history/__tests__/TestSequences/ReplaceURL.js @@ -0,0 +1,26 @@ +import expect from "expect"; + +import { execSteps } from "./utils.js"; + +export default (history, done) => { + let steps = [ + ({ location }) => { + expect(location).toMatchObject({ + pathname: "/", + }); + + const nextURL = new URL("/home?the=query#the-hash", new URL('https://example.com')); + history.replace(nextURL); + }, + ({ action, location }) => { + expect(action).toBe("REPLACE"); + expect(location).toMatchObject({ + pathname: "/home", + search: "?the=query", + hash: "#the-hash", + }); + }, + ]; + + execSteps(steps, history, done); +}; diff --git a/packages/history/__tests__/browser-test.js b/packages/history/__tests__/browser-test.js index 983b9a8c..50a3e802 100644 --- a/packages/history/__tests__/browser-test.js +++ b/packages/history/__tests__/browser-test.js @@ -6,11 +6,13 @@ import Listen from "./TestSequences/Listen.js"; import PushNewLocation from "./TestSequences/PushNewLocation.js"; import PushSamePath from "./TestSequences/PushSamePath.js"; import PushState from "./TestSequences/PushState.js"; +import PushURL from "./TestSequences/PushURL.js"; import PushMissingPathname from "./TestSequences/PushMissingPathname.js"; import PushRelativePathname from "./TestSequences/PushRelativePathname.js"; import ReplaceNewLocation from "./TestSequences/ReplaceNewLocation.js"; import ReplaceSamePath from "./TestSequences/ReplaceSamePath.js"; import ReplaceState from "./TestSequences/ReplaceState.js"; +import ReplaceURL from "./TestSequences/ReplaceURL.js"; import EncodedReservedCharacters from "./TestSequences/EncodedReservedCharacters.js"; import GoBack from "./TestSequences/GoBack.js"; import GoForward from "./TestSequences/GoForward.js"; @@ -81,6 +83,12 @@ describe("a browser history", () => { }); }); + describe("push URL", () => { + it("calls change listeners with the new location", (done) => { + PushURL(history, done); + }); + }); + describe("push with no pathname", () => { it("reuses the current location pathname", (done) => { PushMissingPathname(history, done); @@ -111,6 +119,12 @@ describe("a browser history", () => { }); }); + describe("replace URL", () => { + it("calls change listeners with the new location", (done) => { + ReplaceURL(history, done); + }); + }); + describe("location created with encoded/unencoded reserved characters", () => { it("produces different location objects", (done) => { EncodedReservedCharacters(history, done); diff --git a/packages/history/__tests__/hash-test.js b/packages/history/__tests__/hash-test.js index ce74f87b..b5e800c6 100644 --- a/packages/history/__tests__/hash-test.js +++ b/packages/history/__tests__/hash-test.js @@ -6,11 +6,13 @@ import InitialLocationDefaultKey from "./TestSequences/InitialLocationDefaultKey import PushNewLocation from "./TestSequences/PushNewLocation.js"; import PushSamePath from "./TestSequences/PushSamePath.js"; import PushState from "./TestSequences/PushState.js"; +import PushURL from "./TestSequences/PushURL.js"; import PushMissingPathname from "./TestSequences/PushMissingPathname.js"; import PushRelativePathnameWarning from "./TestSequences/PushRelativePathnameWarning.js"; import ReplaceNewLocation from "./TestSequences/ReplaceNewLocation.js"; import ReplaceSamePath from "./TestSequences/ReplaceSamePath.js"; import ReplaceState from "./TestSequences/ReplaceState.js"; +import ReplaceURL from "./TestSequences/ReplaceURL.js"; import EncodedReservedCharacters from "./TestSequences/EncodedReservedCharacters.js"; import GoBack from "./TestSequences/GoBack.js"; import GoForward from "./TestSequences/GoForward.js"; @@ -85,6 +87,12 @@ describe("a hash history", () => { }); }); + describe("push URL", () => { + it("calls change listeners with the new location", (done) => { + PushURL(history, done); + }); + }); + describe("push with no pathname", () => { it("reuses the current location pathname", (done) => { PushMissingPathname(history, done); @@ -115,6 +123,12 @@ describe("a hash history", () => { }); }); + describe("replace URL", () => { + it("calls change listeners with the new location", (done) => { + ReplaceURL(history, done); + }); + }); + describe("location created with encoded/unencoded reserved characters", () => { it("produces different location objects", (done) => { EncodedReservedCharacters(history, done); diff --git a/packages/history/__tests__/memory-test.js b/packages/history/__tests__/memory-test.js index 906c68a4..ff0f0c84 100644 --- a/packages/history/__tests__/memory-test.js +++ b/packages/history/__tests__/memory-test.js @@ -6,11 +6,13 @@ import InitialLocationHasKey from "./TestSequences/InitialLocationHasKey.js"; import PushNewLocation from "./TestSequences/PushNewLocation.js"; import PushSamePath from "./TestSequences/PushSamePath.js"; import PushState from "./TestSequences/PushState.js"; +import PushURL from "./TestSequences/PushURL.js"; import PushMissingPathname from "./TestSequences/PushMissingPathname.js"; import PushRelativePathnameWarning from "./TestSequences/PushRelativePathnameWarning.js"; import ReplaceNewLocation from "./TestSequences/ReplaceNewLocation.js"; import ReplaceSamePath from "./TestSequences/ReplaceSamePath.js"; import ReplaceState from "./TestSequences/ReplaceState.js"; +import ReplaceURL from "./TestSequences/ReplaceURL.js"; import EncodedReservedCharacters from "./TestSequences/EncodedReservedCharacters.js"; import GoBack from "./TestSequences/GoBack.js"; import GoForward from "./TestSequences/GoForward.js"; @@ -84,6 +86,12 @@ describe("a memory history", () => { }); }); + describe("push URL", () => { + it("calls change listeners with the new location", (done) => { + PushURL(history, done); + }); + }); + describe("push with no pathname", () => { it("reuses the current location pathname", (done) => { PushMissingPathname(history, done); @@ -114,6 +122,12 @@ describe("a memory history", () => { }); }); + describe("replace URL", () => { + it("calls change listeners with the new location", (done) => { + ReplaceURL(history, done); + }); + }); + describe("location created with encoded/unencoded reserved characters", () => { it("produces different location objects", (done) => { EncodedReservedCharacters(history, done); diff --git a/packages/history/index.ts b/packages/history/index.ts index c8cdfc04..86925c56 100644 --- a/packages/history/index.ts +++ b/packages/history/index.ts @@ -443,18 +443,6 @@ export function createBrowserHistory( return typeof to === "string" ? to : createPath(to); } - // state defaults to `null` because `window.history.state` does - function getNextLocation(to: To, state: any = null): Location { - return readOnly({ - pathname: location.pathname, - hash: "", - search: "", - ...(typeof to === "string" ? parsePath(to) : to), - state, - key: createKey(), - }); - } - function getHistoryStateAndUrl( nextLocation: Location, index: number @@ -483,7 +471,7 @@ export function createBrowserHistory( function push(to: To, state?: any) { let nextAction = Action.Push; - let nextLocation = getNextLocation(to, state); + let nextLocation = getNextLocation(location, to, state); function retry() { push(to, state); } @@ -507,7 +495,7 @@ export function createBrowserHistory( function replace(to: To, state?: any) { let nextAction = Action.Replace; - let nextLocation = getNextLocation(to, state); + let nextLocation = getNextLocation(location, to, state); function retry() { replace(to, state); } @@ -693,17 +681,6 @@ export function createHashHistory( return getBaseHref() + "#" + (typeof to === "string" ? to : createPath(to)); } - function getNextLocation(to: To, state: any = null): Location { - return readOnly({ - pathname: location.pathname, - hash: "", - search: "", - ...(typeof to === "string" ? parsePath(to) : to), - state, - key: createKey(), - }); - } - function getHistoryStateAndUrl( nextLocation: Location, index: number @@ -732,7 +709,7 @@ export function createHashHistory( function push(to: To, state?: any) { let nextAction = Action.Push; - let nextLocation = getNextLocation(to, state); + let nextLocation = getNextLocation(location, to, state); function retry() { push(to, state); } @@ -763,7 +740,7 @@ export function createHashHistory( function replace(to: To, state?: any) { let nextAction = Action.Replace; - let nextLocation = getNextLocation(to, state); + let nextLocation = getNextLocation(location, to, state); function retry() { replace(to, state); } @@ -891,17 +868,6 @@ export function createMemoryHistory( return typeof to === "string" ? to : createPath(to); } - function getNextLocation(to: To, state: any = null): Location { - return readOnly({ - pathname: location.pathname, - search: "", - hash: "", - ...(typeof to === "string" ? parsePath(to) : to), - state, - key: createKey(), - }); - } - function allowTx(action: Action, location: Location, retry: () => void) { return ( !blockers.length || (blockers.call({ action, location, retry }), false) @@ -916,7 +882,7 @@ export function createMemoryHistory( function push(to: To, state?: any) { let nextAction = Action.Push; - let nextLocation = getNextLocation(to, state); + let nextLocation = getNextLocation(location, to, state); function retry() { push(to, state); } @@ -937,7 +903,7 @@ export function createMemoryHistory( function replace(to: To, state?: any) { let nextAction = Action.Replace; - let nextLocation = getNextLocation(to, state); + let nextLocation = getNextLocation(location, to, state); function retry() { replace(to, state); } @@ -1089,3 +1055,34 @@ export function parsePath(path: string): Partial { return parsedPath; } + +/** + * Constructs new location object. + * + * @param from The current location. + * @param to + * @param [state=null] Defaults to `null` because `window.history.state` does. + */ + function getNextLocation(from: Location, to: To, state: any = null): Location { + let normalizedTo; + if ( typeof to === "string" ) { + normalizedTo = parsePath(to); + } else if (to instanceof URL) { + normalizedTo = { + pathname: to.pathname, + search: to.search, + hash: to.hash + } + } else { + normalizedTo = to; + } + + return readOnly({ + pathname: from.pathname, + hash: "", + search: "", + ...normalizedTo, + state, + key: createKey(), + }); +}