import { renderToStaticMarkup } from "react-dom/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ReactNode } from "react";
const mocks = vi.hoisted(() => ({
useSearchItemMatchCandidates: vi.fn(),
useApplyItemMatch: vi.fn(),
useCatalogItemDetail: vi.fn(),
}));
vi.mock("@/hooks/queries/items", () => ({
useSearchItemMatchCandidates: (...args: unknown[]) => mocks.useSearchItemMatchCandidates(...args),
useApplyItemMatch: (...args: unknown[]) => mocks.useApplyItemMatch(...args),
}));
vi.mock("@/hooks/queries/catalogRead", () => ({
useCatalogItemDetail: (...args: unknown[]) => mocks.useCatalogItemDetail(...args),
}));
// Mock Dialog components to render inline (Radix portals don't render in SSR)
vi.mock("@/components/ui/dialog", () => ({
Dialog: ({ children, open }: { children: ReactNode; open: boolean }) =>
open ?
{children}
: null,
DialogContent: ({ children }: { children: ReactNode }) => {children}
,
DialogHeader: ({ children }: { children: ReactNode }) => {children}
,
DialogTitle: ({ children }: { children: ReactNode }) => {children}
,
}));
import MatchItemDialog from "./MatchItemDialog";
const baseItem = {
content_id: "movie-1",
title: "Inception",
year: 2010,
type: "movie" as const,
};
describe("MatchItemDialog", () => {
beforeEach(() => {
const mutate = vi.fn();
mocks.useSearchItemMatchCandidates.mockReturnValue({
mutate,
isPending: false,
isSuccess: false,
data: null,
});
mocks.useApplyItemMatch.mockReturnValue({
mutate,
isPending: false,
});
mocks.useCatalogItemDetail.mockReturnValue({
data: undefined,
isLoading: false,
});
});
it("renders title, year, IMDb, TMDB, and TVDB inputs", () => {
const markup = renderToStaticMarkup(
{}} />,
);
expect(markup).toContain('id="match-title"');
expect(markup).toContain('id="match-year"');
expect(markup).toContain('id="match-imdb"');
expect(markup).toContain('id="match-tmdb"');
expect(markup).toContain('id="match-tvdb"');
});
it("renders a search button", () => {
const markup = renderToStaticMarkup(
{}} />,
);
expect(markup).toContain("Search");
});
it("renders candidate list when search returns results", () => {
mocks.useSearchItemMatchCandidates.mockReturnValue({
mutate: vi.fn(),
isPending: false,
isSuccess: true,
data: {
candidates: [
{
title: "Inception",
year: 2010,
content_type: "movie",
overview: "A thief who steals secrets...",
image_url: "https://image.tmdb.org/poster.jpg",
provider_ids: { tmdb: "27205", imdb: "tt1375666" },
sources: ["tmdb", "tvdb"],
agreement_hints: ["agreed_by_tmdb_and_tvdb"],
},
{
title: "Inception (TV)",
year: 2012,
content_type: "series",
overview: "An unrelated series",
image_url: "",
provider_ids: { tvdb: "999" },
sources: ["tvdb"],
agreement_hints: [],
},
],
},
});
const markup = renderToStaticMarkup(
{}} />,
);
expect(markup).toContain("Inception");
expect(markup).toContain("Inception (TV)");
expect(markup).toContain("tmdb");
expect(markup).toContain("2 sources agree");
expect(markup).toContain('data-testid="match-candidate"');
});
it("shows no results message when search returns empty", () => {
mocks.useSearchItemMatchCandidates.mockReturnValue({
mutate: vi.fn(),
isPending: false,
isSuccess: true,
data: { candidates: [] },
});
const markup = renderToStaticMarkup(
{}} />,
);
expect(markup).toContain("No candidates found");
});
it("displays current item summary with title, year, and type badge", () => {
const markup = renderToStaticMarkup(
{}} />,
);
expect(markup).toContain("Inception");
expect(markup).toContain("(2010)");
expect(markup).toContain("movie");
});
it("renders local media rows when file paths are available", () => {
const markup = renderToStaticMarkup(
{}}
/>,
);
expect(markup).toContain("Local media");
expect(markup).toContain("Inception (2010)/");
expect(markup).toContain("Inception.4K.mkv");
expect(markup).toContain("2160p");
});
});