// @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()), 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( {}} /> , ); 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"); }); });