test(e2e): a plain member's shell is held end to end, and a page it cannot open lands on the feed
The suite ran only as an admin, so nothing end to end held what a member sees. A second seeded account, a member with its own session, now walks every rail page (no admin entry, no admin call, no error state) and follows links to the admin pages. It found one: a member's link to an admin page showed the feed with no rail row current, and the address bar kept naming the admin page — only the content fell back. The navigation state now derives the page on screen through each module's gate (reachablePage), so the rail, the content, the tab title and the address bar agree, on a link, a Back, and a role taken away mid-session.
This commit is contained in:
@@ -79,7 +79,8 @@ async function openOverlay() {
|
||||
|
||||
beforeEach(() => {
|
||||
qc = new QueryClient();
|
||||
qc.setQueryData(qk.me(), { id: 1 });
|
||||
// An admin on a Plex-enabled instance: every module is open to it, so no gate moves a page here.
|
||||
qc.setQueryData(qk.me(), { id: 1, role: "admin", plex_enabled: true });
|
||||
localStorage.clear();
|
||||
sessionStorage.setItem("siftlode.activeAccount", "1"); // account-scoped page memory
|
||||
window.history.replaceState({ sfPage: "feed" }, "", "/");
|
||||
@@ -295,6 +296,43 @@ describe("NavigationProvider · Back", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("NavigationProvider · a page the account cannot open", () => {
|
||||
const member = { id: 1, role: "user", plex_enabled: true };
|
||||
|
||||
// A member following a link to an admin page saw the feed with no rail row current: the page
|
||||
// stayed "scheduler" while only the content fell back.
|
||||
it("lands on the feed, and the address bar says so", () => {
|
||||
qc.setQueryData(qk.me(), member);
|
||||
window.history.replaceState(null, "", "/?page=scheduler&keep=1");
|
||||
const { result } = render();
|
||||
expect(result.current.page).toBe("feed");
|
||||
expect(window.location.search).toBe("?keep=1");
|
||||
expect(window.history.state).toMatchObject({ sfPage: "feed" });
|
||||
});
|
||||
|
||||
it("lands on the feed when Back reaches an entry naming one", () => {
|
||||
qc.setQueryData(qk.me(), member);
|
||||
const { result } = render();
|
||||
act(() => result.current.setPage("files"));
|
||||
act(() => {
|
||||
window.history.replaceState({ sfPage: "config" }, "", "/?page=config");
|
||||
window.dispatchEvent(new PopStateEvent("popstate", { state: { sfPage: "config" } }));
|
||||
});
|
||||
expect(result.current.page).toBe("feed");
|
||||
});
|
||||
|
||||
it("leaves the page it is on once the account loses it", async () => {
|
||||
window.history.replaceState(null, "", "/?page=users");
|
||||
const { result } = render();
|
||||
expect(result.current.page).toBe("users");
|
||||
act(() => {
|
||||
qc.setQueryData(qk.me(), member);
|
||||
});
|
||||
await settle();
|
||||
expect(result.current.page).toBe("feed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("NavigationProvider · leaving a sub-view whose entry is the oldest of this document", () => {
|
||||
// A full tab can have dropped the entry under the one the channel page or the search pushed (see
|
||||
// lib/history's backStaysInDocument): leaving clears it in place instead of stepping out.
|
||||
|
||||
Reference in New Issue
Block a user