项目文件夹

文件
2026-07-13 13:39:12 +08:00

127 行
4.7 KiB
TypeScript

// @vitest-environment jsdom
import React from "react";
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
// ── Mocks ─────────────────────────────────────────────────────────────────────
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
// ── Import component after mocks ──────────────────────────────────────────────
const { default: SearchConceptCard } = await import(
"../../../src/app/(dashboard)/dashboard/search-tools/components/SearchConceptCard"
);
// ── Helpers ───────────────────────────────────────────────────────────────────
const containers: Array<{ root: ReturnType<typeof createRoot>; el: HTMLDivElement }> = [];
function renderCard(props?: { defaultCollapsed?: boolean }): HTMLDivElement {
const el = document.createElement("div");
document.body.appendChild(el);
const root = createRoot(el);
act(() => {
root.render(React.createElement(SearchConceptCard, props ?? {}));
});
containers.push({ root, el });
return el;
}
// ── Tests ─────────────────────────────────────────────────────────────────────
describe("SearchConceptCard", () => {
beforeEach(() => {
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
});
afterEach(() => {
for (const { root, el } of containers.splice(0)) {
act(() => root.unmount());
el.remove();
}
document.body.innerHTML = "";
});
it("renders with data-testid", () => {
const el = renderCard();
expect(el.querySelector("[data-testid='search-concept-card']")).toBeTruthy();
});
it("is expanded by default (defaultCollapsed=false)", () => {
const el = renderCard({ defaultCollapsed: false });
const content = el.querySelector("[data-testid='concept-card-content']");
expect(content).toBeTruthy();
});
it("is expanded when no prop is passed", () => {
const el = renderCard();
const content = el.querySelector("[data-testid='concept-card-content']");
expect(content).toBeTruthy();
});
it("is collapsed when defaultCollapsed=true", () => {
const el = renderCard({ defaultCollapsed: true });
const content = el.querySelector("[data-testid='concept-card-content']");
expect(content).toBeNull();
});
it("toggles collapsed state when header is clicked", () => {
const el = renderCard({ defaultCollapsed: false });
const header = el.querySelector("button") as HTMLButtonElement;
expect(el.querySelector("[data-testid='concept-card-content']")).toBeTruthy();
act(() => {
header.click();
});
expect(el.querySelector("[data-testid='concept-card-content']")).toBeNull();
act(() => {
header.click();
});
expect(el.querySelector("[data-testid='concept-card-content']")).toBeTruthy();
});
it("renders Search concept item", () => {
const el = renderCard();
const searchItem = el.querySelector("[data-testid='concept-item-search']");
expect(searchItem).toBeTruthy();
// useTranslations mock returns the key as text, so we assert on the i18n key
expect(searchItem?.textContent).toContain("searchConceptTitle");
});
it("renders Scrape concept item", () => {
const el = renderCard();
const scrapeItem = el.querySelector("[data-testid='concept-item-scrape']");
expect(scrapeItem).toBeTruthy();
expect(scrapeItem?.textContent).toContain("scrapeConceptTitle");
});
it("renders Compare concept item", () => {
const el = renderCard();
const compareItem = el.querySelector("[data-testid='concept-item-compare']");
expect(compareItem).toBeTruthy();
expect(compareItem?.textContent).toContain("compareConceptTitle");
});
it("renders Rerank concept item", () => {
const el = renderCard();
const rerankItem = el.querySelector("[data-testid='concept-item-rerank']");
expect(rerankItem).toBeTruthy();
});
it("aria-expanded reflects collapsed state", () => {
const el = renderCard({ defaultCollapsed: false });
const header = el.querySelector("button") as HTMLButtonElement;
expect(header.getAttribute("aria-expanded")).toBe("true");
act(() => {
header.click();
});
expect(header.getAttribute("aria-expanded")).toBe("false");
});
});