diff --git a/frontend/src/components/NotificationsPanel.menu.dom.test.tsx b/frontend/src/components/NotificationsPanel.menu.dom.test.tsx index b56592fb..5d1c7fc7 100644 --- a/frontend/src/components/NotificationsPanel.menu.dom.test.tsx +++ b/frontend/src/components/NotificationsPanel.menu.dom.test.tsx @@ -46,7 +46,28 @@ vi.mock("../lib/notifications", async (importOriginal) => ({ vi.mock("./navigationContext", () => ({ useNavigationActions: () => ({ setPage: h.setPage }) })); vi.mock("./channelsContext", () => ({ useChannelsActions: () => ({ focusChannel: vi.fn() }) })); vi.mock("./feedFiltersContext", () => ({ - useFeedFilters: () => ({ tags: [54], untagged: true, q: "rust", maxAgeDays: 30, sort: "newest" }), + // Every narrowing a feed can carry (E28 review C2: the year, the custom range, the length, the + // content types and the scope survived "Find in feed" and could still hide the video). + useFeedFilters: () => ({ + tags: [54], + tagModes: { language: "and", topic: "or", other: "or" }, + untagged: true, + q: "rust", + maxAgeDays: 30, + dateFrom: "2024-01-01", + dateTo: "2024-02-01", + year: 2024, + minDuration: 60, + maxDuration: 600, + includeNormal: true, + includeShorts: false, + includeLive: false, + scope: "my", + librarySource: "organic", + sort: "oldest", + show: "unwatched", + channelIds: ["x"], + }), useFeedFiltersActions: () => ({ setFilters: h.setFilters }), })); vi.mock("./ui/PageShell", async (importOriginal) => ({ @@ -223,12 +244,18 @@ describe("NotificationsPanel · an activity row", () => { await openMenu(() => fireEvent.contextMenu(rowOf("Activity 9"))); choose("notifications.findInFeed"); await waitFor(() => expect(h.setFilters).toHaveBeenCalled()); + // Nothing but the state and the channel narrows; the reader's sort and tag modes stay theirs. expect(h.setFilters).toHaveBeenCalledWith({ tags: [], + tagModes: { language: "and", topic: "or", other: "or" }, untagged: false, q: "", - maxAgeDays: undefined, - sort: "newest", + includeNormal: true, + includeShorts: true, + includeLive: true, + scope: "all", + librarySource: "all", + sort: "oldest", show: "watched", channelIds: ["c"], }); diff --git a/frontend/src/components/NotificationsPanel.tsx b/frontend/src/components/NotificationsPanel.tsx index f1566865..eca8753b 100644 --- a/frontend/src/components/NotificationsPanel.tsx +++ b/frontend/src/components/NotificationsPanel.tsx @@ -142,15 +142,22 @@ export default function NotificationsPanel() { // "Find in feed" / undo, ported from the old bell. Works for both hidden and watched // notices — jump to the feed filtered to that state and the video's channel. - // The narrowing filters that could leave the video out (a tag, "no tag", a search, an age cap) are - // dropped: kept, a channel outside the reader's tag filter found an empty feed. + // Built from nothing, not spread over the reader's filters: EVERY narrowing that could leave the + // video out goes — a tag, "no tag", a search, the age cap, the year, the custom range, the length, + // a content type (a hidden Short under "no Shorts"), the scope and the library source (E28 review + // C2: the spread kept whatever this list forgot). Only how the reader orders and matches stays. function locate(meta: VideoHiddenMeta | VideoWatchedMeta) { setFilters({ - ...filters, tags: [], + tagModes: filters.tagModes, untagged: false, q: "", - maxAgeDays: undefined, + includeNormal: true, + includeShorts: true, + includeLive: true, + scope: "all", + librarySource: "all", + sort: filters.sort, show: meta.kind === "video-hidden" ? "hidden" : "watched", channelIds: meta.channelId ? [meta.channelId] : [], }); diff --git a/frontend/src/components/usePlaylistActions.dom.test.tsx b/frontend/src/components/usePlaylistActions.dom.test.tsx index 957a15d1..d3b66bd8 100644 --- a/frontend/src/components/usePlaylistActions.dom.test.tsx +++ b/frontend/src/components/usePlaylistActions.dom.test.tsx @@ -161,10 +161,9 @@ describe("usePlaylistActions — a playlist's menu", () => { // offering a second push of the same list. describe("usePlaylistActions — one push at a time, across menus", () => { it("a push started from one menu is off in the other until it ends", async () => { - let finish: (v: unknown) => void = () => {}; - vi.spyOn(api, "playlistPushPlan").mockReturnValue( - new Promise((r) => (finish = r)) as ReturnType, - ); + type Plan = Awaited>; + let finish: (v: Plan) => void = () => {}; + vi.spyOn(api, "playlistPushPlan").mockReturnValue(new Promise((r) => (finish = r))); const rail = mount(); const header = mount(); const dirty = { ...linked, dirty: true }; @@ -176,7 +175,7 @@ describe("usePlaylistActions — one push at a time, across menus", () => { await act(async () => push(header).onSelect!()); expect(api.playlistPushPlan).toHaveBeenCalledTimes(1); await act(async () => { - finish({ action: "update", affordable: true }); + finish({ action: "update", affordable: true } as Plan); }); expect(push(header).disabled).toBeFalsy(); });