Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
59 lines
3.8 KiB
TypeScript
59 lines
3.8 KiB
TypeScript
// 2026-09-30 loading round: the chart wait lights houses up in order, the people
|
|
// archive uses the shared orbit, and today's line enters softly (BUG-1125).
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import React from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
|
|
import { ChartPageSkeleton } from "../src/components/chart-page/chart-page-skeleton.tsx";
|
|
import { EMPTY_NORTH_INDIAN_CHART, VedicChartSvg } from "../src/components/personal-report/vedic-chart-svg.tsx";
|
|
import { StarterGreeting } from "../src/components/starter-home.tsx";
|
|
import { cssDeclarations } from "./css-contract-test-support.ts";
|
|
|
|
Object.assign(globalThis, { React });
|
|
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
|
const read = (path: string) => readFileSync(new URL(path, import.meta.url), "utf8");
|
|
|
|
test("the Vedic wait numbers its twelve houses in house order; the finished chart carries no index", () => {
|
|
const waiting = renderToStaticMarkup(<ChartPageSkeleton copy="虚构的等待句" />);
|
|
const indexes = [...waiting.matchAll(/--house-index:\s*(\d+)/g)].map((match) => Number(match[1]));
|
|
assert.deepEqual(indexes, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
|
|
assert.doesNotMatch(waiting, /app-loading-orbit|app-loading-mark/, "no orbit logo over the chart");
|
|
assert.match(waiting, /role="status"/);
|
|
assert.match(waiting, /<span class="sr-only">虚构的等待句<\/span>/);
|
|
const finished = renderToStaticMarkup(<VedicChartSvg chart={EMPTY_NORTH_INDIAN_CHART} ariaLabel="x" />);
|
|
assert.doesNotMatch(finished, /--house-index/);
|
|
});
|
|
|
|
test("the western wait draws twelve sectors that light up around the wheel", () => {
|
|
const waiting = renderToStaticMarkup(<ChartPageSkeleton copy="虚构的等待句" western />);
|
|
assert.equal((waiting.match(/class="chart-skeleton-sector"/g) ?? []).length, 12);
|
|
assert.deepEqual([...waiting.matchAll(/--house-index:\s*(\d+)/g)].map((match) => Number(match[1])), [...Array(12).keys()]);
|
|
});
|
|
|
|
test("the house glow is a fill-only keyframe; lines stay still", () => {
|
|
const start = css.indexOf("@keyframes chart-skeleton-house");
|
|
const keyframes = css.slice(start, css.indexOf("\n}", start) + 2);
|
|
assert.match(keyframes, /fill-opacity: 0\.18/);
|
|
assert.doesNotMatch(keyframes, /(^|[^-])opacity:|transform|stroke/);
|
|
assert.match(cssDeclarations(".chart-page-skeleton .chart-page-western-spoke", css), /stroke-opacity: 0\.55/);
|
|
});
|
|
|
|
test("people archive and the picker wait with the shared orbit, sentence for screen readers only", () => {
|
|
const people = read("../src/components/people/people-page.tsx");
|
|
assert.match(people, /<SecondaryPageShell title="星盘档案" waiting=\{status === "loading" \? "人物列表还在读" : null\}>/);
|
|
assert.doesNotMatch(people, /<p className="secondary-page-waiting">/);
|
|
const picker = read("../src/components/subject-switcher.tsx");
|
|
assert.match(picker, /<OrbitWaiting className="chat-profile-picker-waiting" label="人物列表还在读" \/>/);
|
|
});
|
|
|
|
test("today's line fades in (240ms, 4px) and is keyed on its text; reduced motion is instant (BUG-1125)", () => {
|
|
const markup = renderToStaticMarkup(<StarterGreeting starterGreeting={{ text: "你好" }} dailyTrend="虚构的一句。" />);
|
|
assert.match(markup, /<p class="starter-greeting-sub">虚构的一句。<\/p>/);
|
|
assert.match(read("../src/components/starter-home.tsx"), /<p key=\{dailyTrend\} className="starter-greeting-sub"/);
|
|
assert.match(css, /\.starter-greeting-sub \{ animation: starter-greeting-sub-in 240ms var\(--ease-out\) both; \}/);
|
|
assert.match(css, /@keyframes starter-greeting-sub-in \{\s*from \{ opacity: 0; transform: translateY\(4px\); \}/);
|
|
assert.match(css, /@media \(prefers-reduced-motion: reduce\) \{\s*\.starter-greeting-sub \{ animation: none; \}/);
|
|
});
|