Files
Jyotisha/frontend/tests/session-conversation-layout.test.ts
T
jesse-uxandGrok (agent loop) d3be9491ee
Independent Staging Quality Gate / validate (push) Successful in 20m4s
Independent Staging Quality Gate / publish (push) Failing after 7m48s
组件库 K0:换上 Motion 底座和 shadcn 零件,页面还没换(#104)。
去掉 GSAP。对话入场改为按需加载的 Motion,仍是 160 毫秒,仍尊重减少动态效果。
用 shadcn@4.21.4 补上任务书列出的组件,不覆盖已有的 8 个。设计令牌映射到 shadcn 变量。
手写控件检查和只减不增的白名单。Motion 预设与四个 store 只是骨架,没有接到页面上。

自测:tsc --noEmit 退出码 0;npm run lint 0 error(122 条原有 warning);
定向测试 37 项、fail 0、cancelled 0;next build 后 / 仍是 Static;
首屏 JS gzip 578349 → 578435(+86 字节,+0.01%)。

Co-Authored-By: Grok (agent loop)
2026-10-24 15:37:29 +08:00

67 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
const readProjectFile = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
const globalStyles = readProjectFile("src/app/globals.css");
const messageRowSource = readProjectFile("src/components/chat-message-row.tsx");
test("aligns the session transcript and composer to one readable column", () => {
assert.match(globalStyles, /--session-column-width:\s*760px/);
assert.match(globalStyles, /\.conversation:not\(\.is-empty\):not\(\.is-rectification\) \.message-list\s*\{[^}]*width:\s*min\(calc\(var\(--session-column-width\)/);
assert.match(globalStyles, /\.conversation:not\(\.is-empty\):not\(\.is-rectification\) \+ \.composer-wrap \.composer[^\{]*\{[^}]*width:\s*min\(var\(--session-column-width\),\s*100%\)/);
// 原值:`.composer-footer` 与 `.composer` 同宽 / 新值:只剩 `.composer`(上一条已断言)
// / 原因:常驻底栏已删,工具行移进输入框内部,不再有第二个需要对齐的盒子。
assert.doesNotMatch(globalStyles, /\.composer-footer\s*\{/);
});
test("keeps onboarding transcript and intake card on the same session column", () => {
const pageSource = readProjectFile("src/app/(app)/page.tsx");
assert.match(pageSource, /\$\{!profileComplete \? " is-onboarding" : ""\}/);
assert.match(globalStyles, /\.conversation\.is-onboarding \.welcome \{[\s\S]*width:\s*min\(calc\(var\(--session-column-width\) \+ \(var\(--session-column-gutter\) \* 2\)\), 100%\)/);
assert.match(globalStyles, /\.conversation\.is-onboarding\.is-empty \.welcome \{[\s\S]*width:\s*min\(var\(--session-column-width\), 100%\)/);
assert.match(globalStyles, /\.conversation\.is-onboarding \.onboarding-card,\s*\n\.conversation\.is-onboarding \.onboarding-card\.birth-time-transition-card \{[\s\S]*width:\s*100%;[\s\S]*max-width:\s*none/);
assert.match(globalStyles, /\.conversation\.is-onboarding-form \.welcome \{[\s\S]*padding:\s*var\(--space-6\) var\(--session-column-gutter\) var\(--space-8\)/);
});
test("centers the starter home even when the conversation grid is not is-empty", () => {
const pageSource = readProjectFile("src/app/(app)/page.tsx");
// 原值:空态 `.welcome` 与 `.starter-list` 都是 1040px
// 新值:`.welcome` 收到 `--session-column-width`,`.starter-list` 已删
// 原因:空态输入框此前 1040px、会话态 760px,首条消息落地时输入框会当场变窄一截。
// 现在两态同宽——这比原断言更强:它顺带锁住了「不跳宽度」。
assert.match(globalStyles, /\.welcome \{\s*width:\s*min\(var\(--session-column-width\), 100%\);\s*margin-inline:\s*auto;/);
assert.match(globalStyles, /\.composer-wrap-starter \.composer \{\s*width:\s*min\(var\(--session-column-width\), 100%\);/);
assert.doesNotMatch(globalStyles, /\.starter-list/);
assert.match(
pageSource,
/!onboardingFormActive && !activeSession\?\.messages\.length && \(Boolean\(activeSession\?\.messagesHydrated\) \|\| activeSession\?\.sessionType !== "consultation"\) \? " is-empty"/,
);
});
test("keeps message motion restrained and honors reduced-motion preferences", () => {
// 原值: gsap.matchMedia() / 新值: matchMedia("(prefers-reduced-motion: no-preference)")
// 原因: GSAP 换成 Motion 后仍只在用户允许动态效果时入场。时长、曲线、清属性不变。
assert.match(messageRowSource, /matchMedia\("\(prefers-reduced-motion: no-preference\)"\)/);
assert.match(messageRowSource, /prefers-reduced-motion:\s*no-preference/);
// Former value: `duration:\s*0\.18`. DESIGN.md §6 gives the message entrance 160ms; 0.18 was
// the GSAP copy drifting from the (now removed) 160ms CSS keyframe that ran alongside it.
assert.match(messageRowSource, /duration:\s*0\.16/);
assert.match(messageRowSource, /ease:\s*"cubic-bezier\(\.22,\s*1,\s*\.36,\s*1\)"/);
assert.match(messageRowSource, /clearProps:\s*"opacity,transform,visibility"/);
});
test("lets model controls fit their labels and keeps the stop action on-brand", () => {
assert.match(globalStyles, /\.model-selector-trigger\s*\{[^}]*width:\s*fit-content/);
assert.match(globalStyles, /\.model-selector-popup\s*\{[^}]*width:\s*max-content[^}]*min-width:\s*180px[^}]*max-width:\s*min\(420px,/);
assert.match(globalStyles, /\.model-selector-copy b\s*\{[^}]*overflow:\s*visible[^}]*white-space:\s*normal/);
assert.doesNotMatch(globalStyles, /\.model-selector-copy b\s*\{[^}]*text-overflow:\s*ellipsis/);
// 原值 `var(--color-action)` / 新值 `var(--color-action-strong)` / 原因:停止键是填充面,
// 走品牌 coral 那一阶;文字阶留给 --color-action。见 BUG-738。
// 原值 `var(--color-action-strong)` + 悬停 `var(--color-action-hover)` / 新值
// `var(--color-action-fill)` + 悬停 `var(--color-action-fill-hover)` / 原因:BUG-1079
// 全站实心按钮统一一种底色(浅 #a9583e 4.85:1、深 #8f4a33 5.77:1),停止键与发送键同色。
assert.match(globalStyles, /\.composer \.composer-stop\s*\{[^}]*background:\s*var\(--color-action-fill\)/);
assert.match(globalStyles, /\.composer \.composer-stop:not\(:disabled\):hover\s*\{[^}]*background:\s*var\(--color-action-fill-hover\)/);
});