fix(web-tests): stabilize async auth and usage page assertions
All checks were successful
ci/woodpecker/push/web Pipeline was successful
All checks were successful
ci/woodpecker/push/web Pipeline was successful
This commit is contained in:
@@ -104,19 +104,28 @@ describe("LoginPage", (): void => {
|
|||||||
expect(screen.getByText("Loading authentication options")).toBeInTheDocument();
|
expect(screen.getByText("Loading authentication options")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders the page heading and description", (): void => {
|
it("renders the page heading and description", async (): Promise<void> => {
|
||||||
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
||||||
|
|
||||||
render(<LoginPage />);
|
render(<LoginPage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Welcome to Mosaic Stack");
|
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Welcome to Mosaic Stack");
|
||||||
expect(screen.getByText(/Your personal assistant platform/i)).toBeInTheDocument();
|
expect(screen.getByText(/Your personal assistant platform/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has proper layout styling", (): void => {
|
it("has proper layout styling", async (): Promise<void> => {
|
||||||
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
||||||
|
|
||||||
const { container } = render(<LoginPage />);
|
const { container } = render(<LoginPage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const main = container.querySelector("main");
|
const main = container.querySelector("main");
|
||||||
expect(main).toHaveClass("flex", "min-h-screen");
|
expect(main).toHaveClass("flex", "min-h-screen");
|
||||||
});
|
});
|
||||||
@@ -430,37 +439,56 @@ describe("LoginPage", (): void => {
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
describe("responsive layout", (): void => {
|
describe("responsive layout", (): void => {
|
||||||
it("applies mobile-first padding to main element", (): void => {
|
it("applies mobile-first padding to main element", async (): Promise<void> => {
|
||||||
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
||||||
|
|
||||||
const { container } = render(<LoginPage />);
|
const { container } = render(<LoginPage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const main = container.querySelector("main");
|
const main = container.querySelector("main");
|
||||||
|
|
||||||
expect(main).toHaveClass("p-4", "sm:p-8");
|
expect(main).toHaveClass("p-4", "sm:p-8");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies responsive text size to heading", (): void => {
|
it("applies responsive text size to heading", async (): Promise<void> => {
|
||||||
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
||||||
|
|
||||||
render(<LoginPage />);
|
render(<LoginPage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const heading = screen.getByRole("heading", { level: 1 });
|
const heading = screen.getByRole("heading", { level: 1 });
|
||||||
expect(heading).toHaveClass("text-2xl", "sm:text-4xl");
|
expect(heading).toHaveClass("text-2xl", "sm:text-4xl");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies responsive padding to card container", (): void => {
|
it("applies responsive padding to card container", async (): Promise<void> => {
|
||||||
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
||||||
|
|
||||||
const { container } = render(<LoginPage />);
|
const { container } = render(<LoginPage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const card = container.querySelector(".bg-white");
|
const card = container.querySelector(".bg-white");
|
||||||
|
|
||||||
expect(card).toHaveClass("p-4", "sm:p-8");
|
expect(card).toHaveClass("p-4", "sm:p-8");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("card container has full width with max-width constraint", (): void => {
|
it("card container has full width with max-width constraint", async (): Promise<void> => {
|
||||||
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
mockFetchConfig(EMAIL_ONLY_CONFIG);
|
||||||
|
|
||||||
const { container } = render(<LoginPage />);
|
const { container } = render(<LoginPage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const wrapper = container.querySelector(".max-w-md");
|
const wrapper = container.querySelector(".max-w-md");
|
||||||
|
|
||||||
expect(wrapper).toHaveClass("w-full", "max-w-md");
|
expect(wrapper).toHaveClass("w-full", "max-w-md");
|
||||||
@@ -539,7 +567,9 @@ describe("LoginPage", (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// LoginForm auto-focuses the email input on mount
|
// LoginForm auto-focuses the email input on mount
|
||||||
|
await waitFor((): void => {
|
||||||
expect(screen.getByLabelText(/email/i)).toHaveFocus();
|
expect(screen.getByLabelText(/email/i)).toHaveFocus();
|
||||||
|
});
|
||||||
|
|
||||||
// Tab forward through form: email -> password -> submit
|
// Tab forward through form: email -> password -> submit
|
||||||
await user.tab();
|
await user.tab();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import UsagePage from "./page";
|
import UsagePage from "./page";
|
||||||
|
|
||||||
@@ -113,6 +114,15 @@ function setupMocks(overrides?: { empty?: boolean; error?: boolean }): void {
|
|||||||
vi.mocked(fetchTaskOutcomes).mockResolvedValue(mockTaskOutcomes);
|
vi.mocked(fetchTaskOutcomes).mockResolvedValue(mockTaskOutcomes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupPendingMocks(): void {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function -- intentionally unresolved for loading-state test
|
||||||
|
const pending = new Promise<never>(() => {});
|
||||||
|
vi.mocked(fetchUsageSummary).mockReturnValue(pending);
|
||||||
|
vi.mocked(fetchTokenUsage).mockReturnValue(pending);
|
||||||
|
vi.mocked(fetchCostBreakdown).mockReturnValue(pending);
|
||||||
|
vi.mocked(fetchTaskOutcomes).mockReturnValue(pending);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Tests ───────────────────────────────────────────────────────────
|
// ─── Tests ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
describe("UsagePage", (): void => {
|
describe("UsagePage", (): void => {
|
||||||
@@ -120,23 +130,32 @@ describe("UsagePage", (): void => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the page title and subtitle", (): void => {
|
it("should render the page title and subtitle", async (): Promise<void> => {
|
||||||
setupMocks();
|
setupMocks();
|
||||||
render(<UsagePage />);
|
render(<UsagePage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Usage");
|
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Usage");
|
||||||
expect(screen.getByText("Token usage and cost overview")).toBeInTheDocument();
|
expect(screen.getByText("Token usage and cost overview")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have proper layout structure", (): void => {
|
it("should have proper layout structure", async (): Promise<void> => {
|
||||||
setupMocks();
|
setupMocks();
|
||||||
const { container } = render(<UsagePage />);
|
const { container } = render(<UsagePage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const main = container.querySelector("main");
|
const main = container.querySelector("main");
|
||||||
expect(main).toBeInTheDocument();
|
expect(main).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show loading skeleton initially", (): void => {
|
it("should show loading skeleton initially", (): void => {
|
||||||
setupMocks();
|
setupPendingMocks();
|
||||||
render(<UsagePage />);
|
render(<UsagePage />);
|
||||||
expect(screen.getByTestId("loading-skeleton")).toBeInTheDocument();
|
expect(screen.getByTestId("loading-skeleton")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -171,25 +190,34 @@ describe("UsagePage", (): void => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the time range selector with three options", (): void => {
|
it("should render the time range selector with three options", async (): Promise<void> => {
|
||||||
setupMocks();
|
setupMocks();
|
||||||
render(<UsagePage />);
|
render(<UsagePage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
expect(screen.getByText("7 Days")).toBeInTheDocument();
|
expect(screen.getByText("7 Days")).toBeInTheDocument();
|
||||||
expect(screen.getByText("30 Days")).toBeInTheDocument();
|
expect(screen.getByText("30 Days")).toBeInTheDocument();
|
||||||
expect(screen.getByText("90 Days")).toBeInTheDocument();
|
expect(screen.getByText("90 Days")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have 30 Days selected by default", (): void => {
|
it("should have 30 Days selected by default", async (): Promise<void> => {
|
||||||
setupMocks();
|
setupMocks();
|
||||||
render(<UsagePage />);
|
render(<UsagePage />);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const button30d = screen.getByText("30 Days");
|
const button30d = screen.getByText("30 Days");
|
||||||
expect(button30d).toHaveAttribute("aria-pressed", "true");
|
expect(button30d).toHaveAttribute("aria-pressed", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should change time range when a different option is clicked", async (): Promise<void> => {
|
it("should change time range when a different option is clicked", async (): Promise<void> => {
|
||||||
setupMocks();
|
setupMocks();
|
||||||
|
const user = userEvent.setup();
|
||||||
render(<UsagePage />);
|
render(<UsagePage />);
|
||||||
|
|
||||||
// Wait for initial load
|
// Wait for initial load
|
||||||
@@ -199,7 +227,11 @@ describe("UsagePage", (): void => {
|
|||||||
|
|
||||||
// Click 7 Days
|
// Click 7 Days
|
||||||
const button7d = screen.getByText("7 Days");
|
const button7d = screen.getByText("7 Days");
|
||||||
fireEvent.click(button7d);
|
await user.click(button7d);
|
||||||
|
|
||||||
|
await waitFor((): void => {
|
||||||
|
expect(fetchUsageSummary).toHaveBeenCalledWith("7d");
|
||||||
|
});
|
||||||
|
|
||||||
expect(button7d).toHaveAttribute("aria-pressed", "true");
|
expect(button7d).toHaveAttribute("aria-pressed", "true");
|
||||||
expect(screen.getByText("30 Days")).toHaveAttribute("aria-pressed", "false");
|
expect(screen.getByText("30 Days")).toHaveAttribute("aria-pressed", "false");
|
||||||
|
|||||||
Reference in New Issue
Block a user