import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { characterRemainingAnnouncement, characterRemainingLabel, characterRemainingVisible, remainingThreshold, } from "../src/lib/character-remaining.ts"; const composer = readFileSync(new URL("../src/components/chat-composer.tsx", import.meta.url), "utf8"); const remainingSource = readFileSync(new URL("../src/components/character-remaining.tsx", import.meta.url), "utf8"); const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); const guide = readFileSync(new URL("../src/components/birth-time-guide-turn.tsx", import.meta.url), "utf8"); const choice = readFileSync(new URL("../src/components/birth-time-choice-question.tsx", import.meta.url), "utf8"); const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8"); test("thresholds stay quiet until the last 10 percent, floored at 8 and capped at 50", () => { assert.equal(remainingThreshold(500), 50); assert.equal(remainingThreshold(240), 24); assert.equal(remainingThreshold(80), 8); assert.equal(characterRemainingVisible(449, 500), false); assert.equal(characterRemainingVisible(450, 500), true); assert.equal(characterRemainingVisible(71, 80), false); assert.equal(characterRemainingVisible(72, 80), true); }); test("the visible label updates while the live announcement stays at two phrases", () => { assert.equal(characterRemainingLabel(12, 500), "还剩 12 字"); assert.equal(characterRemainingLabel(0, 500), "已达 500 字上限"); assert.equal(characterRemainingAnnouncement(12, 500), "接近字数上限"); assert.equal(characterRemainingAnnouncement(0, 80), "已达 80 字上限"); }); test("the count region is polite, atomic, and only mounted inside the threshold", () => { assert.match(remainingSource, /if \(!characterRemainingVisible\(length, maxLength\)\) return null;/); assert.match(remainingSource, /aria-live="polite"/); assert.match(remainingSource, /aria-atomic="true"/); assert.match(remainingSource, /aria-hidden="true"/); assert.match(remainingSource, /className="sr-only"/); assert.match(css, /\.character-remaining\[data-limit\] \{ color: var\(--color-danger\); \}/); }); test("the four capped fields wire describedby to a count that lives in existing chrome", () => { assert.match(composer, /aria-describedby=\{showRemaining \? composerRemainingId : undefined\}/); assert.match(page, //); assert.match(page, /className="composer-footer"/); assert.match(page, /aria-describedby=\{characterRemainingVisible\(value\.name\.length, 80\) \? nameRemainingId : undefined\}/); assert.match(guide, /aria-describedby=\{describedBy\}/); assert.match(choice, /aria-describedby=\{characterRemainingVisible\(note\.length, 240\) \? unmatchedRemainingId : undefined\}/); assert.match(css, /\.composer-footer > p \{ display: none; \}/); assert.doesNotMatch(page, /