fix(nav): a page settling keeps a reload's overlay entry; an old entry's sub-view goes to its own (review round 4)
- useUrlMirror: a change before the user has done anything since the load is the page settling (the rail stepping off a playlist deleted since, Files off a folder gone), not another view: the reload mark stays, and the player that reopens takes its entry over — one Back closes it, as before. - useHistorySubview: an entry an older build wrote is a module's only when its _sub is the view the address names under that module's key (the card's push kept the season's `v` beside its `m`). - Playlists: the rail's default selection and the page's "the rail moved there" read one helper. - moduleUrl contract: writer literals in a choice's branches are scanned too; the sub-view census walks type arguments of any depth and refuses an aliased import. - A stale comment on the nav press over a live search.
This commit is contained in:
@@ -265,6 +265,26 @@ describe("Playlists — a player the address names", () => {
|
||||
expect(new URLSearchParams(window.location.search).get("watch")).toBe("Z");
|
||||
});
|
||||
|
||||
it("plays alone over a playlist this account does not hold, the rail moving to the first of several", async () => {
|
||||
window.history.replaceState(null, "", "/?page=playlists&pl=7&watch=Z");
|
||||
h.selectedId = 7;
|
||||
let answer: (v: Video) => void = () => {};
|
||||
vi.spyOn(api, "videoRow").mockReturnValue(new Promise((r) => (answer = r)) as never);
|
||||
const { rerender } = mount(
|
||||
{ 1: [vid("A", 0)], 2: [vid("B", 0)] },
|
||||
{},
|
||||
{
|
||||
detail: async (id) => (id === 7 ? new Promise<never>(() => {}) : list(id, [vid("A", 0)])),
|
||||
},
|
||||
);
|
||||
await waitFor(() => expect(qc.getQueryData(qk.playlists())).toBeDefined());
|
||||
h.selectedId = 1; // the rail's own move: to the first of the account's list
|
||||
rerender();
|
||||
act(() => answer(vid("Z", 0)));
|
||||
await waitFor(() => expect(h.player?.video.id).toBe("Z"));
|
||||
expect(h.player!.queue?.map((v) => v.id)).toEqual(["Z"]);
|
||||
});
|
||||
|
||||
it("plays alone over a playlist this account does not hold, the rail moved on before the page came", async () => {
|
||||
window.history.replaceState(null, "", "/?page=playlists&pl=1&watch=Z");
|
||||
// The rail's keep-valid step ran before the page mounted: the selection is already 1, and 1 even
|
||||
|
||||
@@ -65,6 +65,7 @@ import NavGrid from "../../components/ui/NavGrid";
|
||||
import { PageToolbar } from "../../components/ui/PageShell";
|
||||
import { usePlaylistActions } from "./usePlaylistActions";
|
||||
import { usePlaylists } from "./playlistsContext";
|
||||
import { railDefaultId } from "./playlistsInit";
|
||||
import { usePref } from "../../lib/usePref";
|
||||
import { navItemAttrs } from "../../lib/listNav";
|
||||
import { ColumnHead } from "../../components/ui/tableHeader";
|
||||
@@ -593,7 +594,7 @@ function PlaylistDetail() {
|
||||
const linkedOver = linkedId;
|
||||
const linkedOutOfReach =
|
||||
listQuery.data != null && !listQuery.data.some((p) => p.id === linkedOver);
|
||||
const railMovedTo = listQuery.data?.[0]?.id ?? null;
|
||||
const railMovedTo = railDefaultId(listQuery.data);
|
||||
const linkedListIn =
|
||||
linkedOutOfReach ||
|
||||
(detail != null && (detail.items.length === 0 || items.length > 0)) ||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { type PanelItem } from "../../components/ui/PanelGroups";
|
||||
import SortControl from "../../components/ui/SortControl";
|
||||
import { type SortOption } from "../../lib/columnSort";
|
||||
import { usePlaylists, usePlaylistsActions } from "./playlistsContext";
|
||||
import { railDefaultId } from "./playlistsInit";
|
||||
import { useMe } from "../../lib/useMe";
|
||||
import { playlistName } from "../../lib/playlistName";
|
||||
import { usePlaylistActions } from "./usePlaylistActions";
|
||||
@@ -105,7 +106,7 @@ export default function PlaylistsRail() {
|
||||
return;
|
||||
}
|
||||
if (selectedId == null || !playlists.some((p) => p.id === selectedId)) {
|
||||
setSelectedId(playlists[0]!.id); // playlists is non-empty (the length guard above returns)
|
||||
setSelectedId(railDefaultId(playlists));
|
||||
}
|
||||
}, [playlists, selectedId, listQuery.data, setSelectedId]);
|
||||
|
||||
|
||||
@@ -12,3 +12,9 @@ export function readSelectedId(): number | null {
|
||||
parsePositiveIntId(getAccountRaw(LS.playlist))
|
||||
);
|
||||
}
|
||||
|
||||
/** Where the rail puts the selection when it holds none of the account's playlists: the first one.
|
||||
* The page reads the same, so a move there is told apart from a pick (a linked player's scope). */
|
||||
export function railDefaultId(list: readonly { id: number }[] | undefined): number | null {
|
||||
return list?.[0]?.id ?? null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user