vitest setup + component tests

This commit is contained in:
2025-05-30 21:16:27 +01:00
parent 64e5f8b3b6
commit c0cef0c2d3
4 changed files with 171 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { Badge } from "./badge";
describe("Badge", () => {
it("renders children content", () => {
render(<Badge>Easy</Badge>);
expect(screen.getByText("Easy")).toBeInTheDocument();
});
it("applies default variant styles", () => {
render(<Badge>Test</Badge>);
const badge = screen.getByText("Test");
expect(badge).toHaveClass("rounded-full", "text-xs", "font-medium");
});
it("applies outline variant styles", () => {
render(<Badge variant="outline">Test</Badge>);
const badge = screen.getByText("Test");
expect(badge).toHaveClass("border");
});
it("applies custom className", () => {
render(<Badge className="custom-class">Test</Badge>);
const badge = screen.getByText("Test");
expect(badge).toHaveClass("custom-class");
});
it("supports aria-label for accessibility", () => {
render(<Badge aria-label="Easy difficulty">Easy</Badge>);
const badge = screen.getByText("Easy");
expect(badge).toHaveAttribute("aria-label", "Easy difficulty");
});
});

View File

@@ -0,0 +1,92 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
describe("API client", () => {
const originalFetch = global.fetch;
beforeEach(() => {
vi.resetModules();
});
afterEach(() => {
global.fetch = originalFetch;
});
it("getQuestions builds correct query string with filters", async () => {
let capturedUrl = "";
global.fetch = vi.fn().mockImplementation((url: string) => {
capturedUrl = url;
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
items: [],
total: 0,
page: 1,
limit: 20,
pages: 0,
}),
});
});
const { getQuestions } = await import("./api");
await getQuestions({ page: 2, difficulty: "easy", category: "arrays" });
expect(capturedUrl).toContain("page=2");
expect(capturedUrl).toContain("difficulty=easy");
expect(capturedUrl).toContain("category=arrays");
});
it("getQuestions omits empty filters", async () => {
let capturedUrl = "";
global.fetch = vi.fn().mockImplementation((url: string) => {
capturedUrl = url;
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
items: [],
total: 0,
page: 1,
limit: 20,
pages: 0,
}),
});
});
const { getQuestions } = await import("./api");
await getQuestions({});
expect(capturedUrl).not.toContain("?");
});
it("throws error on non-ok response", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 404,
statusText: "Not Found",
});
const { getQuestion } = await import("./api");
await expect(getQuestion("nonexistent")).rejects.toThrow("API error: 404");
});
it("getStats returns stats data", async () => {
const mockStats = {
total_questions: 10,
by_difficulty: { easy: 3, medium: 5, hard: 2 },
by_category: [],
by_pattern: [],
};
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockStats),
});
const { getStats } = await import("./api");
const result = await getStats();
expect(result).toEqual(mockStats);
});
});

View File

@@ -0,0 +1,21 @@
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterEach, vi } from "vitest";
afterEach(() => {
cleanup();
});
Object.defineProperty(window, "matchMedia", {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});