diff --git a/__tests__/benchmarks-page.test.tsx b/__tests__/benchmarks-page.test.tsx index a61784f6..2de06fc3 100644 --- a/__tests__/benchmarks-page.test.tsx +++ b/__tests__/benchmarks-page.test.tsx @@ -1,147 +1,28 @@ /** @jest-environment jsdom */ -import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import BenchmarksPage from '@/app/benchmarks/page'; +import React from "react"; +import { render, screen } from "@testing-library/react"; -describe('Benchmarks Page', () => { - test('renders main heading', () => { - render(); - - expect(screen.getByRole('heading', { name: /performance benchmarks/i })).toBeInTheDocument(); - }); - - test('renders page description', () => { - render(); - - expect(screen.getByText(/comprehensive performance metrics for top skills/i)).toBeInTheDocument(); - }); - - test('displays environment information', () => { - render(); - - expect(screen.getByText(/hardware:/i)).toBeInTheDocument(); - expect(screen.getByText(/model:/i)).toBeInTheDocument(); - expect(screen.getByText(/date:/i)).toBeInTheDocument(); - }); - - test('renders category filters', () => { - render(); - - expect(screen.getByText(/all categories/i)).toBeInTheDocument(); - expect(screen.getByText(/filter by category/i)).toBeInTheDocument(); - }); +jest.mock("@/lib/agent-benchmarks", () => ({ + agentBenchmarksData: [{ id: "sample-benchmark" }], +})); - test('renders skill cards with benchmark data', () => { - render(); - - // Should render multiple skill cards (using getAllByText since they appear in both cards and table) - expect(screen.getAllByText('Web Search')[0]).toBeInTheDocument(); - expect(screen.getAllByText('File Operations')[0]).toBeInTheDocument(); - expect(screen.getAllByText('Code Execution')[0]).toBeInTheDocument(); - }); +jest.mock("@/app/benchmarks/benchmarks-client", () => ({ + BenchmarksClient: ({ data }: { data: unknown[] }) => ( +
+

Agent Benchmark Suite

+ {data.length} +
+ ), +})); - test('displays latency metrics', () => { - render(); - - // Check for latency labels - const p50Labels = screen.getAllByText('p50'); - const p95Labels = screen.getAllByText('p95'); - const p99Labels = screen.getAllByText('p99'); - - expect(p50Labels.length).toBeGreaterThan(0); - expect(p95Labels.length).toBeGreaterThan(0); - expect(p99Labels.length).toBeGreaterThan(0); - }); +import BenchmarksPage from "@/app/benchmarks/page"; - test('displays throughput and reliability metrics', () => { +describe("Benchmarks Page", () => { + test("renders benchmarks client", () => { render(); - - expect(screen.getAllByText(/throughput/i).length).toBeGreaterThan(0); - expect(screen.getAllByText(/reliability/i).length).toBeGreaterThan(0); - }); - - test('displays resource usage information', () => { - render(); - - expect(screen.getAllByText(/resource usage/i).length).toBeGreaterThan(0); - expect(screen.getAllByText(/avg memory/i).length).toBeGreaterThan(0); - }); - - test('displays historical trends', () => { - render(); - - expect(screen.getAllByText(/historical trend/i).length).toBeGreaterThan(0); - expect(screen.getAllByText(/last 5 runs/i).length).toBeGreaterThan(0); - }); - - test('renders detailed comparison table', () => { - render(); - - expect(screen.getByText(/detailed comparison/i)).toBeInTheDocument(); - // Use getByRole to target table header specifically - expect(screen.getByRole('columnheader', { name: /skill/i })).toBeInTheDocument(); - expect(screen.getByText(/req\/sec/i)).toBeInTheDocument(); - expect(screen.getByText(/success %/i)).toBeInTheDocument(); - }); - - test('renders metrics explanation section', () => { - render(); - - expect(screen.getByText(/metrics explained/i)).toBeInTheDocument(); - expect(screen.getByText(/latency percentiles/i)).toBeInTheDocument(); - expect(screen.getByText(/reliability metrics/i)).toBeInTheDocument(); - }); - - test('category filter buttons are clickable', () => { - render(); - - const allCategoriesButton = screen.getByText(/all categories/i); - expect(allCategoriesButton).toBeInTheDocument(); - - fireEvent.click(allCategoriesButton); - expect(allCategoriesButton).toBeInTheDocument(); - }); - - test('displays skill descriptions', () => { - render(); - - expect(screen.getByText(/search and retrieve web content/i)).toBeInTheDocument(); - expect(screen.getByText(/read, write, and manage files/i)).toBeInTheDocument(); - }); - - test('renders category badges', () => { - render(); - - // Categories should be displayed as badges - const categoryBadges = screen.getAllByText(/research|system|development|integration/i); - expect(categoryBadges.length).toBeGreaterThan(0); - }); - - test('comparison table has sortable columns', () => { - render(); - - // Check for sort icons or indicators in table header - const skillHeader = screen.getByRole('columnheader', { name: /skill/i }); - expect(skillHeader).toBeInTheDocument(); - }); - - test('displays multiple benchmark skills', () => { - render(); - - // Should render at least 8 skills as per requirement (using getAllByText since they appear in both cards and table) - expect(screen.getAllByText('Web Search')[0]).toBeInTheDocument(); - expect(screen.getAllByText('File Operations')[0]).toBeInTheDocument(); - expect(screen.getAllByText('Code Execution')[0]).toBeInTheDocument(); - expect(screen.getAllByText('API Integration')[0]).toBeInTheDocument(); - expect(screen.getAllByText('Database Queries')[0]).toBeInTheDocument(); - expect(screen.getAllByText('Image Processing')[0]).toBeInTheDocument(); - expect(screen.getAllByText('Text Generation')[0]).toBeInTheDocument(); - expect(screen.getAllByText('Notifications')[0]).toBeInTheDocument(); - }); - test('renders without errors', () => { - const { container } = render(); - expect(container).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: /agent benchmark suite/i })).toBeInTheDocument(); + expect(screen.getByTestId("benchmarks-count")).toHaveTextContent("1"); }); }); diff --git a/__tests__/changelog-content.test.tsx b/__tests__/changelog-content.test.tsx index 9f2688d9..4ada8469 100644 --- a/__tests__/changelog-content.test.tsx +++ b/__tests__/changelog-content.test.tsx @@ -22,36 +22,31 @@ jest.mock("next/link", () => { const mockEntries: ChangelogEntry[] = [ { + id: "1", date: "2026-02-08", title: "New Feature Release", - description: "Added new feature by @user1", + description: "Added new feature", category: "feature", - link: "/feature", - pr: "https://github.com/test/pr/1", + prNumber: 11, + prUrl: "https://github.com/test/pr/11", }, { + id: "2", date: "2026-02-07", title: "Bug Fix", - description: "Fixed bug by @user2", - category: "fix", - link: "/fix", - pr: "https://github.com/test/pr/2", + description: "Fixed bug", + category: "bugfix", + prNumber: 12, + prUrl: "https://github.com/test/pr/12", }, { + id: "3", date: "2026-02-06", title: "Documentation Update", - description: "Updated docs by @user3", + description: "Updated docs", category: "docs", - link: "/docs", - pr: "https://github.com/test/pr/3", - }, - { - date: "2026-02-05", - title: "Code Refactor", - description: "Refactored code by @user4", - category: "refactor", - link: "/refactor", - pr: "https://github.com/test/pr/4", + prNumber: 13, + prUrl: "https://github.com/test/pr/13", }, ]; @@ -61,80 +56,24 @@ describe("ChangelogContent", () => { expect(screen.getByText("New Feature Release")).toBeInTheDocument(); expect(screen.getByText("Bug Fix")).toBeInTheDocument(); expect(screen.getByText("Documentation Update")).toBeInTheDocument(); - expect(screen.getByText("Code Refactor")).toBeInTheDocument(); - }); - - it("displays stats banner", () => { - render(); - expect(screen.getByText("Changes This Month")).toBeInTheDocument(); - expect(screen.getByText("Features Shipped")).toBeInTheDocument(); - expect(screen.getByText("Bugs Fixed")).toBeInTheDocument(); }); it("displays category filter tabs", () => { render(); - const buttons = screen.getAllByRole("button"); - const buttonTexts = buttons.map(b => b.textContent); - expect(buttonTexts).toContain("All"); - expect(buttonTexts).toContain("Features"); - expect(buttonTexts).toContain("Fixes"); - expect(buttonTexts).toContain("Docs"); - expect(buttonTexts).toContain("Refactors"); - expect(buttonTexts).toContain("Tests"); + const buttonTexts = screen.getAllByRole("button").map((b) => b.textContent); + expect(buttonTexts).toEqual(expect.arrayContaining(["All", "Features", "Bugfixes", "Docs"])); }); - it("filters by feature category", () => { + it("filters by bugfix category", () => { render(); - const featuresButton = screen.getAllByRole("button").find(b => b.textContent === "Features"); - fireEvent.click(featuresButton!); - - expect(screen.getByText("New Feature Release")).toBeInTheDocument(); - expect(screen.queryByText("Bug Fix")).not.toBeInTheDocument(); - expect(screen.queryByText("Documentation Update")).not.toBeInTheDocument(); - }); + fireEvent.click(screen.getByRole("button", { name: "Bugfixes" })); - it("filters by fix category", () => { - render(); - const fixesButton = screen.getAllByRole("button").find(b => b.textContent === "Fixes"); - fireEvent.click(fixesButton!); - expect(screen.getByText("Bug Fix")).toBeInTheDocument(); expect(screen.queryByText("New Feature Release")).not.toBeInTheDocument(); }); - it("filters by docs category", () => { - render(); - const docsButton = screen.getAllByRole("button").find(b => b.textContent === "Docs"); - fireEvent.click(docsButton!); - - expect(screen.getByText("Documentation Update")).toBeInTheDocument(); - expect(screen.queryByText("New Feature Release")).not.toBeInTheDocument(); - }); - - it("shows all entries when clicking All filter", () => { - render(); - - // First filter by features - const featuresButton = screen.getAllByRole("button").find(b => b.textContent === "Features"); - fireEvent.click(featuresButton!); - expect(screen.queryByText("Bug Fix")).not.toBeInTheDocument(); - - // Then click All - const allButton = screen.getAllByRole("button").find(b => b.textContent === "All"); - fireEvent.click(allButton!); - expect(screen.getByText("New Feature Release")).toBeInTheDocument(); - expect(screen.getByText("Bug Fix")).toBeInTheDocument(); - }); - - it("displays PR links", () => { - render(); - const prLinks = screen.getAllByText(/View PR/); - expect(prLinks.length).toBeGreaterThan(0); - }); - - it("renders empty state when no entries match filter", () => { - const emptyEntries: ChangelogEntry[] = []; - render(); + it("renders empty state when no entries", () => { + render(); expect(screen.getByText("No updates found for this filter.")).toBeInTheDocument(); }); }); diff --git a/__tests__/changelog-page.test.tsx b/__tests__/changelog-page.test.tsx index bf073707..b7728055 100644 --- a/__tests__/changelog-page.test.tsx +++ b/__tests__/changelog-page.test.tsx @@ -3,7 +3,6 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import ChangelogPage from "../src/app/changelog/page"; -import * as changelogLib from "../src/lib/changelog"; type LinkProps = { href: string; @@ -20,34 +19,44 @@ jest.mock("next/link", () => { return LinkMock; }); -// Mock the changelog data -jest.mock("../src/lib/changelog", () => ({ - getChangelogEntries: jest.fn(), - type: jest.fn(), +jest.mock("next/headers", () => ({ + headers: jest.fn(async () => + new Map([ + ["host", "localhost:3000"], + ["x-forwarded-proto", "http"], + ]) + ), })); const mockEntries = [ { + id: "entry-1", date: "2026-02-08", title: "Test Feature", description: "A test feature description", - category: "feature" as const, - link: "/test", - pr: "https://github.com/test/pr/1", + category: "feature", + prNumber: 101, + prUrl: "https://github.com/test/pr/101", }, { + id: "entry-2", date: "2026-02-07", title: "Test Fix", description: "A test fix description", - category: "fix" as const, - link: "/test-fix", - pr: "https://github.com/test/pr/2", + category: "bugfix", + prNumber: 102, + prUrl: "https://github.com/test/pr/102", }, ]; describe("Changelog Page", () => { beforeEach(() => { - (changelogLib.getChangelogEntries as jest.Mock).mockResolvedValue(mockEntries); + global.fetch = jest.fn(async () => + ({ + ok: true, + json: async () => ({ entries: mockEntries, total: mockEntries.length }), + } as Response) + ) as jest.Mock; }); it("renders the changelog page", async () => { @@ -56,35 +65,30 @@ describe("Changelog Page", () => { expect(container).toBeInTheDocument(); }); - it("displays the page title", async () => { + it("displays the page title and description", async () => { const jsx = await ChangelogPage(); render(jsx); expect(screen.getByText("Changelog")).toBeInTheDocument(); - }); - - it("displays the description", async () => { - const jsx = await ChangelogPage(); - render(jsx); expect(screen.getByText(/Recent updates and improvements/)).toBeInTheDocument(); }); - it("displays newsletter signup section", async () => { + it("displays newsletter CTA and API link", async () => { const jsx = await ChangelogPage(); render(jsx); - const subscribeButtons = screen.getAllByText("Subscribe"); - expect(subscribeButtons.length).toBeGreaterThan(0); - }); - it("displays the API link", async () => { - const jsx = await ChangelogPage(); - render(jsx); + expect(screen.getAllByText("Subscribe").length).toBeGreaterThan(0); const apiLink = screen.getByText("/api/changelog"); - expect(apiLink).toBeInTheDocument(); expect(apiLink.closest("a")).toHaveAttribute("href", "/api/changelog"); }); - it("renders with no entries", async () => { - (changelogLib.getChangelogEntries as jest.Mock).mockResolvedValue([]); + it("renders even when API returns no entries", async () => { + global.fetch = jest.fn(async () => + ({ + ok: true, + json: async () => ({ entries: [], total: 0 }), + } as Response) + ) as jest.Mock; + const jsx = await ChangelogPage(); const { container } = render(jsx); expect(container).toBeInTheDocument(); diff --git a/__tests__/events-page.test.tsx b/__tests__/events-page.test.tsx index 8ae2a0d7..adf5be30 100644 --- a/__tests__/events-page.test.tsx +++ b/__tests__/events-page.test.tsx @@ -3,114 +3,21 @@ import React from "react"; import { render, screen } from "@testing-library/react"; -jest.setTimeout(10_000); - -// ---- Browser API polyfills commonly used by UI libs ---- -class NoopObserver { - observe() {} - unobserve() {} - disconnect() {} -} - -// @ts-expect-error - polyfill for tests -global.ResizeObserver = global.ResizeObserver ?? NoopObserver; -// @ts-expect-error - polyfill for tests -global.IntersectionObserver = global.IntersectionObserver ?? NoopObserver; - -Object.defineProperty(window, "matchMedia", { - writable: true, - value: - window.matchMedia ?? - ((query: string) => ({ - matches: false, - media: query, - onchange: null, - addListener: () => {}, - removeListener: () => {}, - addEventListener: () => {}, - removeEventListener: () => {}, - dispatchEvent: () => false, - })), -}); - -// ---- Next.js shims ---- - -type LinkProps = { - href: string; - children?: React.ReactNode; -} & React.AnchorHTMLAttributes; - -jest.mock("next/link", () => { - const LinkMock = ({ href, children, ...props }: LinkProps) => ( - - {children} - - ); - LinkMock.displayName = "Link"; - return LinkMock; -}); - -jest.mock("next/navigation", () => ({ - useRouter: () => ({ - push: jest.fn(), - replace: jest.fn(), - back: jest.fn(), - prefetch: jest.fn(), - refresh: jest.fn(), - }), - useSearchParams: () => new URLSearchParams(), - usePathname: () => "/events", - useParams: () => ({}), - notFound: () => { - throw new Error("NEXT_NOT_FOUND"); - }, - redirect: (url: string) => { - throw new Error(`NEXT_REDIRECT:${url}`); - }, +jest.mock("@/app/events/events-client", () => ({ + EventsClient: () => ( +
+

Events & Community

+

Mock events client

+
+ ), })); -jest.mock("next/headers", () => ({ - headers: () => - new Map([ - ["host", "localhost:3000"], - ["x-forwarded-proto", "http"], - ]), - cookies: () => ({ get: () => undefined, getAll: () => [] }), -})); +import EventsPage from "@/app/events/page"; describe("Events page", () => { - test("renders without crashing", async () => { - const EventsPage = (await import("@/app/events/page")).default; - const { container } = render(); - expect(container).toBeInTheDocument(); - }); - - test("displays page title", async () => { - const EventsPage = (await import("@/app/events/page")).default; - render(); - expect(screen.getByText("Events & Community")).toBeInTheDocument(); - }); - - test("displays upcoming and past events sections", async () => { - const EventsPage = (await import("@/app/events/page")).default; - render(); - expect(screen.getByText("Upcoming Events")).toBeInTheDocument(); - expect(screen.getByText("Past Events")).toBeInTheDocument(); - }); - - test("displays event type filters", async () => { - const EventsPage = (await import("@/app/events/page")).default; - render(); - expect(screen.getByText("All Events")).toBeInTheDocument(); - expect(screen.getAllByText("Webinar").length).toBeGreaterThan(0); - expect(screen.getAllByText("Workshop").length).toBeGreaterThan(0); - expect(screen.getAllByText("Hackathon").length).toBeGreaterThan(0); - expect(screen.getAllByText("Meetup").length).toBeGreaterThan(0); - }); - - test("displays host an event CTA", async () => { - const EventsPage = (await import("@/app/events/page")).default; + test("renders events client", () => { render(); - expect(screen.getByText("Want to host an event?")).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: /events & community/i })).toBeInTheDocument(); + expect(screen.getByText("Mock events client")).toBeInTheDocument(); }); }); diff --git a/__tests__/feed-and-health.test.ts b/__tests__/feed-and-health.test.ts index d1001b38..d786a2a7 100644 --- a/__tests__/feed-and-health.test.ts +++ b/__tests__/feed-and-health.test.ts @@ -34,15 +34,17 @@ describe('/api/feed', () => { }); describe('/api/health', () => { - test('returns ok + data counts', async () => { + test('returns health metadata', async () => { const res = await healthGET(); expect(res.status).toBe(200); const body = await res.json(); expect(body.status).toBe('ok'); - expect(body.data).toHaveProperty('news'); - expect(body.data).toHaveProperty('total'); - expect(body.data.total).toBeGreaterThan(0); + expect(body).toHaveProperty('version'); + expect(body).toHaveProperty('skills_count'); + expect(body).toHaveProperty('mcp_count'); + expect(body).toHaveProperty('uptime'); + expect(body).toHaveProperty('timestamp'); }); }); diff --git a/__tests__/glossary-page.test.tsx b/__tests__/glossary-page.test.tsx index 114c0069..eddffaee 100644 --- a/__tests__/glossary-page.test.tsx +++ b/__tests__/glossary-page.test.tsx @@ -1,21 +1,20 @@ /** @jest-environment jsdom */ import React from "react"; -import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"; import GlossaryPage from "@/app/glossary/page"; jest.setTimeout(10_000); -// ---- Browser API polyfills ---- class NoopObserver { observe() {} unobserve() {} disconnect() {} } -// @ts-expect-error - polyfill for tests +// @ts-expect-error - test polyfill global.ResizeObserver = global.ResizeObserver ?? NoopObserver; -// @ts-expect-error - polyfill for tests +// @ts-expect-error - test polyfill global.IntersectionObserver = global.IntersectionObserver ?? NoopObserver; Object.defineProperty(window, "matchMedia", { @@ -34,7 +33,6 @@ Object.defineProperty(window, "matchMedia", { })), }); -// ---- Next.js shims ---- type LinkProps = { href: string; children?: React.ReactNode; @@ -50,160 +48,103 @@ jest.mock("next/link", () => { return LinkMock; }); -jest.mock("next/navigation", () => ({ - useRouter: () => ({ - push: jest.fn(), - replace: jest.fn(), - back: jest.fn(), - prefetch: jest.fn(), - refresh: jest.fn(), - }), - useSearchParams: () => new URLSearchParams(), - usePathname: () => "/glossary", - useParams: () => ({}), -})); +const mockTerms = [ + { + id: "agent", + term: "Agent", + definition: "Autonomous software actor", + relatedTerms: ["Tool"], + seeAlso: ["MCP"], + }, + { + id: "mcp", + term: "MCP", + definition: "Model Context Protocol", + relatedTerms: ["Agent"], + seeAlso: [], + }, + { + id: "tool", + term: "Tool", + definition: "External capability used by agents", + relatedTerms: ["Agent"], + seeAlso: [], + }, +]; describe("/glossary page", () => { - test("renders without crashing", () => { - const { container } = render(); - expect(container).toBeInTheDocument(); + beforeEach(() => { + jest.useFakeTimers(); + global.fetch = jest.fn(async (input: RequestInfo | URL) => { + const url = String(input); + const params = new URL(url, "http://localhost").searchParams; + const search = params.get("search")?.toLowerCase() ?? ""; + const letter = params.get("letter"); + + let terms = [...mockTerms]; + if (search) { + terms = terms.filter( + (t) => + t.term.toLowerCase().includes(search) || + t.definition.toLowerCase().includes(search) + ); + } + if (letter) { + terms = terms.filter((t) => t.term.startsWith(letter)); + } + + const letters = Array.from(new Set(mockTerms.map((t) => t.term[0].toUpperCase()))); + return { + ok: true, + json: async () => ({ terms, total: terms.length, letters }), + } as Response; + }) as jest.Mock; }); - test("displays hero section with title", () => { - const { getByText } = render(); - expect(getByText("📖 Agent Terminology")).toBeInTheDocument(); - expect( - getByText("Your comprehensive guide to AI agent concepts and protocols") - ).toBeInTheDocument(); + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); }); - test("displays search input", () => { - const { getByPlaceholderText } = render(); - const searchInput = getByPlaceholderText( - "Search terms, definitions, or concepts..." - ); - expect(searchInput).toBeInTheDocument(); - }); - - test("displays alphabet navigation", () => { - const { getByLabelText } = render(); - expect(getByLabelText("Filter by letter A")).toBeInTheDocument(); - expect(getByLabelText("Filter by letter Z")).toBeInTheDocument(); - }); - - test("displays glossary terms", () => { - const { getAllByText } = render(); - // Check for some key terms that should be in the glossary - // Use getAllByText since terms appear multiple times (as headings and as links) - expect(getAllByText("Agent").length).toBeGreaterThan(0); - expect(getAllByText("MCP").length).toBeGreaterThan(0); - expect(getAllByText("Tool").length).toBeGreaterThan(0); - }); - - test("search functionality filters terms", async () => { - const { getByPlaceholderText, getAllByText } = render( - - ); - const searchInput = getByPlaceholderText( - "Search terms, definitions, or concepts..." - ); - - // Search for "agent" - fireEvent.change(searchInput, { target: { value: "agent" } }); - - await waitFor(() => { - // Use getAllByText since "Agent" appears multiple times - expect(getAllByText("Agent").length).toBeGreaterThan(0); - // Terms not matching "agent" should not be visible or should be filtered out - }); - }); - - test("alphabet filter works", async () => { - const { getByLabelText } = render(); - const letterA = getByLabelText("Filter by letter A"); - - // Click on letter A - fireEvent.click(letterA); - - await waitFor(() => { - // The letter A button should be highlighted - expect(letterA).toHaveClass("bg-[#06D6A0]"); - }); - }); - - test("clear filters button appears when filtering", async () => { - const { getByPlaceholderText, getByText } = render(); - const searchInput = getByPlaceholderText( - "Search terms, definitions, or concepts..." - ); - - // Trigger search - fireEvent.change(searchInput, { target: { value: "test" } }); + test("renders hero and search input", async () => { + render(); + expect(screen.getByText("📖 Agent Terminology")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Search terms or definitions...")).toBeInTheDocument(); - await waitFor(() => { - expect(getByText("Clear filters")).toBeInTheDocument(); + await act(async () => { + jest.advanceTimersByTime(350); }); - }); - - test("displays related terms section", () => { - const { getAllByText } = render(); - // Should have at least one "Related:" label - const relatedLabels = getAllByText("Related:"); - expect(relatedLabels.length).toBeGreaterThan(0); - }); - test("displays see also section", () => { - const { getAllByText } = render(); - // Should have at least one "See also:" label - const seeAlsoLabels = getAllByText("See also:"); - expect(seeAlsoLabels.length).toBeGreaterThan(0); + await waitFor(() => expect(screen.getAllByText("Agent").length).toBeGreaterThan(0)); }); - test("displays CTA section", () => { - const { getByText } = render(); - expect(getByText("Ready to Build?")).toBeInTheDocument(); - expect(getByText("Browse Skills →")).toBeInTheDocument(); - expect(getByText("Start Learning")).toBeInTheDocument(); - }); - - test("displays empty state when no results found", async () => { - const { getByPlaceholderText, getByText } = render(); - const searchInput = getByPlaceholderText( - "Search terms, definitions, or concepts..." - ); + test("search filters terms", async () => { + render(); + const input = screen.getByPlaceholderText("Search terms or definitions..."); - // Search for something that doesn't exist - fireEvent.change(searchInput, { - target: { value: "xyznonexistentterm123" }, + fireEvent.change(input, { target: { value: "protocol" } }); + await act(async () => { + jest.advanceTimersByTime(350); }); await waitFor(() => { - expect(getByText("No terms found")).toBeInTheDocument(); - expect(getByText("Show all terms")).toBeInTheDocument(); + expect(screen.getByText("MCP")).toBeInTheDocument(); + expect(screen.queryByText("Tool")).not.toBeInTheDocument(); }); }); - test("shows result count when filtering", async () => { - const { getByPlaceholderText, getByText } = render(); - const searchInput = getByPlaceholderText( - "Search terms, definitions, or concepts..." - ); + test("shows empty state when no results", async () => { + render(); + const input = screen.getByPlaceholderText("Search terms or definitions..."); - // Search for "protocol" - fireEvent.change(searchInput, { target: { value: "protocol" } }); + fireEvent.change(input, { target: { value: "xyznonexistent" } }); + await act(async () => { + jest.advanceTimersByTime(350); + }); await waitFor(() => { - // Should show "X terms found" or "X term found" - const resultText = screen.getByText(/\d+ terms? found/); - expect(resultText).toBeInTheDocument(); + expect(screen.getByText("No terms found")).toBeInTheDocument(); + expect(screen.getByText("Show all terms")).toBeInTheDocument(); }); }); - - test("contains minimum 30 terms", () => { - render(); - // Count the number of term entries by checking for elements with specific structure - // Each term has an id attribute on its container - const termElements = document.querySelectorAll("[id][class*='scroll-mt']"); - expect(termElements.length).toBeGreaterThanOrEqual(30); - }); }); diff --git a/__tests__/partners-page.test.tsx b/__tests__/partners-page.test.tsx index 359c3b97..7d5df9ac 100644 --- a/__tests__/partners-page.test.tsx +++ b/__tests__/partners-page.test.tsx @@ -1,9 +1,8 @@ /** @jest-environment jsdom */ import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import PartnersPage from "@/app/partners/page"; -import partnersData from "@/data/partners.json"; type LinkProps = { href: string; @@ -20,208 +19,77 @@ jest.mock("next/link", () => { return LinkMock; }); +const mockPartners = [ + { + id: "1", + name: "OpenClaw", + slug: "openclaw", + icon: "🛠️", + description: "Agent runtime", + tier: "Gold", + integrationType: "API", + type: "partner", + website: "https://openclaw.example", + }, + { + id: "2", + name: "Supabase", + slug: "supabase", + icon: "🟢", + description: "Database backend", + tier: "Silver", + integrationType: "SDK", + type: "sponsor", + website: "https://supabase.example", + }, +] as const; + describe("Partners Page", () => { - it("renders the partners page", () => { - const { container } = render(); - expect(container).toBeInTheDocument(); - }); + beforeEach(() => { + global.fetch = jest.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); - it("displays the page title", () => { - render(); - expect(screen.getByRole("heading", { name: /partners & integrations/i })).toBeInTheDocument(); - }); + if (url.includes("/api/partners/apply") && init?.method === "POST") { + return { ok: true, json: async () => ({ message: "Application received." }) } as Response; + } - it("displays partner count", () => { - render(); - expect(screen.getByText(`${partnersData.length} verified partners`)).toBeInTheDocument(); - }); + if (url.includes("/api/partners")) { + return { + ok: true, + json: async () => ({ partners: mockPartners, total: mockPartners.length }), + } as Response; + } - it("renders search input", () => { - render(); - const searchInput = screen.getByPlaceholderText(/search partners/i); - expect(searchInput).toBeInTheDocument(); + return { ok: false, json: async () => ({}) } as Response; + }) as jest.Mock; }); - it("renders tier filter buttons", () => { + it("renders title and loaded partner count", async () => { render(); - expect(screen.getByText("Filter by Tier")).toBeInTheDocument(); - const buttons = screen.getAllByRole("button"); - expect(buttons.some(btn => btn.textContent === "Gold")).toBe(true); - expect(buttons.some(btn => btn.textContent === "Silver")).toBe(true); - expect(buttons.some(btn => btn.textContent === "Bronze")).toBe(true); - expect(buttons.some(btn => btn.textContent === "Community")).toBe(true); - }); - - it("renders integration type filter buttons", () => { - render(); - const buttons = screen.getAllByRole("button"); - const apiButton = buttons.find(btn => btn.textContent === "API"); - const sdkButton = buttons.find(btn => btn.textContent === "SDK"); - const pluginButton = buttons.find(btn => btn.textContent === "Plugin"); - const serviceButton = buttons.find(btn => btn.textContent === "Service"); - - expect(apiButton).toBeInTheDocument(); - expect(sdkButton).toBeInTheDocument(); - expect(pluginButton).toBeInTheDocument(); - expect(serviceButton).toBeInTheDocument(); - }); - it("displays all partners by default", () => { - render(); - const goldPartners = partnersData.filter(p => p.tier === "Gold"); - goldPartners.forEach(partner => { - expect(screen.getByText(partner.name)).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: /partners & sponsors/i })).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(`${mockPartners.length} listed organizations`)).toBeInTheDocument(); }); }); - it("filters partners by tier", () => { + it("filters by search", async () => { render(); - const goldButton = screen.getByRole("button", { name: /^gold$/i }); - fireEvent.click(goldButton); - - const goldPartners = partnersData.filter(p => p.tier === "Gold"); - const silverPartners = partnersData.filter(p => p.tier === "Silver"); - - goldPartners.forEach(partner => { - expect(screen.getByText(partner.name)).toBeInTheDocument(); - }); - - silverPartners.forEach(partner => { - expect(screen.queryByText(partner.name)).not.toBeInTheDocument(); - }); - }); - it("filters partners by integration type", () => { - render(); - const buttons = screen.getAllByRole("button"); - const apiButton = buttons.find(btn => btn.textContent === "API") as HTMLElement; - fireEvent.click(apiButton); - - const apiPartners = partnersData.filter(p => p.integrationType === "API"); - const sdkPartners = partnersData.filter(p => p.integrationType === "SDK"); - - apiPartners.forEach(partner => { - expect(screen.getByText(partner.name)).toBeInTheDocument(); - }); - - sdkPartners.forEach(partner => { - expect(screen.queryByText(partner.name)).not.toBeInTheDocument(); + await waitFor(() => expect(screen.getByText("OpenClaw")).toBeInTheDocument()); + + fireEvent.change(screen.getByPlaceholderText(/search partners/i), { + target: { value: "OpenClaw" }, }); - }); - it("filters partners by search query", () => { - render(); - const searchInput = screen.getByPlaceholderText(/search partners/i); - fireEvent.change(searchInput, { target: { value: "OpenClaw" } }); - expect(screen.getByText("OpenClaw")).toBeInTheDocument(); expect(screen.queryByText("Supabase")).not.toBeInTheDocument(); }); - it("shows no results message when filters match nothing", () => { - render(); - const searchInput = screen.getByPlaceholderText(/search partners/i); - fireEvent.change(searchInput, { target: { value: "nonexistent partner xyz" } }); - - expect(screen.getByText(/no partners found/i)).toBeInTheDocument(); - }); - - it("displays partner cards with correct information", () => { - render(); - const openclaw = partnersData.find(p => p.slug === "openclaw"); - - expect(screen.getByText(openclaw!.name)).toBeInTheDocument(); - expect(screen.getByText(openclaw!.description)).toBeInTheDocument(); - const tierBadges = screen.getAllByText(openclaw!.tier); - expect(tierBadges.length).toBeGreaterThan(0); - const integrationTypeBadges = screen.getAllByText(openclaw!.integrationType); - expect(integrationTypeBadges.length).toBeGreaterThan(0); - }); - - it("displays partner icons", () => { + it("shows apply section", async () => { render(); - partnersData.forEach(partner => { - expect(screen.getByText(partner.icon)).toBeInTheDocument(); - }); - }); - - it("has links to partner detail pages", () => { - render(); - partnersData.forEach(partner => { - const links = screen.getAllByRole("link"); - const partnerLink = links.find(link => - link.getAttribute("href") === `/partners/${partner.slug}` - ); - expect(partnerLink).toBeInTheDocument(); - }); - }); - - it("displays become a partner CTA section", () => { - render(); - expect(screen.getByRole("heading", { name: /become a partner/i })).toBeInTheDocument(); - }); - - it("displays partner benefits", () => { - render(); - expect(screen.getByText(/featured placement/i)).toBeInTheDocument(); - expect(screen.getByText(/co-marketing/i)).toBeInTheDocument(); - expect(screen.getByText(/technical support/i)).toBeInTheDocument(); - expect(screen.getByText(/community access/i)).toBeInTheDocument(); - expect(screen.getByText(/early access/i)).toBeInTheDocument(); - expect(screen.getByText(/partner badge/i)).toBeInTheDocument(); - }); - it("has apply for partnership link", () => { - render(); - const applyLink = screen.getByRole("link", { name: /apply for partnership/i }); - expect(applyLink).toHaveAttribute("href", "/contact"); - }); - - it("has view integration guides link", () => { - render(); - const guidesLink = screen.getByRole("link", { name: /view integration guides/i }); - expect(guidesLink).toHaveAttribute("href", "/guides"); - }); - - it("combines tier and type filters correctly", () => { - render(); - - // Filter by Gold tier - const goldButton = screen.getByRole("button", { name: /^gold$/i }); - fireEvent.click(goldButton); - - // Then filter by API type - const buttons = screen.getAllByRole("button"); - const apiButton = buttons.find(btn => btn.textContent === "API") as HTMLElement; - fireEvent.click(apiButton); - - // Should only show Gold + API partners - const goldApiPartners = partnersData.filter( - p => p.tier === "Gold" && p.integrationType === "API" - ); - const otherPartners = partnersData.filter( - p => p.tier !== "Gold" || p.integrationType !== "API" - ); - - goldApiPartners.forEach(partner => { - expect(screen.getByText(partner.name)).toBeInTheDocument(); - }); - }); - - it("resets to all partners when clicking All button", () => { - render(); - - // Filter by Gold - const goldButton = screen.getByRole("button", { name: /^gold$/i }); - fireEvent.click(goldButton); - - // Reset to All - const allButtons = screen.getAllByRole("button", { name: /^all$/i }); - fireEvent.click(allButtons[0]); - - // Should show all partners again - partnersData.forEach(partner => { - expect(screen.getByText(partner.name)).toBeInTheDocument(); - }); + expect(screen.getByText("Apply to Join")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /submit application/i })).toBeInTheDocument(); }); }); diff --git a/__tests__/render-missing-pages-151.test.tsx b/__tests__/render-missing-pages-151.test.tsx index 9ba15f7b..bd257312 100644 --- a/__tests__/render-missing-pages-151.test.tsx +++ b/__tests__/render-missing-pages-151.test.tsx @@ -92,6 +92,15 @@ jest.mock('next/navigation', () => { }; }); +jest.mock('next/headers', () => ({ + headers: () => + new Map([ + ['host', 'localhost:3000'], + ['x-forwarded-proto', 'http'], + ]), + cookies: () => ({ get: () => undefined, getAll: () => [] }), +})); + // ---- Shared component mocks (keep render tests lightweight/deterministic) ---- jest.mock('@/components/mobile-nav', () => ({ @@ -270,6 +279,27 @@ jest.mock('@/lib/data', () => { }; }); +beforeAll(() => { + global.fetch = jest.fn(async (input: RequestInfo | URL) => { + const url = String(input); + + if (url.includes('/api/blog')) { + return { ok: true, json: async () => ({ posts: [], total: 0 }) } as Response; + } + if (url.includes('/api/changelog')) { + return { ok: true, json: async () => ({ entries: [], total: 0 }) } as Response; + } + if (url.includes('/api/status/history')) { + return { ok: true, json: async () => ({ history: [] }) } as Response; + } + if (url.includes('/api/status')) { + return { ok: true, json: async () => ({ services: [], overall: 'operational' }) } as Response; + } + + return { ok: true, json: async () => ({}) } as Response; + }) as jest.Mock; +}); + async function renderPageModule(importPath: string, props: Record = {}) { const mod: Record = await import(importPath); const Page = mod.default; diff --git a/__tests__/showcase-page.test.tsx b/__tests__/showcase-page.test.tsx index 8f5c27ca..b7dd3d69 100644 --- a/__tests__/showcase-page.test.tsx +++ b/__tests__/showcase-page.test.tsx @@ -1,108 +1,39 @@ /** @jest-environment jsdom */ -import React from 'react'; -import { render, screen } from '@testing-library/react'; +import React from "react"; +import { render, screen } from "@testing-library/react"; type LinkProps = { href: string; children?: React.ReactNode; } & React.AnchorHTMLAttributes; -jest.mock('next/link', () => { +jest.mock("next/link", () => { const LinkMock = ({ href, children, ...props }: LinkProps) => ( {children} ); - LinkMock.displayName = 'Link'; + LinkMock.displayName = "Link"; return LinkMock; }); -// Mock the client component -jest.mock('@/app/showcase/showcase-client', () => ({ - ShowcaseClient: ({ agents }: { agents: unknown[] }) => ( -
-
{agents.length} agents in hall of fame
-
- ), +jest.mock("@/app/showcase/showcase-client", () => ({ + ShowcaseClient: () =>
Mocked Showcase Client
, })); -import ShowcasePage from '@/app/showcase/page'; +import ShowcasePage from "@/app/showcase/page"; -describe('Showcase Page', () => { - test('renders showcase heading', () => { +describe("Showcase Page", () => { + test("renders heading and description", () => { render(); - - expect(screen.getByRole('heading', { name: /🏆 agent showcase/i, level: 1 })).toBeInTheDocument(); - }); - - test('renders page description', () => { - render(); - - expect(screen.getByText(/celebrating the best ai agents/i)).toBeInTheDocument(); - }); - - test('renders stats banner with all metrics', () => { - render(); - - // Should show all 4 stats - expect(screen.getByText(/total agents/i)).toBeInTheDocument(); - expect(screen.getByText(/skills installed/i)).toBeInTheDocument(); - expect(screen.getByText(/tasks completed/i)).toBeInTheDocument(); - expect(screen.getByText(/community members/i)).toBeInTheDocument(); - }); - - test('renders agent of the month section', () => { - render(); - - expect(screen.getByRole('heading', { name: /agent of the month/i })).toBeInTheDocument(); - }); - test('renders featured agents', () => { - render(); - - // Should render featured agents (Kai, Scout, Link) - expect(screen.getByText('Kai')).toBeInTheDocument(); - expect(screen.getByText('Scout')).toBeInTheDocument(); - expect(screen.getByText('Link')).toBeInTheDocument(); - }); - - test('renders hall of fame section', () => { - render(); - - expect(screen.getByRole('heading', { name: /hall of fame/i })).toBeInTheDocument(); - }); - - test('renders ShowcaseClient component', () => { - render(); - - expect(screen.getByTestId('showcase-client')).toBeInTheDocument(); - }); - - test('renders submit your agent CTA', () => { - render(); - - expect(screen.getByRole('heading', { name: /is your agent showcase-worthy/i })).toBeInTheDocument(); - - const submitLink = screen.getByRole('link', { name: /submit your agent/i }); - expect(submitLink).toBeInTheDocument(); - expect(submitLink).toHaveAttribute('href', '/submit'); - }); - - test('displays achievements for featured agents', () => { - render(); - - // Check for achievements sections (there are 3 featured agents) - const achievements = screen.getAllByText('Achievements'); - expect(achievements.length).toBeGreaterThan(0); + expect(screen.getByRole("heading", { name: /community showcase/i, level: 1 })).toBeInTheDocument(); + expect(screen.getByText(/discover what other builders are shipping/i)).toBeInTheDocument(); }); - test('displays trust scores for featured agents', () => { + test("renders showcase client component", () => { render(); - - // All featured agents should show trust scores - expect(screen.getByText(/98%/)).toBeInTheDocument(); // Kai - expect(screen.getByText(/96%/)).toBeInTheDocument(); // Scout - expect(screen.getByText(/97%/)).toBeInTheDocument(); // Link + expect(screen.getByTestId("showcase-client")).toBeInTheDocument(); }); }); diff --git a/__tests__/sponsor-page.test.tsx b/__tests__/sponsor-page.test.tsx index 3b2e1a0f..3b537c81 100644 --- a/__tests__/sponsor-page.test.tsx +++ b/__tests__/sponsor-page.test.tsx @@ -20,98 +20,30 @@ jest.mock("next/link", () => { }); describe("Sponsor Page", () => { - it("renders the sponsor page", () => { - const { container } = render(); - expect(container).toBeInTheDocument(); - }); - - it("displays the hero heading", () => { - render(); - expect( - screen.getByText("Support the Agent Ecosystem") - ).toBeInTheDocument(); - }); + it("renders hero and core sections", async () => { + const jsx = await SponsorPage(); + render(jsx); - it("displays the mission statement", () => { - render(); - expect( - screen.getByText(/forAgents.dev is built by the community/) - ).toBeInTheDocument(); + expect(screen.getByText("Support the Agent Ecosystem")).toBeInTheDocument(); + expect(screen.getByText("Sponsor Tiers")).toBeInTheDocument(); + expect(screen.getByText("Why Sponsor?")).toBeInTheDocument(); + expect(screen.getByText("Current Sponsors")).toBeInTheDocument(); }); - it("displays all sponsor tiers", () => { - render(); - const individualTiers = screen.getAllByText("Individual"); - const teamTiers = screen.getAllByText("Team"); - const enterpriseTiers = screen.getAllByText("Enterprise"); - const customTiers = screen.getAllByText("Custom"); - - expect(individualTiers.length).toBeGreaterThan(0); - expect(teamTiers.length).toBeGreaterThan(0); - expect(enterpriseTiers.length).toBeGreaterThan(0); - expect(customTiers.length).toBeGreaterThan(0); - }); + it("shows tier pricing", async () => { + const jsx = await SponsorPage(); + render(jsx); - it("displays tier prices", () => { - render(); expect(screen.getByText("$5")).toBeInTheDocument(); expect(screen.getByText("$25")).toBeInTheDocument(); expect(screen.getByText("$100")).toBeInTheDocument(); }); - it("displays sponsor CTA buttons", () => { - render(); - const sponsorButtons = screen.getAllByText("Sponsor"); - expect(sponsorButtons.length).toBeGreaterThan(0); - }); - - it("displays the Why Sponsor section", () => { - render(); - expect(screen.getByText("Why Sponsor?")).toBeInTheDocument(); - }); - - it("displays all four benefit cards", () => { - render(); - expect(screen.getByText("Visibility")).toBeInTheDocument(); - expect(screen.getByText("Priority Support")).toBeInTheDocument(); - expect(screen.getByText("Early Access")).toBeInTheDocument(); - expect(screen.getByText("Community Recognition")).toBeInTheDocument(); - }); - - it("displays the Our Sponsors section", () => { - render(); - expect(screen.getByText("Our Sponsors")).toBeInTheDocument(); - }); - - it("displays impact stats", () => { - render(); - expect(screen.getByText("Skills Funded")).toBeInTheDocument(); - expect(screen.getByText("Agents Supported")).toBeInTheDocument(); - expect(screen.getByText("Community Members")).toBeInTheDocument(); - }); - - it("displays impact stat values", () => { - render(); - expect(screen.getByText("127")).toBeInTheDocument(); - expect(screen.getByText("2,400+")).toBeInTheDocument(); - expect(screen.getByText("18,000+")).toBeInTheDocument(); - }); - - it("displays sponsor tier badges", () => { - render(); - // Check that tier names appear in the tier cards - const tierCards = screen.getAllByText("Individual"); - expect(tierCards.length).toBeGreaterThan(0); - }); - - it("displays the final CTA section", () => { - render(); - expect(screen.getByText("Ready to make an impact?")).toBeInTheDocument(); - }); + it("renders CTA buttons", async () => { + const jsx = await SponsorPage(); + render(jsx); - it("displays contact link", () => { - render(); - const contactButtons = screen.getAllByText("Contact Us"); - expect(contactButtons.length).toBeGreaterThan(0); + expect(screen.getAllByText("Apply").length).toBeGreaterThan(0); + expect(screen.getByText("Become a Sponsor")).toBeInTheDocument(); }); }); diff --git a/__tests__/status-page.test.tsx b/__tests__/status-page.test.tsx index 5d7fc1e8..5250676d 100644 --- a/__tests__/status-page.test.tsx +++ b/__tests__/status-page.test.tsx @@ -1,7 +1,7 @@ /** @jest-environment jsdom */ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import StatusPage from "../src/app/status/page"; type LinkProps = { @@ -19,107 +19,67 @@ jest.mock("next/link", () => { return LinkMock; }); +const statusPayload = { + services: [ + { + name: "API", + status: "operational", + latencyMs: 42, + lastCheck: "2026-02-09T12:00:00.000Z", + }, + { + name: "Website", + status: "degraded", + latencyMs: 180, + lastCheck: "2026-02-09T12:00:00.000Z", + }, + ], + overall: "degraded", +}; + +const historyPayload = { + history: [ + { date: "2026-02-03", uptime: 99.9, incidents: 0 }, + { date: "2026-02-04", uptime: 99.5, incidents: 1 }, + ], +}; + describe("Status Page", () => { beforeEach(() => { jest.useFakeTimers(); + global.fetch = jest.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.includes("/api/status/history")) { + return { ok: true, json: async () => historyPayload } as Response; + } + if (url.includes("/api/status")) { + return { ok: true, json: async () => statusPayload } as Response; + } + return { ok: false, json: async () => ({}) } as Response; + }) as jest.Mock; }); afterEach(() => { + jest.runOnlyPendingTimers(); jest.useRealTimers(); }); - it("renders the status page", () => { - const { container } = render(); - expect(container).toBeInTheDocument(); - }); - - it("displays the page title", () => { - render(); - expect(screen.getByText("System Status")).toBeInTheDocument(); - }); - - it("displays overall status banner", () => { + it("renders title and services after fetch", async () => { render(); - expect(screen.getByText("All Systems Operational")).toBeInTheDocument(); - }); - it("displays all service components", () => { - render(); - // Service names appear in both the services section and incident affected services - const apiElements = screen.getAllByText("API"); - expect(apiElements.length).toBeGreaterThan(0); - const websiteElements = screen.getAllByText("Website"); - expect(websiteElements.length).toBeGreaterThan(0); - const mcpElements = screen.getAllByText("MCP Registry"); - expect(mcpElements.length).toBeGreaterThan(0); - const supabaseElements = screen.getAllByText("Supabase"); - expect(supabaseElements.length).toBeGreaterThan(0); - const cdnElements = screen.getAllByText("CDN"); - expect(cdnElements.length).toBeGreaterThan(0); - }); - - it("displays services section heading", () => { - render(); - expect(screen.getByText("Services")).toBeInTheDocument(); - }); - - it("displays uptime metrics", () => { - render(); - const uptimeElements = screen.getAllByText(/Uptime \(30d\)/i); - expect(uptimeElements.length).toBeGreaterThan(0); - }); - - it("displays response time metrics", () => { - render(); - const responseTimeElements = screen.getAllByText(/Response Time/i); - expect(responseTimeElements.length).toBeGreaterThan(0); - }); - - it("displays incident history section", () => { - render(); - expect(screen.getByText("Incident History")).toBeInTheDocument(); - }); - - it("displays at least 5 incidents", () => { - render(); - // Check for resolved badge which should appear for each incident - const resolvedBadges = screen.getAllByText(/resolved/i); - expect(resolvedBadges.length).toBeGreaterThanOrEqual(5); - }); - - it("displays incident titles", () => { - render(); - expect(screen.getByText("Brief API Latency Spike")).toBeInTheDocument(); - expect( - screen.getByText("CDN Cache Invalidation Issue") - ).toBeInTheDocument(); - }); - - it("displays affected services for incidents", () => { - render(); - // "Affected Services:" appears multiple times (once per incident) - const affectedServicesElements = screen.getAllByText("Affected Services:"); - expect(affectedServicesElements.length).toBeGreaterThanOrEqual(5); - }); - - it("displays subscribe to updates section", () => { - render(); - expect(screen.getByText("Stay Updated")).toBeInTheDocument(); - }); + expect(screen.getByText("System Status")).toBeInTheDocument(); - it("displays last updated timestamp", () => { - render(); - expect(screen.getByText(/Last updated:/)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText("Some Systems Degraded")).toBeInTheDocument(); + expect(screen.getByText("API")).toBeInTheDocument(); + expect(screen.getByText("Website")).toBeInTheDocument(); + expect(screen.getByText("Uptime (Last 7 Days)")).toBeInTheDocument(); + }); }); it("renders structured data for SEO", () => { const { container } = render(); const scriptTag = container.querySelector('script[type="application/ld+json"]'); expect(scriptTag).toBeInTheDocument(); - if (scriptTag) { - const jsonLd = JSON.parse(scriptTag.textContent || "{}"); - expect(jsonLd["@type"]).toBe("WebPage"); - expect(jsonLd.name).toContain("Status"); - } }); }); diff --git a/__tests__/untested-pages-render.test.tsx b/__tests__/untested-pages-render.test.tsx index 45869d8a..5f6b3102 100644 --- a/__tests__/untested-pages-render.test.tsx +++ b/__tests__/untested-pages-render.test.tsx @@ -160,6 +160,10 @@ jest.mock("@/components/skill-page-client", () => ({ SkillPageClient: () =>
, })); +jest.mock("@/components/skill-install-cta", () => ({ + SkillInstallCta: () =>
, +})); + // Mock subscribe success client (uses useSearchParams) jest.mock("@/app/subscribe/success/success-client", () => ({ SubscribeSuccessClient: () =>
, @@ -305,17 +309,53 @@ jest.mock("@/lib/testimonials", () => ({ ], })); -// Mock fetch - return ok: false for unknown API calls (triggers notFound in pages) +// Mock fetch with route-aware defaults for page render tests const originalFetch = global.fetch; beforeAll(() => { - (global as Record).fetch = jest.fn(() => - Promise.resolve({ + (global as Record).fetch = jest.fn((input: RequestInfo | URL) => { + const url = String(input); + + if (url.includes('/api/blog/example-post')) { + return Promise.resolve({ + ok: true, + status: 200, + json: () => + Promise.resolve({ + slug: 'example-post', + title: 'Example Post', + excerpt: 'Example excerpt', + content: '# Heading\nBody copy', + tags: ['example'], + author: 'Team', + publishedAt: '2026-01-01T00:00:00.000Z', + readingTime: '3 min read', + }), + }); + } + + if (url.includes('/api/blog?sort=recent')) { + return Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve({ posts: [], total: 0 }), + }); + } + + if (url.includes('/api/changelog')) { + return Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve({ entries: [], total: 0 }), + }); + } + + return Promise.resolve({ ok: false, status: 404, json: () => Promise.resolve({}), - text: () => Promise.resolve(""), - }) - ); + text: () => Promise.resolve(''), + }); + }); }); afterAll(() => { global.fetch = originalFetch; @@ -397,6 +437,13 @@ describe("Untested Pages – Render Tests", () => { test.each(syncPages)("/%s renders without crashing", async (_name, importFn) => { const mod = await importFn(); const Page = mod.default; + + if (Page?.constructor?.name === "AsyncFunction") { + const { container } = await renderServerComponent(Page as (...args: unknown[]) => unknown); + expect(container).toBeInTheDocument(); + return; + } + const { container } = render(); expect(container).toBeInTheDocument(); }); @@ -418,8 +465,9 @@ describe("Untested Pages – Render Tests", () => { test("/blog/[slug] renders without crashing", async () => { const mod = await import("@/app/blog/[slug]/page"); - const Page = mod.default; - const { container } = render(); + const { container } = await renderServerComponent(mod.default, { + params: Promise.resolve({ slug: "example-post" }), + }); expect(container).toBeInTheDocument(); });