mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 12:21:31 +00:00
fix(web): Phase 3 hotfix — UsersPage.test.tsx Router context + Breadcrumbs defensive guard
CI failure on Phase 3 commit (e761ae40):
FAIL src/pages/auth/UsersPage.test.tsx > 8 tests (all)
Error: useLocation() may be used only in the context of a <Router> component.
Root cause:
Phase 3 wired <Breadcrumbs /> into PageHeader (UX-M5 closure). UsersPage
renders PageHeader at the top of its tree. UsersPage.test.tsx was the
only auth-page test file whose renderWithProviders helper lacked a
MemoryRouter wrapper — every other sibling (BreakglassPage, KeysPage,
OIDCProvidersPage, SessionsPage, RolesPage, AuthSettingsPage,
ApprovalsPage, etc.) already wraps in MemoryRouter. The 2026-05-11
MED-11 closure that shipped UsersPage + 8 tests predated Phase 3 and so
predated the need for Router context in test trees.
Fix is two-layered:
(1) Targeted — add MemoryRouter to UsersPage.test.tsx renderWithProviders
so the test tree has the same Router context the production tree gets
from <BrowserRouter> in main.tsx.
(2) Defensive — Breadcrumbs.tsx now gates useLocation() behind
useInRouterContext(). If a future test mounts PageHeader (or any
other Breadcrumbs consumer) without a Router wrapper, the component
renders null instead of crashing. The actual useLocation() + render
work moves into a BreadcrumbsInner sub-component called only after
the Router-context check passes. This prevents the same class of
failure ever happening again — any new auth-page test author who
forgets MemoryRouter will see a missing breadcrumb (cosmetic),
not 8 red test failures.
Verification (sandbox):
• TypeScript clean — npx tsc --noEmit exits 0
• UsersPage suite — 8/8 green (was 0/8 in CI)
• Breadcrumbs suite — 8/8 green
• All sibling auth tests — 72/72 green (BreakglassPage 6 + KeysPage 7
+ OIDCProvidersPage 13 + SessionsPage 11 + RolesPage 6 +
AuthSettingsPage 6 + ApprovalsPage 23). Unchanged because they
already had MemoryRouter; pinned to confirm defensive guard didn't
regress them.
CI expectation: web-test job goes from red to green on next push.
No behavior change to production — Breadcrumbs still renders identically
under <BrowserRouter> at runtime; useInRouterContext returns true and
delegates to BreadcrumbsInner unchanged.
Touches:
web/src/components/Breadcrumbs.tsx (+14 / -2)
web/src/pages/auth/UsersPage.test.tsx (+8 / -1)
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
// upgrading to data-router-driven crumbs is a future task once the
|
||||
// router migration ships.
|
||||
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Link, useLocation, useInRouterContext } from 'react-router-dom';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
|
||||
// pathSegmentLabels — map first-segment URL keys to human labels.
|
||||
@@ -126,7 +126,19 @@ function looksLikeID(s: string): boolean {
|
||||
return s.includes('-') || /^[a-f0-9]{8,}$/i.test(s);
|
||||
}
|
||||
|
||||
// Breadcrumbs is the public entry. Defensive against missing Router
|
||||
// context (a test that mounts a PageHeader without a <MemoryRouter>
|
||||
// wrapper used to crash here). useLocation() throws an invariant
|
||||
// error if there's no Router; gate it behind useInRouterContext()
|
||||
// + render the actual logic in a sibling so useLocation() is only
|
||||
// called when we know the context is present.
|
||||
export default function Breadcrumbs() {
|
||||
const inRouter = useInRouterContext();
|
||||
if (!inRouter) return null;
|
||||
return <BreadcrumbsInner />;
|
||||
}
|
||||
|
||||
function BreadcrumbsInner() {
|
||||
const { pathname } = useLocation();
|
||||
const crumbs = crumbsFor(pathname);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
// =============================================================================
|
||||
@@ -29,8 +30,13 @@ function renderWithProviders(ui: ReactNode) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
||||
});
|
||||
// MemoryRouter required because PageHeader now renders Breadcrumbs
|
||||
// (Phase 3 UX-M5), which calls useLocation() and throws when there
|
||||
// is no Router context in the tree.
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>,
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>{ui}</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user