diff --git a/src/__tests__/DocsProvider.behavior.test.tsx b/src/__tests__/DocsProvider.behavior.test.tsx
new file mode 100644
index 0000000..e134a68
--- /dev/null
+++ b/src/__tests__/DocsProvider.behavior.test.tsx
@@ -0,0 +1,296 @@
+// @vitest-environment jsdom
+//
+// Behavior tests for the docs chrome state layer and the two small widgets
+// that drive it:
+// - DocsProvider: localStorage-backed banner dismissal, menu/sidebar toggles,
+// nav-folder collapse set, and the useDocsMenu outside-provider guard
+// - MobileHeader: breadcrumb derivation from the pathname and the
+// dismiss-banner-then-toggle wiring
+// - SidebarFooter: theme toggle in full and slim variants, mounted/unmounted
+// placeholders, and the isMobile collapse-button suppression
+import React from "react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { fireEvent, render, screen } from "@testing-library/react";
+import { renderToStaticMarkup } from "react-dom/server";
+
+let pathname = "/docs/hive/overview/introduction";
+vi.mock("next/navigation", () => ({
+ usePathname: () => pathname,
+}));
+
+const setTheme = vi.fn();
+let resolvedTheme: string | undefined = "light";
+vi.mock("next-themes", () => ({
+ useTheme: () => ({ resolvedTheme, setTheme }),
+}));
+
+import { DocsProvider, useDocsMenu } from "../components/docs/DocsProvider";
+import { MobileHeader } from "../components/docs/MobileSidebarToggle";
+import { SidebarFooter } from "../components/docs/SidebarFooter";
+
+function Probe() {
+ const ctx = useDocsMenu();
+ return (
+
+ {String(ctx.menuOpen)}
+ {String(ctx.sidebarCollapsed)}
+ {String(ctx.bannerDismissed)}
+ {[...ctx.navCollapsed].sort().join(",")}
+ {String(ctx.navInitialized.current)}
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+beforeEach(() => {
+ pathname = "/docs/hive/overview/introduction";
+ resolvedTheme = "light";
+ setTheme.mockReset();
+ localStorage.clear();
+});
+
+afterEach(() => {
+ localStorage.clear();
+});
+
+describe("DocsProvider", () => {
+ it("starts with closed menu, expanded sidebar, visible banner and empty nav set", () => {
+ render(
+
+
+
+ );
+ expect(screen.getByTestId("menu").textContent).toBe("false");
+ expect(screen.getByTestId("sidebar").textContent).toBe("false");
+ expect(screen.getByTestId("banner").textContent).toBe("false");
+ expect(screen.getByTestId("nav").textContent).toBe("");
+ expect(screen.getByTestId("init").textContent).toBe("false");
+ });
+
+ it("reads a previously dismissed banner from localStorage on mount", () => {
+ localStorage.setItem("docs-banner-dismissed", "true");
+ render(
+
+
+
+ );
+ expect(screen.getByTestId("banner").textContent).toBe("true");
+ });
+
+ it("ignores a non-'true' localStorage value for the banner flag", () => {
+ localStorage.setItem("docs-banner-dismissed", "yes");
+ render(
+
+
+
+ );
+ expect(screen.getByTestId("banner").textContent).toBe("false");
+ });
+
+ it("dismissBanner flips state and persists the flag; setBannerDismissed does not persist", () => {
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getByText("dismiss-banner"));
+ expect(screen.getByTestId("banner").textContent).toBe("true");
+ expect(localStorage.getItem("docs-banner-dismissed")).toBe("true");
+
+ fireEvent.click(screen.getByText("restore-banner"));
+ expect(screen.getByTestId("banner").textContent).toBe("false");
+ // The raw setter is an in-memory override only.
+ expect(localStorage.getItem("docs-banner-dismissed")).toBe("true");
+ });
+
+ it("toggleMenu and setMenuOpen drive menuOpen", () => {
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getByText("toggle-menu"));
+ expect(screen.getByTestId("menu").textContent).toBe("true");
+ fireEvent.click(screen.getByText("toggle-menu"));
+ expect(screen.getByTestId("menu").textContent).toBe("false");
+ fireEvent.click(screen.getByText("open-menu"));
+ expect(screen.getByTestId("menu").textContent).toBe("true");
+ });
+
+ it("toggleSidebar and setSidebarCollapsed drive sidebarCollapsed", () => {
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getByText("toggle-sidebar"));
+ expect(screen.getByTestId("sidebar").textContent).toBe("true");
+ fireEvent.click(screen.getByText("toggle-sidebar"));
+ expect(screen.getByTestId("sidebar").textContent).toBe("false");
+ fireEvent.click(screen.getByText("collapse-sidebar"));
+ expect(screen.getByTestId("sidebar").textContent).toBe("true");
+ });
+
+ it("toggleNavCollapsed adds then removes keys without mutating the previous set", () => {
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getByText("nav-hive"));
+ expect(screen.getByTestId("nav").textContent).toBe("hive");
+ fireEvent.click(screen.getByText("nav-console"));
+ expect(screen.getByTestId("nav").textContent).toBe("console,hive");
+ fireEvent.click(screen.getByText("nav-hive"));
+ expect(screen.getByTestId("nav").textContent).toBe("console");
+ fireEvent.click(screen.getByText("nav-set"));
+ expect(screen.getByTestId("nav").textContent).toBe("x,y");
+ });
+
+ it("exposes a navInitialized ref that persists across re-renders", () => {
+ function RefProbe() {
+ const { navInitialized, toggleMenu } = useDocsMenu();
+ return (
+
+ );
+ }
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getByText("mark-false"));
+ expect(screen.getByText("mark-true")).toBeTruthy();
+ });
+
+ it("useDocsMenu throws a descriptive error outside a DocsProvider", () => {
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ try {
+ expect(() => render()).toThrow(
+ "useDocsMenu must be used within a DocsProvider"
+ );
+ } finally {
+ spy.mockRestore();
+ }
+ });
+});
+
+describe("MobileHeader", () => {
+ function renderHeader(onToggleSidebar = vi.fn()) {
+ const utils = render(
+
+
+
+
+ );
+ return { ...utils, onToggleSidebar };
+ }
+
+ it("renders the special-cased introduction breadcrumb", () => {
+ pathname = "/docs/introduction";
+ renderHeader();
+ expect(screen.getByText("Docs > Guide")).toBeTruthy();
+ });
+
+ it("title-cases kebab- and snake-case path segments", () => {
+ pathname = "/docs/console/getting-started/multi_cluster-setup";
+ renderHeader();
+ expect(
+ screen.getByText("Docs > Console > Getting Started > Multi Cluster Setup")
+ ).toBeTruthy();
+ });
+
+ it("falls back to 'Docs' when the pathname is not under /docs/", () => {
+ pathname = "/blog/post";
+ renderHeader();
+ expect(screen.getByText("Docs")).toBeTruthy();
+ });
+
+ it("dismisses the banner and toggles the sidebar on click", () => {
+ const { onToggleSidebar } = renderHeader();
+ expect(screen.getByTestId("banner").textContent).toBe("false");
+
+ fireEvent.click(screen.getByLabelText("Open sidebar"));
+
+ expect(onToggleSidebar).toHaveBeenCalledTimes(1);
+ expect(screen.getByTestId("banner").textContent).toBe("true");
+ expect(localStorage.getItem("docs-banner-dismissed")).toBe("true");
+ });
+});
+
+describe("SidebarFooter", () => {
+ it("full variant: renders theme label and collapse button, toggles light -> dark", () => {
+ const onCollapse = vi.fn();
+ render();
+
+ expect(screen.getByText("Light")).toBeTruthy();
+ fireEvent.click(screen.getByTitle("Change theme"));
+ expect(setTheme).toHaveBeenCalledWith("dark");
+
+ fireEvent.click(screen.getByTitle("Collapse sidebar"));
+ expect(onCollapse).toHaveBeenCalledTimes(1);
+ });
+
+ it("full variant: shows Dark label and toggles dark -> light", () => {
+ resolvedTheme = "dark";
+ render();
+
+ expect(screen.getByText("Dark")).toBeTruthy();
+ fireEvent.click(screen.getByTitle("Change theme"));
+ expect(setTheme).toHaveBeenCalledWith("light");
+ });
+
+ it("full variant on mobile omits the collapse button", () => {
+ render();
+ expect(screen.queryByTitle("Collapse sidebar")).toBeNull();
+ expect(screen.getByTitle("Change theme")).toBeTruthy();
+ });
+
+ it("slim variant: icon-only expand button and theme toggle", () => {
+ const onCollapse = vi.fn();
+ resolvedTheme = "dark";
+ render();
+
+ expect(screen.queryByText("Dark")).toBeNull();
+ fireEvent.click(screen.getByTitle("Change theme"));
+ expect(setTheme).toHaveBeenCalledWith("light");
+
+ fireEvent.click(screen.getByTitle("Expand sidebar"));
+ expect(onCollapse).toHaveBeenCalledTimes(1);
+ });
+
+ it("server-renders size-reserving placeholders (mount effect has not run)", () => {
+ const full = renderToStaticMarkup();
+ expect(full).toContain('class="h-7 w-full"');
+ expect(full).not.toContain("Change theme");
+
+ const slim = renderToStaticMarkup(
+
+ );
+ expect(slim.match(/class="w-5 h-5"/g)?.length).toBe(2);
+ expect(slim).not.toContain("Expand sidebar");
+ });
+});