import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; 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"); const guidedTopicsSource = readFileSync(new URL("../src/lib/guided-jyotish-topics.ts", import.meta.url), "utf8"); function sourceBetween(source: string, startMarker: string, endMarker: string) { const start = source.indexOf(startMarker); const end = source.indexOf(endMarker, start); assert.notEqual(start, -1); assert.notEqual(end, -1); return source.slice(start, end); } test("keeps starter questions visible while the user edits a draft", () => { // Given: the empty-session starter block and its render guard. const guardStart = pageSource.indexOf("{profileComplete && presetMessageFinished"); const onboardingBranch = pageSource.indexOf("(onboardingPending ?", guardStart); // When: the guard is inspected independently of the card copy and layout. assert.notEqual(guardStart, -1); assert.notEqual(onboardingBranch, -1); const starterVisibilityGuard = pageSource.slice(guardStart, onboardingBranch); // Then: draft text cannot hide the cards before a message is submitted. assert.doesNotMatch(starterVisibilityGuard, /\bdraft\b/); }); test("completed account initialization switches directly to the home cards", () => { assert.doesNotMatch(pageSource, /setOnboardingJustCompleted\(true\)/); assert.doesNotMatch(pageSource, /!profileComplete \|\| onboardingJustCompleted/); }); test("default starter questions are guided Jyotish topics with evidence and claim boundaries", () => { assert.match(pageSource, /defaultGuidedJyotishTopics/); assert.match(pageSource, /theme\.evidencePreview\.join/); assert.match(pageSource, /theme\.claimBoundary/); assert.match(guidedTopicsSource, /strictWorkflowRoute/); assert.match(guidedTopicsSource, /evidencePreview/); assert.match(guidedTopicsSource, /confidenceCap/); assert.match(guidedTopicsSource, /claimBoundary/); assert.match(guidedTopicsSource, /D10/); assert.match(guidedTopicsSource, /D9/); assert.match(guidedTopicsSource, /Ashtakavarga/); assert.match(guidedTopicsSource, /独立 holdout/); }); test("keeps follow-up suggestions visible while the user edits a draft", () => { // Given: the follow-up suggestion block and its render guard. const suggestionGuard = sourceBetween( pageSource, "{activeSuggestions.length > 0", "(\n
{ // Given: the page-owned selection callback and the app sidebar session action. const selectSession = pageSource.match(/function selectSession\(sessionId: string\) \{([\s\S]*?)\n \}/); // When: request-time navigation constraints are inspected. // Then: pending request state cannot disable read-only session switching. assert.ok(selectSession); assert.doesNotMatch(selectSession[1], /pendingSessionId|isLoading|cancellationPending|creatingSession/); assert.match(appSidebarSource, /onSelectSession\(session\.id\)/); assert.doesNotMatch(appSidebarSource, /pendingSession|isLoading|cancellationPending|requestPending/); }); test("uses the compact model popup width", () => { // Given: the model selector popup styles. const popupStyles = sourceBetween(globalStyles, ".model-selector-popup {", "}\n.model-selector-popup[data-starting-style]"); // When: its responsive width is inspected. // Then: the desktop cap is half of the former 360px width. assert.match(popupStyles, /width:\s*min\(180px,/); }); test("centers the credit value with its icon", () => { // Given: the credit value styles next to the existing flex-centered icon. const creditValueStyles = sourceBetween(globalStyles, ".credit-button span {", "}\n.credit-button .credit-icon"); // When: the value line box is inspected. // Then: it participates in flex centering without extra line-height drift. assert.match(creditValueStyles, /display:\s*inline-flex/); assert.match(creditValueStyles, /align-items:\s*center/); assert.match(creditValueStyles, /line-height:\s*1/); }); test("routes account actions through a popover and focused dialogs", () => { // Given: the account surface state and entry-point handlers. // When: the page source is inspected for independent menu and dialog routes. // 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\(null\)/); assert.match(pageSource, /openAccountDialog\("profile"/); assert.match(pageSource, /openAccountDialog\("redeem"/); assert.match(pageSource, /openAccountDialog\("logout"/); assert.match(appSidebarSource, //); }); test("removes the monolithic account sheet", () => { // Given: the former sheet implementation names. // When: the page and global styles are inspected. // Then: no right-side account sheet remains. assert.doesNotMatch(pageSource, /profile-overlay|profile-dialog|openAccount\(/); assert.doesNotMatch(globalStyles, /\.profile-overlay|\.profile-dialog/); }); 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(appSidebarSource, /account\.isAdmin\s*&&\s*]+href="\/admin\/codes"[^>]+role="menuitem"/); });