Under "Open the channel" a video card's menu (the right-click on its channel name included) adds the channel to the feed's channel filter, or, when it is already in, takes it out. Channels OR together, so it widens a filter that names others, as "Show in feed" does. Not on a channel page, whose view is that channel already, nor in a live YouTube search or on Home.
107 lines
4.1 KiB
TypeScript
107 lines
4.1 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// The video menu's download row was labelled with the download's STATE ("Downloaded",
|
|
// "In queue") — a row that reads as a status yet opens the download dialog. It is "Download…" now,
|
|
// the state riding along as the row's hint.
|
|
import type { ReactNode } from "react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { cleanup, renderHook, waitFor } from "@testing-library/react";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
|
vi.mock("react-i18next", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("react-i18next")>()),
|
|
useTranslation: () => ({ t: (k: string) => k }),
|
|
}));
|
|
vi.mock("./navigationContext", () => ({ useNavigationActions: () => ({ openChannel: vi.fn() }) }));
|
|
vi.mock("./comfortPin", () => ({
|
|
pinVideo: vi.fn(),
|
|
useComfortPin: () => ({ item: () => [] }),
|
|
}));
|
|
|
|
import { api, type Video } from "../lib/api";
|
|
import { useVideoActions } from "./useVideoActions";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
const video = { id: "v1", title: "A video", status: "new", position_seconds: 0 } as Video;
|
|
|
|
function downloadRow(state: string | undefined) {
|
|
vi.spyOn(api, "downloadIndex").mockResolvedValue(
|
|
(state ? { v1: state } : {}) as Awaited<ReturnType<typeof api.downloadIndex>>,
|
|
);
|
|
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
<QueryClientProvider client={qc}>{children}</QueryClientProvider>
|
|
);
|
|
const { result } = renderHook(
|
|
() => useVideoActions({ onState: vi.fn(), onToggleSave: vi.fn() }, true),
|
|
{ wrapper },
|
|
);
|
|
return () => result.current.items(video, null).find((i) => i.id === "download")!;
|
|
}
|
|
|
|
describe("the video menu's download row", () => {
|
|
// The state rode in a `title`, which a key or a finger never shows.
|
|
it("says what it does, whatever the state, and carries the state as its note", async () => {
|
|
const done = downloadRow("done");
|
|
await waitFor(() => expect(done().note).toBe("downloads.button.downloaded"));
|
|
expect(done().label).toBe("downloads.menu.download");
|
|
cleanup();
|
|
const none = downloadRow(undefined);
|
|
await waitFor(() => expect(none().label).toBe("downloads.menu.download"));
|
|
expect(none().note).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
// The feed's channel filter from a card (owner): under "Open the channel", a row that adds the card's
|
|
// channel to the filter, or takes it out again when it is already in.
|
|
describe("the video menu's channel-filter row", () => {
|
|
const menu = (ids: string[] | null) => {
|
|
vi.spyOn(api, "downloadIndex").mockResolvedValue({});
|
|
const set = vi.fn();
|
|
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
<QueryClientProvider client={qc}>{children}</QueryClientProvider>
|
|
);
|
|
const { result } = renderHook(
|
|
() =>
|
|
useVideoActions(
|
|
{
|
|
onState: vi.fn(),
|
|
onToggleSave: vi.fn(),
|
|
channelFilter: ids ? { ids, set } : undefined,
|
|
},
|
|
true,
|
|
),
|
|
{ wrapper },
|
|
);
|
|
const items = result.current.items({ ...video, channel_id: "UCa" }, null);
|
|
return { items, set };
|
|
};
|
|
|
|
it("adds the channel to a filter that names others, right under Open the channel", () => {
|
|
const { items, set } = menu(["UCz"]);
|
|
const at = items.findIndex((i) => i.id === "channel");
|
|
const row = items[at + 1]!;
|
|
expect(row.id).toBe("channel-filter");
|
|
expect(row.label).toBe("card.menu.channelFilterAdd");
|
|
row.onSelect?.();
|
|
expect(set).toHaveBeenCalledWith(["UCz", "UCa"]);
|
|
});
|
|
|
|
it("takes it out again when it is in", () => {
|
|
const { items, set } = menu(["UCz", "UCa"]);
|
|
const row = items.find((i) => i.id === "channel-filter")!;
|
|
expect(row.label).toBe("card.menu.channelFilterRemove");
|
|
row.onSelect?.();
|
|
expect(set).toHaveBeenCalledWith(["UCz"]);
|
|
});
|
|
|
|
it("is absent where the host gives no filter (a channel page, Home, a live search)", () => {
|
|
expect(menu(null).items.some((i) => i.id === "channel-filter")).toBe(false);
|
|
});
|
|
});
|