Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/ui/src/components/lang-provider/lang-provider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { render, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";

import { LangProvider } from "./lang-provider";

let mockPathname = "/en/docs";

vi.mock("next/navigation", () => ({
usePathname: () => mockPathname,
}));

describe("LangProvider", () => {
afterEach(() => {
mockPathname = "/en/docs";
document.documentElement.removeAttribute("lang");
});

it("sets the document language from a supported pathname prefix", async () => {
mockPathname = "/fr/components/button";

render(<LangProvider />);

await waitFor(() => {
expect(document.documentElement).toHaveAttribute("lang", "fr");
});
});

it("uses the default language when the pathname has no locale prefix", async () => {
mockPathname = "/components/button";

render(<LangProvider defaultLanguage="fr" />);

await waitFor(() => {
expect(document.documentElement).toHaveAttribute("lang", "fr");
});
});

it("ignores unsupported locale prefixes", async () => {
mockPathname = "/de/components/button";

render(<LangProvider defaultLanguage="en" supportedLanguages={["en"]} />);

await waitFor(() => {
expect(document.documentElement).toHaveAttribute("lang", "en");
});
});
});
72 changes: 72 additions & 0 deletions packages/ui/src/components/overview-board/overview-board.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import { OverviewBoard, type OverviewBoardItem } from "./overview-board";

const errorItem: OverviewBoardItem = {
ctaLabel: "Review errors",
description: "Two checks need attention.",
handleCtaClick: vi.fn(),
heading: "Error budget",
id: "errors",
metric: "2",
tone: "danger",
};

const actionItem: OverviewBoardItem = {
description: "One action is waiting.",
heading: "Action queue",
id: "actions",
metric: "1",
tone: "warning",
};

const items: OverviewBoardItem[] = [errorItem, actionItem];

describe("OverviewBoard", () => {
it("renders board copy and item metrics", () => {
render(
<OverviewBoard
eyebrow="Operations"
heading="Run overview"
items={items}
subtitle="Critical work at a glance."
/>,
);

expect(screen.getByText("Operations")).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Run overview" }),
).toBeInTheDocument();
expect(screen.getByText("Critical work at a glance.")).toBeInTheDocument();
expect(screen.getByText("Error budget")).toBeInTheDocument();
expect(screen.getByText("Two checks need attention.")).toBeInTheDocument();
expect(screen.getByText("2")).toBeInTheDocument();
});

it("renders default icons based on heading text", () => {
const { container } = render(
<OverviewBoard heading="Run overview" items={items} />,
);

expect(container.querySelector(".lucide-circle-alert")).toBeInTheDocument();
expect(container.querySelector(".lucide-list-todo")).toBeInTheDocument();
});

it("renders CTA buttons only when configured and calls the item handler", () => {
const handleCtaClick = vi.fn();
render(
<OverviewBoard
heading="Run overview"
items={[{ ...errorItem, handleCtaClick }, actionItem]}
/>,
);

fireEvent.click(screen.getByRole("button", { name: /review errors/i }));

expect(handleCtaClick).toHaveBeenCalledTimes(1);
expect(
screen.queryByRole("button", { name: /action queue/i }),
).not.toBeInTheDocument();
});
});
96 changes: 96 additions & 0 deletions packages/ui/src/components/quiz/quiz.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import { Quiz, type QuizOption } from "./quiz";

function keyedOption(option: QuizOption, key: string): QuizOption {
return Object.assign(option, {
toString: () => key,
});
}

const options: QuizOption[] = [
keyedOption(
{
correct: true,
explanation: "Paris is the capital of France.",
label: "Paris",
},
"paris",
),
keyedOption(
{
explanation: "London is the capital of the United Kingdom.",
label: "London",
},
"london",
),
];

function renderQuiz(onAnswer = vi.fn()) {
render(
<Quiz
explanation={<p>Paris is correct.</p>}
hint="Think of the Eiffel Tower."
onAnswer={onAnswer}
options={options}
question="What is the capital of France?"
/>,
);

return { onAnswer };
}

describe("Quiz", () => {
it("renders the question and disables submission until an option is selected", () => {
renderQuiz();

expect(
screen.getByText("What is the capital of France?"),
).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Check Answer" })).toBeDisabled();

fireEvent.click(screen.getByRole("button", { name: "Paris" }));

expect(
screen.getByRole("button", { name: "Check Answer" }),
).not.toBeDisabled();
});

it("reveals the hint before submission", () => {
renderQuiz();

fireEvent.click(screen.getByRole("button", { name: "Show hint" }));

expect(screen.getByText("Think of the Eiffel Tower.")).toBeInTheDocument();
});

it("submits a correct answer and resets state", () => {
const { onAnswer } = renderQuiz();

fireEvent.click(screen.getByRole("button", { name: "Paris" }));
fireEvent.click(screen.getByRole("button", { name: "Check Answer" }));

expect(onAnswer).toHaveBeenCalledWith(true);
expect(screen.getByText("Correct!")).toBeInTheDocument();
expect(screen.getByText("Paris is correct.")).toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "Try Again" }));

expect(screen.getByRole("button", { name: "Check Answer" })).toBeDisabled();
expect(screen.queryByText("Correct!")).not.toBeInTheDocument();
});

it("submits an incorrect answer", () => {
const { onAnswer } = renderQuiz();

fireEvent.click(screen.getByRole("button", { name: "London" }));
fireEvent.click(screen.getByRole("button", { name: "Check Answer" }));

expect(onAnswer).toHaveBeenCalledWith(false);
expect(screen.getByText("Not quite right.")).toBeInTheDocument();
expect(
screen.getByText("London is the capital of the United Kingdom."),
).toBeInTheDocument();
});
});
48 changes: 48 additions & 0 deletions packages/ui/src/components/step-by-step/step-by-step.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";

import { Step, StepByStep } from "./step-by-step";

function renderSteps(interactive = false) {
render(
<StepByStep interactive={interactive} title="Setup guide">
<Step title="Install">
<p>Install dependencies.</p>
</Step>
<Step title="Build">
<p>Build the package.</p>
</Step>
</StepByStep>,
);
}

describe("StepByStep", () => {
it("renders non-interactive numbered steps", () => {
renderSteps();

expect(
screen.getByRole("heading", { name: "Setup guide" }),
).toBeInTheDocument();
expect(screen.getByText("1")).toBeInTheDocument();
expect(screen.getByText("2")).toBeInTheDocument();
expect(screen.getByText("Install")).toBeInTheDocument();
expect(screen.getByText("Build the package.")).toBeInTheDocument();
});

it("tracks completed steps in interactive mode", () => {
renderSteps(true);

expect(screen.getByText("0/2 completed")).toBeInTheDocument();

const firstStepButton = screen.getByRole("button", { name: "1" });
fireEvent.click(firstStepButton);

expect(screen.getByText("1/2 completed")).toBeInTheDocument();
expect(screen.getByText("Install")).toHaveClass("line-through");

fireEvent.click(firstStepButton);

expect(screen.getByText("0/2 completed")).toBeInTheDocument();
expect(screen.getByText("Install")).not.toHaveClass("line-through");
});
});
Loading
Loading