From ecf16444aa278358050c2a1aad3c3176974b9624 Mon Sep 17 00:00:00 2001 From: Evgeny Vlasenko Date: Tue, 2 Aug 2022 19:24:48 +0400 Subject: [PATCH] feat: Add index property to BrowserHistory, HashHistory and Update #957 --- .../TestSequences/BackButtonTransitionHook.js | 9 ++- .../TestSequences/BlockEverything.js | 4 +- .../history/__tests__/TestSequences/GoBack.js | 9 ++- .../__tests__/TestSequences/GoForward.js | 12 ++-- .../TestSequences/PushMissingPathname.js | 9 ++- .../TestSequences/PushNewLocation.js | 6 +- .../TestSequences/PushRelativePathname.js | 9 ++- .../PushRelativePathnameWarning.js | 9 ++- .../__tests__/TestSequences/PushSamePath.js | 12 ++-- .../__tests__/TestSequences/PushState.js | 6 +- .../TestSequences/ReplaceNewLocation.js | 9 ++- .../TestSequences/ReplaceSamePath.js | 9 ++- .../__tests__/TestSequences/ReplaceState.js | 6 +- .../history/__tests__/TestSequences/utils.js | 1 + packages/history/__tests__/browser-test.js | 4 ++ packages/history/__tests__/hash-test.js | 4 ++ packages/history/index.ts | 71 ++++++++++++++----- 17 files changed, 134 insertions(+), 55 deletions(-) diff --git a/packages/history/__tests__/TestSequences/BackButtonTransitionHook.js b/packages/history/__tests__/TestSequences/BackButtonTransitionHook.js index 4d7c16c3..3e743e10 100644 --- a/packages/history/__tests__/TestSequences/BackButtonTransitionHook.js +++ b/packages/history/__tests__/TestSequences/BackButtonTransitionHook.js @@ -7,14 +7,16 @@ export default (history, done) => { let unblock; let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/home"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/home", @@ -26,7 +28,8 @@ export default (history, done) => { window.history.go(-1); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toBe("POP"); expect(location).toMatchObject({ pathname: "/", diff --git a/packages/history/__tests__/TestSequences/BlockEverything.js b/packages/history/__tests__/TestSequences/BlockEverything.js index 8b54c195..3f8e1ab2 100644 --- a/packages/history/__tests__/TestSequences/BlockEverything.js +++ b/packages/history/__tests__/TestSequences/BlockEverything.js @@ -4,7 +4,8 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); @@ -13,6 +14,7 @@ export default (history, done) => { history.push("/home"); + expect(history.index).toBe(0); expect(history.location).toMatchObject({ pathname: "/", }); diff --git a/packages/history/__tests__/TestSequences/GoBack.js b/packages/history/__tests__/TestSequences/GoBack.js index 8e65c3ab..34dbee8f 100644 --- a/packages/history/__tests__/TestSequences/GoBack.js +++ b/packages/history/__tests__/TestSequences/GoBack.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/home"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toEqual("PUSH"); expect(location).toMatchObject({ pathname: "/home", @@ -19,7 +21,8 @@ export default (history, done) => { history.back(); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toEqual("POP"); expect(location).toMatchObject({ pathname: "/", diff --git a/packages/history/__tests__/TestSequences/GoForward.js b/packages/history/__tests__/TestSequences/GoForward.js index 9f6971ee..df0e044c 100644 --- a/packages/history/__tests__/TestSequences/GoForward.js +++ b/packages/history/__tests__/TestSequences/GoForward.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/home"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toEqual("PUSH"); expect(location).toMatchObject({ pathname: "/home", @@ -19,7 +21,8 @@ export default (history, done) => { history.back(); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toEqual("POP"); expect(location).toMatchObject({ pathname: "/", @@ -27,7 +30,8 @@ export default (history, done) => { history.forward(); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toEqual("POP"); expect(location).toMatchObject({ pathname: "/home", diff --git a/packages/history/__tests__/TestSequences/PushMissingPathname.js b/packages/history/__tests__/TestSequences/PushMissingPathname.js index ef3b8e37..b1a8d8e0 100644 --- a/packages/history/__tests__/TestSequences/PushMissingPathname.js +++ b/packages/history/__tests__/TestSequences/PushMissingPathname.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/home?the=query#the-hash"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/home", @@ -21,7 +23,8 @@ export default (history, done) => { history.push("?another=query#another-hash"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(2); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/home", diff --git a/packages/history/__tests__/TestSequences/PushNewLocation.js b/packages/history/__tests__/TestSequences/PushNewLocation.js index cc451672..37badac1 100644 --- a/packages/history/__tests__/TestSequences/PushNewLocation.js +++ b/packages/history/__tests__/TestSequences/PushNewLocation.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/home?the=query#the-hash"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/home", diff --git a/packages/history/__tests__/TestSequences/PushRelativePathname.js b/packages/history/__tests__/TestSequences/PushRelativePathname.js index 14684713..e4e51cb1 100644 --- a/packages/history/__tests__/TestSequences/PushRelativePathname.js +++ b/packages/history/__tests__/TestSequences/PushRelativePathname.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/the/path?the=query#the-hash"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/the/path", @@ -21,7 +23,8 @@ export default (history, done) => { history.push("../other/path?another=query#another-hash"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(2); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/other/path", diff --git a/packages/history/__tests__/TestSequences/PushRelativePathnameWarning.js b/packages/history/__tests__/TestSequences/PushRelativePathnameWarning.js index 65b27b25..78259ae2 100644 --- a/packages/history/__tests__/TestSequences/PushRelativePathnameWarning.js +++ b/packages/history/__tests__/TestSequences/PushRelativePathnameWarning.js @@ -4,14 +4,16 @@ import { execSteps, spyOn } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/the/path?the=query#the-hash"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/the/path", @@ -29,7 +31,8 @@ export default (history, done) => { destroy(); }, - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(2); expect(location).toMatchObject({ pathname: "../other/path", search: "?another=query", diff --git a/packages/history/__tests__/TestSequences/PushSamePath.js b/packages/history/__tests__/TestSequences/PushSamePath.js index 3dc705f8..a89536a2 100644 --- a/packages/history/__tests__/TestSequences/PushSamePath.js +++ b/packages/history/__tests__/TestSequences/PushSamePath.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/home"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/home", @@ -19,7 +21,8 @@ export default (history, done) => { history.push("/home"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(2); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/home", @@ -27,7 +30,8 @@ export default (history, done) => { history.back(); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("POP"); expect(location).toMatchObject({ pathname: "/home", diff --git a/packages/history/__tests__/TestSequences/PushState.js b/packages/history/__tests__/TestSequences/PushState.js index 36d462c2..3ec29a59 100644 --- a/packages/history/__tests__/TestSequences/PushState.js +++ b/packages/history/__tests__/TestSequences/PushState.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.push("/home?the=query#the-hash", { the: "state" }); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(1); expect(action).toBe("PUSH"); expect(location).toMatchObject({ pathname: "/home", diff --git a/packages/history/__tests__/TestSequences/ReplaceNewLocation.js b/packages/history/__tests__/TestSequences/ReplaceNewLocation.js index 0f8e8653..e117d8ff 100644 --- a/packages/history/__tests__/TestSequences/ReplaceNewLocation.js +++ b/packages/history/__tests__/TestSequences/ReplaceNewLocation.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.replace("/home?the=query#the-hash"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toBe("REPLACE"); expect(location).toMatchObject({ pathname: "/home", @@ -23,7 +25,8 @@ export default (history, done) => { history.replace("/"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toBe("REPLACE"); expect(location).toMatchObject({ pathname: "/", diff --git a/packages/history/__tests__/TestSequences/ReplaceSamePath.js b/packages/history/__tests__/TestSequences/ReplaceSamePath.js index 7d8c46a2..a37731f0 100644 --- a/packages/history/__tests__/TestSequences/ReplaceSamePath.js +++ b/packages/history/__tests__/TestSequences/ReplaceSamePath.js @@ -6,14 +6,16 @@ export default (history, done) => { let prevLocation; let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.replace("/home"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toBe("REPLACE"); expect(location).toMatchObject({ pathname: "/home", @@ -23,7 +25,8 @@ export default (history, done) => { history.replace("/home"); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toBe("REPLACE"); expect(location).toMatchObject({ pathname: "/home", diff --git a/packages/history/__tests__/TestSequences/ReplaceState.js b/packages/history/__tests__/TestSequences/ReplaceState.js index f8fbc228..8c5ea9df 100644 --- a/packages/history/__tests__/TestSequences/ReplaceState.js +++ b/packages/history/__tests__/TestSequences/ReplaceState.js @@ -4,14 +4,16 @@ import { execSteps } from "./utils.js"; export default (history, done) => { let steps = [ - ({ location }) => { + ({ index, location }) => { + expect(index).toBe(0); expect(location).toMatchObject({ pathname: "/", }); history.replace("/home?the=query#the-hash", { the: "state" }); }, - ({ action, location }) => { + ({ index, action, location }) => { + expect(index).toBe(0); expect(action).toBe("REPLACE"); expect(location).toMatchObject({ pathname: "/home", diff --git a/packages/history/__tests__/TestSequences/utils.js b/packages/history/__tests__/TestSequences/utils.js index 94ddaa1e..edf1f62a 100644 --- a/packages/history/__tests__/TestSequences/utils.js +++ b/packages/history/__tests__/TestSequences/utils.js @@ -44,6 +44,7 @@ export function execSteps(steps, history, done) { unlisten = history.listen(execNextStep); execNextStep({ + index: history.index, action: history.action, location: history.location, }); diff --git a/packages/history/__tests__/browser-test.js b/packages/history/__tests__/browser-test.js index 983b9a8c..27ab8cd7 100644 --- a/packages/history/__tests__/browser-test.js +++ b/packages/history/__tests__/browser-test.js @@ -24,6 +24,10 @@ describe("a browser history", () => { history = createBrowserHistory(); }); + it("has an index property", () => { + expect(typeof history.index).toBe("number"); + }); + it("knows how to create hrefs from location objects", () => { const href = history.createHref({ pathname: "/the/path", diff --git a/packages/history/__tests__/hash-test.js b/packages/history/__tests__/hash-test.js index ce74f87b..9da47c96 100644 --- a/packages/history/__tests__/hash-test.js +++ b/packages/history/__tests__/hash-test.js @@ -28,6 +28,10 @@ describe("a hash history", () => { history = createHashHistory(); }); + it("has an index property", () => { + expect(typeof history.index).toBe("number"); + }); + it("knows how to create hrefs from location objects", () => { const href = history.createHref({ pathname: "/the/path", diff --git a/packages/history/index.ts b/packages/history/index.ts index c8cdfc04..c156b0fa 100644 --- a/packages/history/index.ts +++ b/packages/history/index.ts @@ -134,6 +134,10 @@ export type PartialLocation = Partial; * A change to the current location. */ export interface Update { + /** + * The new index. + */ + index: number; /** * The action that triggered the change. */ @@ -186,6 +190,11 @@ export type To = string | Partial; * focused API. */ export interface History { + /** + * The current index. This value is mutable. + */ + readonly index: number; + /** * The last action that modified the current location. This will always be * Action.Pop when a history instance is first created. This value is mutable. @@ -313,9 +322,7 @@ export interface HashHistory extends History {} * * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#memoryhistory */ -export interface MemoryHistory extends History { - readonly index: number; -} +export interface MemoryHistory extends History {} const readOnly: (obj: T) => Readonly = __DEV__ ? (obj) => Object.freeze(obj) @@ -397,6 +404,7 @@ export function createBrowserHistory( if (delta) { // Revert the POP blockedPopTx = { + index: nextIndex, action: nextAction, location: nextLocation, retry() { @@ -469,16 +477,22 @@ export function createBrowserHistory( ]; } - function allowTx(action: Action, location: Location, retry: () => void) { + function allowTx( + index: number, + action: Action, + location: Location, + retry: () => void + ) { return ( - !blockers.length || (blockers.call({ action, location, retry }), false) + !blockers.length || + (blockers.call({ index, action, location, retry }), false) ); } function applyTx(nextAction: Action) { action = nextAction; [index, location] = getIndexAndLocation(); - listeners.call({ action, location }); + listeners.call({ index, action, location }); } function push(to: To, state?: any) { @@ -488,7 +502,7 @@ export function createBrowserHistory( push(to, state); } - if (allowTx(nextAction, nextLocation, retry)) { + if (allowTx(index + 1, nextAction, nextLocation, retry)) { let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1); // TODO: Support forced reloading @@ -512,7 +526,7 @@ export function createBrowserHistory( replace(to, state); } - if (allowTx(nextAction, nextLocation, retry)) { + if (allowTx(index, nextAction, nextLocation, retry)) { let [historyState, url] = getHistoryStateAndUrl(nextLocation, index); // TODO: Support forced reloading @@ -527,6 +541,9 @@ export function createBrowserHistory( } let history: BrowserHistory = { + get index() { + return index; + }, get action() { return action; }, @@ -623,6 +640,7 @@ export function createHashHistory( if (delta) { // Revert the POP blockedPopTx = { + index: nextIndex, action: nextAction, location: nextLocation, retry() { @@ -718,16 +736,22 @@ export function createHashHistory( ]; } - function allowTx(action: Action, location: Location, retry: () => void) { + function allowTx( + index: number, + action: Action, + location: Location, + retry: () => void + ) { return ( - !blockers.length || (blockers.call({ action, location, retry }), false) + !blockers.length || + (blockers.call({ index, action, location, retry }), false) ); } function applyTx(nextAction: Action) { action = nextAction; [index, location] = getIndexAndLocation(); - listeners.call({ action, location }); + listeners.call({ index, action, location }); } function push(to: To, state?: any) { @@ -744,7 +768,7 @@ export function createHashHistory( )})` ); - if (allowTx(nextAction, nextLocation, retry)) { + if (allowTx(index + 1, nextAction, nextLocation, retry)) { let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1); // TODO: Support forced reloading @@ -775,7 +799,7 @@ export function createHashHistory( )})` ); - if (allowTx(nextAction, nextLocation, retry)) { + if (allowTx(index, nextAction, nextLocation, retry)) { let [historyState, url] = getHistoryStateAndUrl(nextLocation, index); // TODO: Support forced reloading @@ -790,6 +814,9 @@ export function createHashHistory( } let history: HashHistory = { + get index() { + return index; + }, get action() { return action; }, @@ -902,16 +929,22 @@ export function createMemoryHistory( }); } - function allowTx(action: Action, location: Location, retry: () => void) { + function allowTx( + index: number, + action: Action, + location: Location, + retry: () => void + ) { return ( - !blockers.length || (blockers.call({ action, location, retry }), false) + !blockers.length || + (blockers.call({ index, action, location, retry }), false) ); } function applyTx(nextAction: Action, nextLocation: Location) { action = nextAction; location = nextLocation; - listeners.call({ action, location }); + listeners.call({ index, action, location }); } function push(to: To, state?: any) { @@ -928,7 +961,7 @@ export function createMemoryHistory( )})` ); - if (allowTx(nextAction, nextLocation, retry)) { + if (allowTx(index + 1, nextAction, nextLocation, retry)) { index += 1; entries.splice(index, entries.length, nextLocation); applyTx(nextAction, nextLocation); @@ -949,7 +982,7 @@ export function createMemoryHistory( )})` ); - if (allowTx(nextAction, nextLocation, retry)) { + if (allowTx(index, nextAction, nextLocation, retry)) { entries[index] = nextLocation; applyTx(nextAction, nextLocation); } @@ -963,7 +996,7 @@ export function createMemoryHistory( go(delta); } - if (allowTx(nextAction, nextLocation, retry)) { + if (allowTx(nextIndex, nextAction, nextLocation, retry)) { index = nextIndex; applyTx(nextAction, nextLocation); }