feat: compose Jyotisha app sidebar

This commit is contained in:
Jesse_Chen
2026-07-17 17:55:55 +08:00
parent fcaece87df
commit 626524b186
3 changed files with 268 additions and 2 deletions
+43
View File
@@ -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 -2
View File
@@ -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"/);
});