- Add vitest with solid plugin, coverage, jsdom environment - Create MSW mocks for API responses in test setup - Add unit test for ProfessionAdminListPage - Add Playwright accessibility and visual regression configs - Add sample accessibility and visual tests - Add ESLint + Prettier configs with SolidJS rules - Update scripts: test, test:coverage, test:accessibility, test:visual - Add .gitignore entries for coverage, test-results, playwright-report, .vitest - Install required dev dependencies: vitest, @solidjs/testing-library, msw, eslint, prettier, typescript, @axe-core/playwright, etc. - Create .github/workflows/ci.yml with lint, test, coverage, e2e, accessibility, visual checks This sets up full testing pipeline for admin frontend.
182 lines
4.5 KiB
TypeScript
182 lines
4.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, fireEvent, waitFor } from "@solidjs/testing-library";
|
|
import { createSignal } from "solid-js";
|
|
import ProfessionAdminListPage from "~/components/admin/ProfessionAdminListPage";
|
|
|
|
// Mock fetch globally
|
|
const mockFetch = vi.fn();
|
|
global.fetch = mockFetch;
|
|
|
|
describe("ProfessionAdminListPage", () => {
|
|
beforeEach(() => {
|
|
mockFetch.mockClear();
|
|
});
|
|
|
|
it("renders page title and subtitle", async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => [
|
|
{
|
|
id: "1",
|
|
first_name: "John",
|
|
last_name: "Doe",
|
|
email: "john@example.com",
|
|
phone: "1234567890",
|
|
status: "ACTIVE",
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
},
|
|
],
|
|
});
|
|
|
|
render(() =>
|
|
ProfessionAdminListPage({
|
|
endpoint: "/api/admin/test",
|
|
title: "Test Management",
|
|
subtitle: "Manage test records",
|
|
emptyLabel: "No test records found.",
|
|
viewHref: (id) => `/admin/test/${id}`,
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Test Management")).toBeInTheDocument();
|
|
expect(screen.getByText("Manage test records")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("filters by search query", async () => {
|
|
const mockData = [
|
|
{
|
|
id: "1",
|
|
first_name: "Alice",
|
|
last_name: "Smith",
|
|
email: "alice@example.com",
|
|
phone: "",
|
|
status: "ACTIVE",
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
},
|
|
{
|
|
id: "2",
|
|
first_name: "Bob",
|
|
last_name: "Jones",
|
|
email: "bob@example.com",
|
|
phone: "",
|
|
status: "INACTIVE",
|
|
created_at: "2024-01-02T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockData,
|
|
});
|
|
|
|
render(() =>
|
|
ProfessionAdminListPage({
|
|
endpoint: "/api/admin/test",
|
|
title: "Test",
|
|
subtitle: "",
|
|
emptyLabel: "Empty",
|
|
viewHref: (id) => `/admin/test/${id}`,
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Alice Smith")).toBeInTheDocument();
|
|
expect(screen.getByText("Bob Jones")).toBeInTheDocument();
|
|
});
|
|
|
|
const input = screen.getByPlaceholderText("Search by name or email...");
|
|
fireEvent.input(input, { target: { value: "alice" } });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Alice Smith")).toBeInTheDocument();
|
|
expect(screen.queryByText("Bob Jones")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("shows empty state when no data", async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => [],
|
|
});
|
|
|
|
render(() =>
|
|
ProfessionAdminListPage({
|
|
endpoint: "/api/admin/test",
|
|
title: "Test",
|
|
subtitle: "",
|
|
emptyLabel: "No records.",
|
|
viewHref: (id) => `/admin/test/${id}`,
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("No records.")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("handles fetch error gracefully", async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 500,
|
|
});
|
|
|
|
render(() =>
|
|
ProfessionAdminListPage({
|
|
endpoint: "/api/admin/test",
|
|
title: "Test",
|
|
subtitle: "",
|
|
emptyLabel: "Empty",
|
|
viewHref: (id) => `/admin/test/${id}`,
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Failed to load/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("export button calls exportCsv and downloads file", async () => {
|
|
const mockData = [
|
|
{
|
|
id: "1",
|
|
first_name: "Alice",
|
|
last_name: "Smith",
|
|
email: "alice@example.com",
|
|
phone: "123",
|
|
status: "ACTIVE",
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockData,
|
|
});
|
|
|
|
// Mock URL.createObjectURL and link.click
|
|
const mockClick = vi.fn();
|
|
global.URL.createObjectURL = vi.fn(() => "blob:test");
|
|
global.document.createElement = vi.fn(() => ({ click: mockClick, href: "" }));
|
|
|
|
render(() =>
|
|
ProfessionAdminListPage({
|
|
endpoint: "/api/admin/test",
|
|
title: "Test",
|
|
subtitle: "",
|
|
emptyLabel: "Empty",
|
|
viewHref: (id) => `/admin/test/${id}`,
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Alice Smith")).toBeInTheDocument();
|
|
});
|
|
|
|
const exportBtn = screen.getByText("Export");
|
|
fireEvent.click(exportBtn);
|
|
|
|
expect(mockClick).toHaveBeenCalled();
|
|
});
|
|
});
|