import { fireEvent, render, screen } from "@testing-library/react";
import { MemoryRouter, Route, Routes, useLocation, useNavigate } from "react-router-dom";
import { beforeEach, describe, expect, it, vi } from "vitest";
import RootLayout from "@/layouts/RootLayout";
const authState = vi.hoisted(() => ({ currentUser: { name: "users/test" } as { name: string } | undefined }));
const instanceState = vi.hoisted(() => ({ accessMode: 2, instanceUrl: "https://example.com", demo: true }));
vi.mock("@/components/AppSidebar", () => ({
default: () => ,
MobileAppHeader: () => ,
MobileAppSidebar: () => ,
QuickFindDialog: () =>
Quick Find
,
SIDEBAR_WIDTH_VAR: "--app-sidebar-width",
SidebarResizeHandle: () => ,
useSidebarWidth: () => ({ width: 256, minWidth: 192, maxWidth: 384, setWidth: vi.fn() }),
}));
// GlobalMemoEditorContext is deliberately NOT mocked: the real provider calls
// useAppSidebar(), which throws unless RootLayout nests it inside
// AppSidebarProvider. That nesting is the only thing here that can break.
vi.mock("@/contexts/AuthContext", () => ({
useAuth: () => ({ isUserSettingsInitialized: true }),
}));
vi.mock("@/contexts/InstanceContext", () => ({
useInstance: () => ({ profile: instanceState }),
}));
vi.mock("@/contexts/SpaceContext", () => ({
SpaceProvider: ({ children }: { children: React.ReactNode }) => <>{children}>,
useSpaceContext: () => ({ selectedSpaceName: undefined }),
}));
vi.mock("@/hooks/useCurrentUser", () => ({
default: () => authState.currentUser,
}));
vi.mock("@/hooks/useMediaQuery", () => ({
default: () => true,
}));
vi.mock("@/utils/i18n", () => ({
useTranslate: () => (key: string) => key,
}));
const SHELL_TEST_IDS = ["desktop-sidebar", "mobile-sidebar", "mobile-header", "quick-find"];
const RouteState = () => {
const navigate = useNavigate();
const location = useLocation();
return (
);
};
describe("RootLayout global editor shell", () => {
beforeEach(() => {
authState.currentUser = { name: "users/test" };
instanceState.accessMode = 2;
});
it("mounts the composer provider inside the sidebar provider and keeps the shell across routes", () => {
render(
}>
} />
} />
,
);
for (const testId of SHELL_TEST_IDS) {
expect(screen.getByTestId(testId)).toBeInTheDocument();
}
// The composer stays closed until something calls openEditor.
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Open inbox" }));
expect(screen.getByTestId("route")).toHaveTextContent("/inbox");
for (const testId of SHELL_TEST_IDS) {
expect(screen.getByTestId(testId)).toBeInTheDocument();
}
});
it("gates anonymous visitors by access mode even when an instance URL is configured", () => {
authState.currentUser = undefined;
instanceState.accessMode = 1;
render(
}>
Explore} />
} />
,
);
expect(screen.getByTestId("route")).toHaveTextContent("/auth");
});
});