test(fe): cover the palette's content, AltGr, the banner's name, share colours, playlist headers
- AppCommandPalette had no test: modules gated by the account, hidden channels left out, the playlist row selecting before it navigates, and the surprise riding setPage's `then`. - keymap: an Alt/AltGr-modified key (Ctrl+Alt on Windows, a constant on the HU layout) fires no bare-key or Mod row. - Banner: the dismiss button falls back to "Close" rather than going unnamed. - ShareDialog: a protected link's lit lock and Revoke's danger colour stay on the icon. - Both playlist headers are rendered now: the YouTube one needed the toolbar slot it portals into. Meta line sums known runtimes and treats an ABSENT duration as nothing (null never exercised the guard: `3000 + null` is 3000); a one-row list offers no view controls.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// The share dialog's link row says two things with colour alone: a PROTECTED link's lock is lit, and
|
||||
// Revoke is the destructive one. Both colours once sat on the button, where the ghost variant's own
|
||||
// colour class outranks them (clsx does not merge Tailwind), so a password-protected link looked
|
||||
// unprotected and Revoke hovered neutral. They live on the ICON now; this pins them there.
|
||||
import { describe, it, expect, afterEach, vi } from "vitest";
|
||||
import { render, screen, cleanup } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
// Partial: the import graph reaches `src/i18n`, which initialises against the real
|
||||
// `initReactI18next`, so a wholesale mock throws before any test runs.
|
||||
vi.mock("react-i18next", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("react-i18next")>()),
|
||||
useTranslation: () => ({ t: (k: string) => k, i18n: { language: "en" } }),
|
||||
}));
|
||||
vi.mock("./ui/Modal", () => ({ default: ({ children }: { children: ReactNode }) => children }));
|
||||
vi.mock("./confirmContext", () => ({ useConfirm: () => async () => false }));
|
||||
vi.mock("../lib/api", () => ({
|
||||
api: {
|
||||
shareRecipients: async () => [],
|
||||
downloadLinks: async () => [
|
||||
{
|
||||
id: 1,
|
||||
token: "a",
|
||||
url: "/watch/a",
|
||||
allow_download: false,
|
||||
has_password: true,
|
||||
expires_at: null,
|
||||
view_count: 0,
|
||||
created_at: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
token: "b",
|
||||
url: "/watch/b",
|
||||
allow_download: false,
|
||||
has_password: false,
|
||||
expires_at: null,
|
||||
view_count: 0,
|
||||
created_at: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
import ShareDialog from "./ShareDialog";
|
||||
import type { DownloadJob } from "../lib/api";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const iconClass = (button: HTMLElement) => button.querySelector("svg")?.getAttribute("class") ?? "";
|
||||
|
||||
describe("ShareDialog · a link row's state colours", () => {
|
||||
it("lights the lock of a protected link only, and paints Revoke as danger — on the icon", async () => {
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
render(
|
||||
<QueryClientProvider client={qc}>
|
||||
<ShareDialog job={{ id: 9 } as DownloadJob} onClose={() => {}} />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
const protectedLock = await screen.findByRole("button", {
|
||||
name: "downloads.share.changePassword",
|
||||
});
|
||||
const openLock = screen.getByRole("button", { name: "downloads.share.setPassword" });
|
||||
expect(iconClass(protectedLock)).toContain("text-accent");
|
||||
expect(iconClass(openLock)).not.toContain("text-accent");
|
||||
|
||||
for (const revoke of screen.getAllByRole("button", { name: "downloads.share.revoke" }))
|
||||
expect(iconClass(revoke)).toContain("text-danger");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user