fix(playlists): the player's Remove follows the list it was launched from, the player survives a change of selection, and a gone list is said in the reader's language
- D3 snapshotted the removal's TARGET, but whether the player offered Remove still followed the
selected list: launched from Watch later (no Remove, by design), selecting another list under it
made Remove appear — and delete from Watch later. The snapshot now carries the launch list's
editability too.
- Selecting another list under an open player (the command palette works over it) put the page into
"Loading…", and the player was rendered only in the loaded branch — it vanished mid-playback and
came back at its launch video. It now sits at one place in the tree whatever the page is doing.
- A removal takes the video out of the CACHED list as well as the rows: the player's progress
checkpoint rewrote that entry before the refetch landed, handing the membership guard a list that
still held the video — the row came back and the undo history went.
- The watched-state callback refreshed the selected list, not the launch list (D3's sibling).
- `_own_playlist`'s 404 is structured (`{"code": "playlist", "reason": "not_found"}`) and localised:
the bare "Unknown playlist" reached the Hungarian UI in English, which also made round 1's new
`removeFailed` string unreachable. Both Remove and Move report through `notifyActionError` now.
- The "viewer moved on under the confirm" test stepped back only once, which lands on the same video
whether or not the removed one left the queue; it now takes the second step that tells them apart.
This commit is contained in:
@@ -21,7 +21,7 @@ vi.mock("../activeAccount", () => ({
|
||||
notifyActiveAccountChange: vi.fn(),
|
||||
}));
|
||||
|
||||
import { uploadReq, setUnauthorizedHandler, HttpError } from "./core";
|
||||
import { req, uploadReq, setUnauthorizedHandler, HttpError } from "./core";
|
||||
|
||||
// A fake XHR whose lifecycle the test drives: capture the instance, set status/responseText, then
|
||||
// invoke onload/onerror/abort by hand.
|
||||
@@ -117,3 +117,21 @@ describe("uploadReq", () => {
|
||||
expect(reportError).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("req — a structured refusal reads in the user's language", () => {
|
||||
it("names a playlist that is gone instead of passing the server's English through", async () => {
|
||||
// The 404 every playlist route answers when the list was deleted in another tab. It used to be a
|
||||
// bare "Unknown playlist" string, shown verbatim under the Hungarian UI.
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(JSON.stringify({ detail: { code: "playlist", reason: "not_found" } }), {
|
||||
status: 404,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
);
|
||||
await expect(req("/api/playlists/7/items/v1", { method: "DELETE" })).rejects.toMatchObject({
|
||||
status: 404,
|
||||
detail: "playlists.errors.not_found",
|
||||
});
|
||||
expect(reportError).not.toHaveBeenCalled(); // a 404 is the caller's to report
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user