测试文档只在需要的测试里安装。提示和圆点按项分开。清单「去做」走客户端跳转。输入框高亮在离开页面时清掉。六个没有入口的旧校正组件删掉。 Co-Authored-By: Grok (agent loop)
63 lines
3.6 KiB
TypeScript
63 lines
3.6 KiB
TypeScript
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";
|
|
import { homeSurface as page } from "./home-surface.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 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 capped fields wire describedby to a count that lives in existing chrome", () => {
|
|
// Former lock: `aria-describedby={showRemaining ? composerRemainingId : undefined}`. The composer
|
|
// is now shared with the rectification surface, which passes its own count id (BUG-477).
|
|
assert.match(composer, /remainingId = composerRemainingId/);
|
|
assert.match(composer, /showRemaining \? remainingId : undefined\]\.filter\(Boolean\)\.join\(" "\)/);
|
|
assert.match(composer, /aria-describedby=\{describedByIds\}/);
|
|
// 原值:page.tsx 里的 `<ComposerCharacterRemaining>` 加 `.composer-footer` 常驻横栏
|
|
// 新值:计数由 ChatComposer 在自己的底排内联渲染,只在 showRemaining 为真时进 DOM
|
|
// 原因:`.composer-footer` 的 min-height 是常驻 44px,没到字数上限时那一条是空的。
|
|
assert.match(composer, /\{showRemaining \? \(\s*<CharacterRemaining id=\{remainingId\} length=\{draft\.length\} maxLength=\{maxLength\} \/>/);
|
|
assert.doesNotMatch(page, /composer-footer/);
|
|
assert.match(page, /aria-describedby=\{characterRemainingVisible\(value\.name\.length, 80\) \? nameRemainingId : undefined\}/);
|
|
// 原值: 还核对旧引导轮和旧选项题的 aria-describedby。
|
|
// 新值: 只核对仍在页面上的输入框和称呼栏。
|
|
// 原因: 那两个旧校正组件没有入口,已删除。
|
|
// 原值 `.composer-footer > p { display: none; }`(窄屏藏计数)/ 新值:`.composer-row` 无内容时不渲染
|
|
// / 原因:计数不再有常驻容器,窄屏不需要再单独藏它。
|
|
assert.match(css, /\.composer-row \{[^}]*display: flex/);
|
|
assert.doesNotMatch(page, /<p className="character-remaining"/);
|
|
});
|