fix(nav): Back onto a page this build no longer has lands on the feed
A history entry pushed while the Messages page existed still names it after a reload onto the new build; popstate took sfPage unchecked and the shell's module lookup threw. It is now validated with isPage, like every initial-load path already was.
This commit is contained in:
@@ -176,6 +176,19 @@ describe("NavigationProvider · setPage's same-page guard", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("NavigationProvider · Back", () => {
|
||||||
|
// A tab that had a since-removed module open, reloaded onto the new build: its older history
|
||||||
|
// entries still name that page, and Back lands on one of them.
|
||||||
|
it("lands on the feed when the entry names a page this build does not have", () => {
|
||||||
|
const { result } = render();
|
||||||
|
act(() => result.current.setPage("plex"));
|
||||||
|
act(() => {
|
||||||
|
window.dispatchEvent(new PopStateEvent("popstate", { state: { sfPage: "messages" } }));
|
||||||
|
});
|
||||||
|
expect(result.current.page).toBe("feed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("NavigationProvider · never under an open overlay", () => {
|
describe("NavigationProvider · never under an open overlay", () => {
|
||||||
it("setPage closes the overlay first and pushes only once its entry is reclaimed", async () => {
|
it("setPage closes the overlay first and pushes only once its entry is reclaimed", async () => {
|
||||||
const { result } = render();
|
const { result } = render();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react
|
|||||||
import { useLatest } from "../lib/useLatest";
|
import { useLatest } from "../lib/useLatest";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useMe } from "../lib/useMe";
|
import { useMe } from "../lib/useMe";
|
||||||
import { urlWithParams, type Page } from "../lib/urlState";
|
import { isPage, urlWithParams, type Page } from "../lib/urlState";
|
||||||
import { LOGS_KEYS } from "../lib/logsUrlState";
|
import { LOGS_KEYS } from "../lib/logsUrlState";
|
||||||
import { pageTitleKey } from "../lib/pageMeta";
|
import { pageTitleKey } from "../lib/pageMeta";
|
||||||
import { useDocumentObjectTitle } from "../lib/documentTitle";
|
import { useDocumentObjectTitle } from "../lib/documentTitle";
|
||||||
@@ -197,7 +197,10 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
|||||||
window.history.replaceState({ ...rest, sfPage: mountPage }, "", url);
|
window.history.replaceState({ ...rest, sfPage: mountPage }, "", url);
|
||||||
}
|
}
|
||||||
function onPop(e: PopStateEvent) {
|
function onPop(e: PopStateEvent) {
|
||||||
const p = (e.state?.sfPage as Page) ?? "feed";
|
// An entry can name a page this build no longer has (one pushed before a module was removed,
|
||||||
|
// reached by Back after a reload onto the new build); that lands on the feed, not on a crash.
|
||||||
|
const sf: unknown = e.state?.sfPage;
|
||||||
|
const p: Page = typeof sf === "string" && isPage(sf) ? sf : "feed";
|
||||||
setPageState(p);
|
setPageState(p);
|
||||||
setAccountRaw(PAGE_KEY, p);
|
setAccountRaw(PAGE_KEY, p);
|
||||||
// The YouTube-search sub-view and the channel page ride in history.state, so Back/Forward
|
// The YouTube-search sub-view and the channel page ride in history.state, so Back/Forward
|
||||||
|
|||||||
Reference in New Issue
Block a user