{
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
const phoneCards = styles.match(
/\.starter-hero h1 \{\s*font-size: clamp\(38px[\s\S]*?\.daily-starlanguage-card,\s*\.birth-rectification-card \{([^}]*)\}[\s\S]*?\.product-entrypoint-footer \{([^}]*)\}[\s\S]*?\.product-entrypoint-action \{([^}]*)\}/,
);
assert.ok(phoneCards);
assert.match(phoneCards[1], /min-height:\s*0;/);
assert.match(phoneCards[1], /grid-template-rows:\s*auto auto;/);
assert.doesNotMatch(phoneCards[1], /min-height:\s*210px/);
assert.match(phoneCards[2], /flex-direction:\s*column;/);
assert.match(phoneCards[2], /align-items:\s*flex-start;/);
assert.match(phoneCards[3], /margin-left:\s*0;/);
assert.match(phoneCards[3], /justify-content:\s*flex-start;/);
});
test("starter homepage keeps its editorial display typography", () => {
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
const heroHeading = styles.match(/\.starter-hero h1\s*\{([^}]*)\}/)?.[1] ?? "";
const entrypointHeading = styles.match(/\.product-entrypoint-copy h2\s*\{([^}]*)\}/)?.[1] ?? "";
assert.match(heroHeading, /font-family:\s*var\(--font-display\)\s*;/);
assert.match(heroHeading, /font-weight:\s*400\s*;/);
assert.doesNotMatch(heroHeading, /font-family:\s*var\(--font-body\)/);
assert.match(entrypointHeading, /font-family:\s*var\(--font-display\)\s*;/);
assert.match(entrypointHeading, /font-weight:\s*400\s*;/);
assert.doesNotMatch(entrypointHeading, /font-family:\s*var\(--font-body\)/);
});
test("starter homepage stays editorial and hides technical chart parameters", () => {
const source = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
const start = source.indexOf('= 0 && end > start);
assert.match(starterHomepage, /className="starter-hero"/);
assert.match(starterHomepage, /className="starter-theme-accordion"/);
assert.match(starterHomepage, /starterSuggestions\.map/);
assert.doesNotMatch(starterHomepage, /evidencePreview|birthTimeDisplay|Vimshottari|D1|D9/);
assert.match(source, /const starterSuggestions = starterThemes\.map/);
assert.match(source, /composer-wrap-starter/);
assert.match(styles, /\/\* Starter workbench \*\/[\s\S]*?\.starter-list \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\);/);
assert.match(styles, /\.starter-hero,[\s\S]*?\.product-entrypoints,[\s\S]*?\.starter-themes \{[\s\S]*?width: 100%;/);
});
test("rectification 402 navigates to the membership page with the rectification source", () => {
// Given: the Agentic rectification chat owns the /api/rectification/agent call.
const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8");
const membershipLib = readFileSync(new URL("../src/lib/membership.ts", import.meta.url), "utf8");
// Then: a 402 (insufficient credits) hands off to /membership with the
// rectification source instead of leaving a dead-end error.
assert.match(chat, /membershipHref\("rectification"\)/);
assert.match(chat, /if \(response\.status === 402\) \{[\s\S]*window\.location\.assign\(membershipHref\("rectification"\)\)/);
assert.doesNotMatch(chat, /咨询点数不足/);
assert.doesNotMatch(chat, /setError\(`咨询点数不足/);
assert.match(membershipLib, /rectification: "生时校正需要点数或会员权益"/);
});