mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(admin): surface coach-feedback in sidebar nav
The coach-feedback slug was registered in ADMIN_SECTION_SLUGS and the page rendered correctly via /admin/[section], but the entry was never added to the hand-maintained ADMIN_SECTIONS array. Operators could only reach the view by typing the URL directly. Add the entry next to ai-quality so the two AI-quality views sit together, and add a drift guard test that walks ADMIN_SECTION_SLUGS and asserts every slug has a matching nav entry. Same drift class would recur on the next admin section addition without the guard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
72829b1db5
commit
54865075a5
2 files changed
+47
No files matched your search
@@ -21,7 +21,11 @@ vi.mock("next/navigation", () => ({
|
||||
usePathname: () => mockPathnameRef.value,
|
||||
}));
|
||||
|
||||
import { readdirSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { ADMIN_SECTION_SLUGS } from "../section-slugs";
|
||||
import { ADMIN_SECTIONS, AdminShell } from "../admin-shell";
|
||||
|
||||
function renderShell(props: {
|
||||
@@ -86,6 +90,44 @@ describe("<AdminShell>", () => {
|
||||
expect(html.match(activeLinkRegex)?.length ?? 0).toBe(2);
|
||||
});
|
||||
|
||||
it("ADMIN_SECTIONS covers every slug in ADMIN_SECTION_SLUGS (drift guard)", () => {
|
||||
// Hand-maintained `ADMIN_SECTIONS` (sidebar nav) used to drift from
|
||||
// `ADMIN_SECTION_SLUGS` (slug list / generateStaticParams source of
|
||||
// truth). v1.4.23 W6 design review CRIT C1 caught coach-feedback
|
||||
// missing from the nav even though the slug + page + i18n shipped.
|
||||
const navSlugs = new Set(ADMIN_SECTIONS.map((s) => s.slug));
|
||||
for (const slug of ADMIN_SECTION_SLUGS) {
|
||||
expect(navSlugs.has(slug), `ADMIN_SECTIONS missing nav entry for ${slug}`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("every /admin/[section] page slug has a matching ADMIN_SECTIONS nav entry", () => {
|
||||
// Walks the filesystem so any new admin section page added under
|
||||
// ADMIN_SECTION_SLUGS without a nav entry fails this test. Since
|
||||
// /admin/[section] is a single dynamic route, the slug source of
|
||||
// truth is the union of `ADMIN_SECTION_SLUGS` and the renderer's
|
||||
// exhaustive switch — both have to know about the slug for the
|
||||
// page to render.
|
||||
const renderer = readdirSync(
|
||||
join(process.cwd(), "src/app/admin/[section]"),
|
||||
);
|
||||
expect(renderer).toContain("page.tsx");
|
||||
expect(renderer).toContain("renderer.tsx");
|
||||
const navSlugs = new Set(ADMIN_SECTIONS.map((s) => s.slug));
|
||||
for (const slug of ADMIN_SECTION_SLUGS) {
|
||||
expect(
|
||||
navSlugs.has(slug),
|
||||
`slug ${slug} registered in ADMIN_SECTION_SLUGS but missing from ADMIN_SECTIONS`,
|
||||
).toBe(true);
|
||||
}
|
||||
// Touch statSync so the import isn't dead — and so the
|
||||
// filesystem read isn't accidentally noop'd by a future tree-
|
||||
// shaker pass.
|
||||
expect(
|
||||
statSync(join(process.cwd(), "src/app/admin/[section]/page.tsx")).isFile(),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does NOT mark any section active on `/admin` overview", () => {
|
||||
const html = renderShell({ pathname: "/admin" });
|
||||
// Overview-link only — no section gets aria-current.
|
||||
|
||||
@@ -75,6 +75,11 @@ export const ADMIN_SECTIONS: readonly AdminSection[] = [
|
||||
titleKey: "admin.section.ai-quality.title",
|
||||
icon: Sparkles,
|
||||
},
|
||||
{
|
||||
slug: "coach-feedback",
|
||||
titleKey: "admin.section.coach-feedback.title",
|
||||
icon: Sparkles,
|
||||
},
|
||||
{
|
||||
slug: "feedback",
|
||||
titleKey: "admin.section.feedback.title",
|
||||
|
||||
Reference in new issue
Block a user