From 0f5f1206af7a812099876ab530d7b3a6cbf02d33 Mon Sep 17 00:00:00 2001 From: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:31:33 -0400 Subject: [PATCH] test: cover DocPage routeMap miss and LanguageSwitcher navigation error arm Two reachable branches in the docs app had no test: - getPageContent in src/app/docs/[...slug]/page.tsx: a slug present in buildPageMap's routeMap whose mapped file is on disk in neither the project content directory nor the shared docs/content root must fall through to null so DocPage calls notFound(). No committed nav entry points at a missing file, so the route is injected by wrapping buildPageMap in the test. - LanguageSwitcher.handleLanguageChange: when router.replace throws, the error is logged, the dropdown stays open and the transitioning state is still cleared by finally. Branch coverage for the [...slug] page rises from 84.88% to 87.2% and the page's last uncovered line (207) is now exercised. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- .../LanguageSwitcher.behavior.test.tsx | 27 +++++++ .../docs-slug-page.routemap-miss.test.tsx | 74 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/__tests__/docs-slug-page.routemap-miss.test.tsx diff --git a/src/__tests__/LanguageSwitcher.behavior.test.tsx b/src/__tests__/LanguageSwitcher.behavior.test.tsx index 8274ba1..7270287 100644 --- a/src/__tests__/LanguageSwitcher.behavior.test.tsx +++ b/src/__tests__/LanguageSwitcher.behavior.test.tsx @@ -124,6 +124,33 @@ describe("LanguageSwitcher dropdown variant", () => { expect(screen.queryByRole("listbox")).toBeNull(); }); + it("logs and recovers when navigation throws, leaving the menu open", async () => { + const boom = new Error("router exploded"); + replaceMock.mockImplementation(() => { + throw boom; + }); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + try { + render(); + fireEvent.click(trigger()); + fireEvent.click(screen.getByText(localeNames.ja)); + + await act(async () => { + await vi.advanceTimersByTimeAsync(200); + }); + + expect(replaceMock).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledWith("Failed to change language:", boom); + // The catch arm skips setIsOpen(false); finally still clears the spinner. + expect(screen.getByRole("listbox")).toBeTruthy(); + expect(screen.queryByText("Switching...")).toBeNull(); + expect(trigger().hasAttribute("disabled")).toBe(false); + } finally { + errorSpy.mockRestore(); + } + }); + it("closes without navigating when the current locale is re-selected", () => { render(); fireEvent.click(trigger()); diff --git a/src/__tests__/docs-slug-page.routemap-miss.test.tsx b/src/__tests__/docs-slug-page.routemap-miss.test.tsx new file mode 100644 index 0000000..95f12d0 --- /dev/null +++ b/src/__tests__/docs-slug-page.routemap-miss.test.tsx @@ -0,0 +1,74 @@ +/** + * Covers the last arm of getPageContent in src/app/docs/[...slug]/page.tsx: + * the slug is a nav-generated route (present in buildPageMap's routeMap) but + * the mapped file exists in neither the project content directory nor the + * shared docs/content root. The lookup must fall through to `return null`, + * which DocPage turns into notFound(), rather than throwing or rendering an + * empty page. + * + * No committed nav entry points at a missing file (the sync step would fail + * the build), so the route is injected by wrapping buildPageMap. + */ +import { describe, expect, it, vi } from "vitest"; + +const NOT_FOUND = new Error("NEXT_NOT_FOUND (test sentinel)"); +vi.mock("next/navigation", async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + usePathname: () => "/docs/hive/ghost-route", + notFound: () => { + throw NOT_FOUND; + }, + }; +}); + +const GHOST_ROUTE = "ghost-route"; +const GHOST_FILE = "ghost/not-on-disk.md"; + +const { buildPageMapSpy } = vi.hoisted(() => ({ buildPageMapSpy: vi.fn() })); +vi.mock("../app/docs/page-map", async importOriginal => { + const actual = await importOriginal(); + buildPageMapSpy.mockImplementation( + (projectId?: Parameters[0]) => { + const real = actual.buildPageMap(projectId); + return { + ...real, + routeMap: { ...real.routeMap, [GHOST_ROUTE]: GHOST_FILE }, + }; + } + ); + return { ...actual, buildPageMap: buildPageMapSpy }; +}); + +const { compileMdx } = vi.hoisted(() => ({ + compileMdx: vi.fn(() => { + throw new Error( + "compileMdx must not be called when no source file resolves" + ); + }), +})); +vi.mock("nextra/compile", () => ({ compileMdx })); + +import fs from "fs"; +import path from "path"; +import { docsContentPath, getContentPath } from "../app/docs/page-map"; +import DocPage from "../app/docs/[...slug]/page"; + +describe("getPageContent — routeMap hit whose file is missing on disk", () => { + it("404s instead of rendering when the mapped file is absent from both content roots", async () => { + expect(fs.existsSync(path.join(getContentPath("hive"), GHOST_FILE))).toBe( + false + ); + expect(fs.existsSync(path.join(docsContentPath, GHOST_FILE))).toBe(false); + + await expect( + DocPage({ params: Promise.resolve({ slug: ["hive", GHOST_ROUTE] }) }) + ).rejects.toBe(NOT_FOUND); + + // The routeMap was consulted for this project and the miss never reached + // the MDX compiler. + expect(buildPageMapSpy).toHaveBeenCalledWith("hive"); + expect(compileMdx).not.toHaveBeenCalled(); + }); +});