fix(nav): the address keeps what the app rewrites around it (review round 3)
- The Feed press over a live search ends only the search: the feed's own view stays named. - Leaving a search steps back only off an entry the app pushed (_ytOwn); one a link or a reload opened is cleared in place, also where the Navigation API is missing. - useUrlMirror rewrites a changed view through replaceView, so a reload's overlay marks go; its first write and its landing writes keep them for an overlay that reopens. - Files and Playlists: a box emptied and filled again settles at once, never naming the ended search. - Playlists: a linked player is scoped to the playlist the store started with, and only the rail's own move to the first playlist is no pick. - Contract: writer objects are scanned to their matching brace, a computed or shorthand key is refused, and every useHistorySubview call hands a named definition. - Lint: playwright/no-skipped-test also refuses fixme (gate snapshot regenerated). - FEED_DEFAULT_SORT at the three remaining sites; linkedNotListed says to search by name.
This commit is contained in:
@@ -78,7 +78,7 @@
|
||||
{ "allowShortCircuit": false, "allowTaggedTemplates": false, "allowTernary": false }
|
||||
],
|
||||
"playwright/no-focused-test": [2],
|
||||
"playwright/no-skipped-test": [2]
|
||||
"playwright/no-skipped-test": [2, { "allowConditional": false, "disallowFixme": true }]
|
||||
},
|
||||
"settings": null
|
||||
},
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// A reload under an overlay that reopens (the player) loads on the overlay's entry, its mark on it,
|
||||
// and the overlay takes that entry over as it opens again. A page's first mirror write on that load
|
||||
// (the defaults its address did not name yet) is the same view, not another: the mark stays, and the
|
||||
// reopened overlay still takes its entry over rather than pushing one more. Its own file: the entry
|
||||
// is read as the history module loads, so it is set up before the import.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
|
||||
describe("a page's first mirror write on the entry a reload under an overlay started from", () => {
|
||||
it("leaves the entry to the overlay that reopens", async () => {
|
||||
window.history.replaceState({ sfPage: "activity" }, "", "/?page=activity");
|
||||
window.history.pushState({ sfPage: "activity", _ov: 1, _ovd: 1 }, "", "/?page=activity");
|
||||
const { useUrlMirror } = await import("./useUrlMirror");
|
||||
const { useBackToClose } = await import("./history");
|
||||
renderHook(() => useUrlMirror({ at: "runs" }));
|
||||
expect(new URLSearchParams(window.location.search).get("at")).toBe("runs");
|
||||
|
||||
const depth = window.history.length;
|
||||
renderHook(() => useBackToClose(() => {}));
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(window.history.length).toBe(depth);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// A reload under a dialog that does not reopen (About) loads on the dialog's entry, its overlay mark
|
||||
// still on it. A page's view mirrored onto that entry (another tab) is another view: the next overlay
|
||||
// pushes its own entry over it rather than taking this one over — taken over, closing it stepped back
|
||||
// onto the entry below, and the page read its old view back from that address. Its own file: the
|
||||
// entry is read as the history module loads, so it is set up before the import.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
|
||||
describe("a view mirrored onto the entry a reload under a dialog started from", () => {
|
||||
it("is where an overlay over it closes to", async () => {
|
||||
window.history.replaceState({ sfPage: "activity" }, "", "/?page=activity&at=runs");
|
||||
window.history.pushState(
|
||||
{ sfPage: "activity", _ov: 1, _ovd: 1 },
|
||||
"",
|
||||
"/?page=activity&at=runs",
|
||||
);
|
||||
const { useUrlMirror } = await import("./useUrlMirror");
|
||||
const { useBackToClose } = await import("./history");
|
||||
const mirror = renderHook(({ at }) => useUrlMirror({ at }), { initialProps: { at: "runs" } });
|
||||
mirror.rerender({ at: "admin" });
|
||||
expect(new URLSearchParams(window.location.search).get("at")).toBe("admin");
|
||||
|
||||
const depth = window.history.length;
|
||||
const overlay = renderHook(() => useBackToClose(() => {}));
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(window.history.length).toBe(depth + 1);
|
||||
|
||||
await act(async () => {
|
||||
const landed = new Promise((res) => window.addEventListener("popstate", res, { once: true }));
|
||||
overlay.unmount();
|
||||
await landed;
|
||||
});
|
||||
expect(new URLSearchParams(window.location.search).get("at")).toBe("admin");
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useEffectEvent, useState } from "react";
|
||||
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
||||
import { replaceView } from "./history";
|
||||
import { urlWithParams } from "./urlState";
|
||||
|
||||
// Mirror a set of nav params onto the CURRENT history entry (replaceState — mirroring is not a new
|
||||
@@ -19,11 +20,20 @@ export function useUrlMirror(updates: Record<string, string | null>): void {
|
||||
// The object itself is read through an effect event, so the effect's reactive inputs are the
|
||||
// serialized value and the landing count — which is exactly what the dep list says: an effect event
|
||||
// for the identity that churns, primitives for the triggers.
|
||||
// A change of what this mirror writes, after its first write, is another view on the same entry
|
||||
// (replaceView: it drops the marks a reload under an overlay left). Its first write on a mount and
|
||||
// its write on a landing name the view the entry already shows, so the marks stay: an overlay that
|
||||
// reopens after a reload still finds its entry.
|
||||
const written = useRef<string | null>(null);
|
||||
const mirror = useEffectEvent(() => {
|
||||
const href = urlWithParams(updates);
|
||||
const changed = written.current != null && written.current !== dep;
|
||||
written.current = dep;
|
||||
// Every mirror runs on every landing: one with nothing to change writes nothing (a browser
|
||||
// throttles a page that rewrites its address too often).
|
||||
if (href !== urlWithParams({})) window.history.replaceState(window.history.state, "", href);
|
||||
if (href === urlWithParams({})) return;
|
||||
if (changed) replaceView(window.history.state ?? {}, href);
|
||||
else window.history.replaceState(window.history.state, "", href);
|
||||
});
|
||||
// Counted in state, on a task after the popstate dispatch: every listener of it has run by then —
|
||||
// a store that reads its keys back from the landed address among them (Activity) — and so has the
|
||||
|
||||
Reference in New Issue
Block a user