import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { consultationDomainIds, consultationDomainRegistry, } from "../src/lib/consultation-domain-registry.ts"; import { defaultGuidedJyotishTopics, generalGuidedJyotishTopics, } from "../src/lib/guided-jyotish-topics.ts"; 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 derive every canonical domain with evidence and claim boundaries", () => { assert.match(pageSource, /defaultGuidedJyotishTopics/); assert.match(pageSource, /starterSuggestions\.map/); assert.match(pageSource, /starterThemes\.find\(\(candidate\) => candidate\.id === item\.theme\)/); assert.match(pageSource, /chooseSuggestedQuestion\(item\.text, item\.theme\)/); assert.equal(consultationDomainIds.length, 10); assert.deepEqual(consultationDomainRegistry.map((domain) => domain.id), [...consultationDomainIds]); assert.match(guidedTopicsSource, /consultationDomainRegistry\.map/); assert.deepEqual(defaultGuidedJyotishTopics.map((topic) => topic.id), [...consultationDomainIds]); assert.equal(defaultGuidedJyotishTopics.length, consultationDomainRegistry.length); for (const [index, topic] of defaultGuidedJyotishTopics.entries()) { const domain = consultationDomainRegistry[index]; assert.equal(topic.id, domain.id); assert.equal(topic.label, domain.label); assert.equal(topic.prompt, domain.prompt); assert.equal(topic.strictWorkflowRoute, domain.strictWorkflowRoute); assert.deepEqual(topic.evidencePreview, [...domain.evidencePreview]); assert.equal(topic.confidenceCap, domain.confidenceCap); assert.equal(topic.claimBoundary, domain.claimBoundary); } const domainById = new Map(consultationDomainRegistry.map((domain) => [domain.id, domain])); assert.match(domainById.get("career")?.requiredLayers.join(" ") ?? "", /D10/); assert.match(domainById.get("marriage")?.requiredLayers.join(" ") ?? "", /D9/); assert.match(domainById.get("wealth")?.requiredLayers.join(" ") ?? "", /Ashtakavarga/); assert.match(domainById.get("timing")?.requiredLayers.join(" ") ?? "", /negative holdout gate/); }); test("profiles without a usable birth minute receive all canonical general-knowledge prompts", () => { assert.deepEqual(generalGuidedJyotishTopics.map((topic) => topic.id), [...consultationDomainIds]); assert.deepEqual(generalGuidedJyotishTopics.map((topic) => topic.prompt), [ "印度占星一般会从哪些因素理解事业方向?", "印度占星一般如何分析关系模式?", "印度占星一般如何分析财富结构与风险?", "印度占星如何在非医疗诊断边界内理解身心压力?", "印度占星一般如何理解学习方式与进阶节奏?", "印度占星一般如何分析迁居、置业与海外发展?", "印度占星一般如何理解家庭关系与责任模式?", "印度占星中的年运分析通常包含哪些证据层?", "印度占星中的时间推运通常会看哪些因素?", "印度占星综合咨询会如何划分不同主题与证据边界?", ]); for (const topic of generalGuidedJyotishTopics) { const personalTopic = defaultGuidedJyotishTopics.find((candidate) => candidate.id === topic.id); assert.ok(personalTopic); assert.equal(topic.label, personalTopic.label); assert.equal(topic.strictWorkflowRoute, personalTopic.strictWorkflowRoute); assert.deepEqual(topic.evidencePreview, personalTopic.evidencePreview); assert.equal(topic.confidenceCap, personalTopic.confidenceCap); assert.equal(topic.claimBoundary, personalTopic.claimBoundary); assert.notEqual(topic.prompt, personalTopic.prompt); } assert.ok(generalGuidedJyotishTopics.every((topic) => !/我的|我近期|未来一年,事业和收入/.test(topic.prompt))); assert.match(pageSource, /const starterThemes = personalChartAvailable \? themes : generalGuidedJyotishTopics/); assert.doesNotMatch(pageSource, /回答一般占星知识/); assert.match(pageSource, /完成生时校正后,再讨论个人星盘结论/); assert.match(pageSource, /personalChartAvailable \? "daily_starlanguage" : null/); }); 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