feat: compose Jyotisha app sidebar
This commit is contained in:
@@ -85,3 +85,46 @@ test("documents the sidebar shell design contract", () => {
|
||||
assert.match(design, /Scroll ownership/);
|
||||
assert.match(design, /session-local/);
|
||||
});
|
||||
|
||||
test("composes the Jyotisha app sidebar from the generic shell", () => {
|
||||
assert.equal(existsSync(projectFile("src/components/app-sidebar.tsx")), true);
|
||||
const appSidebar = readProjectFile("src/components/app-sidebar.tsx");
|
||||
|
||||
for (const component of ["SidebarHeader", "SidebarContent", "SidebarFooter", "SidebarRail"]) {
|
||||
assert.match(appSidebar, new RegExp(`<${component}\\b`));
|
||||
}
|
||||
});
|
||||
|
||||
test("uses one collapsed history action instead of icon-only session rows", () => {
|
||||
const appSidebar = readProjectFile("src/components/app-sidebar.tsx");
|
||||
assert.match(appSidebar, /MessageSquareText/);
|
||||
assert.match(appSidebar, /state === "collapsed" && !isMobile/);
|
||||
assert.match(appSidebar, /sessions\.map/);
|
||||
assert.doesNotMatch(appSidebar, /sessions\.map\([^)]*\)\s*=>\s*<[^>]+aria-label=/);
|
||||
});
|
||||
|
||||
test("keeps session navigation independent of request state", () => {
|
||||
const appSidebar = readProjectFile("src/components/app-sidebar.tsx");
|
||||
assert.match(appSidebar, /onSelectSession\(session\.id\)/);
|
||||
assert.doesNotMatch(appSidebar, /pendingSession|isLoading|cancellationPending|requestPending/);
|
||||
});
|
||||
|
||||
test("uses a portaled Base UI account popover with safe collision padding", () => {
|
||||
const appSidebar = readProjectFile("src/components/app-sidebar.tsx");
|
||||
assert.match(appSidebar, /import \{ Popover \} from "@base-ui\/react\/popover"/);
|
||||
assert.match(appSidebar, /<Popover\.Portal>/);
|
||||
assert.match(appSidebar, /collisionPadding=\{12\}/);
|
||||
});
|
||||
|
||||
test("closes an open account menu only when its sidebar placement changes", () => {
|
||||
const appSidebar = readProjectFile("src/components/app-sidebar.tsx");
|
||||
assert.match(appSidebar, /previousPopoverPlacement/);
|
||||
assert.match(appSidebar, /previousPopoverPlacement\.current !== popoverPlacement && accountMenuOpen/);
|
||||
});
|
||||
|
||||
test("keeps app sidebar props as product data and callbacks", () => {
|
||||
const appSidebar = readProjectFile("src/components/app-sidebar.tsx");
|
||||
assert.match(appSidebar, /export type AppSidebarProps/);
|
||||
assert.match(appSidebar, /onSelectSession: \(sessionId: string\) => void/);
|
||||
assert.doesNotMatch(appSidebar, /supabase|fetch\(|\/api\//i);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import test from "node:test";
|
||||
|
||||
const pageSource = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
|
||||
const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
const appSidebarSource = readFileSync(new URL("../src/components/app-sidebar.tsx", import.meta.url), "utf8");
|
||||
|
||||
function sourceBetween(source: string, startMarker: string, endMarker: string) {
|
||||
const start = source.indexOf(startMarker);
|
||||
@@ -79,10 +80,11 @@ test("routes account actions through a popover and focused dialogs", () => {
|
||||
// Then: each account task has a focused destination instead of one combined sheet.
|
||||
assert.match(pageSource, /const \[accountMenuOpen, setAccountMenuOpen\] = useState\(false\)/);
|
||||
assert.match(pageSource, /const \[activeAccountDialog, setActiveAccountDialog\] = useState<AccountDialog \| null>\(null\)/);
|
||||
assert.match(pageSource, /className="account-menu"/);
|
||||
assert.match(pageSource, /openAccountDialog\("profile"/);
|
||||
assert.match(pageSource, /openAccountDialog\("redeem"/);
|
||||
assert.match(pageSource, /openAccountDialog\("logout"/);
|
||||
assert.match(appSidebarSource, /className="account-menu"/);
|
||||
assert.match(appSidebarSource, /<Popover\.Root open=\{accountMenuOpen\} onOpenChange=\{onAccountMenuOpenChange\}>/);
|
||||
});
|
||||
|
||||
test("removes the monolithic account sheet", () => {
|
||||
@@ -97,5 +99,5 @@ test("keeps admin navigation separate from account task dialogs", () => {
|
||||
// Given: the administrator-only route and the new account menu.
|
||||
// When: their source relationship is inspected.
|
||||
// Then: code management stays a guarded navigation action rather than a modal.
|
||||
assert.match(pageSource, /account\?\.isAdmin\s*&&\s*<Link[^>]+href="\/admin\/codes"[^>]+role="menuitem"/);
|
||||
assert.match(appSidebarSource, /account\.isAdmin\s*&&\s*<Link[^>]+href="\/admin\/codes"[^>]+role="menuitem"/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user