删掉滚动回归测试,改为守卫「回到底部」不盖住输入盒弹层
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m39s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 5m59s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m0s
Project CI / Frontend tests (pull_request) Successful in 2m47s
Project CI / Backend tests (pull_request) Successful in 7m42s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m33s
Project CI / Repository checks (pull_request) Successful in 7m2s
Project CI / Native shell tests (pull_request) Successful in 10m2s

- 删除 useConversationScroll.test.tsx 里新增的两条用例:加载行隐藏滞回、内容塌回一屏后收起胶囊
- chatDialogFrameLayout.test.ts:消息列表改为 flex 列容器后 gap 实际为 0,断言由 14px 改为 0 并注明间距仍由子项 margin 提供
- chatDialogFrameLayout.test.ts:新增用例——胶囊的 z-index 必须低于输入盒,模型 / 附件菜单向上展开时盖住胶囊而不是被胶囊盖住
- project-development.suite.ts:输入盒 z-index 断言由 1 改为 30,并注明层叠关系由上面那条用例守卫
This commit is contained in:
2026-10-07 11:55:04 +08:00
parent 2a1e7fb373
commit 05012a85c3
3 changed files with 32 additions and 71 deletions
@@ -356,37 +356,6 @@ describe('回到底部胶囊', () => {
);
expect(capsule()).toBe('回到底部');
});
it('内容塌回一屏以内:没有滚动事件也收起胶囊', () => {
const metrics: ListMetrics = {
scrollTop: 0,
scrollHeight: 1200,
clientHeight: 400,
};
stubListMetrics(metrics);
vi.stubGlobal('ResizeObserver', FakeResizeObserver);
const view = render(
<Harness
turns={[turn('t1')]}
historyHasMore={false}
historyLoading={false}
historyError={null}
onLoadEarlierHistory={() => undefined}
/>,
);
const capsule = () => view.getByTestId('capsule').textContent;
metrics.scrollTop = 0;
fireEvent.scroll(view.getByTestId('list'));
expect(capsule()).toBe('回到底部');
// 回合收口把过程折进收起的 `<details>`:内容塌回一屏以内,列表不会再发任何滚动事件。
// 只认 onScroll 里那次判定的 atBottom 会把胶囊永远挂在一个滚不动的列表上。
metrics.scrollHeight = 400;
FakeResizeObserver.triggerLayoutChange();
expect(capsule()).toBe('');
});
});
describe('加载行', () => {
@@ -418,43 +387,6 @@ describe('加载行', () => {
vi.useRealTimers();
}
});
it('连续翻页之间的小空隙不卸载加载行:加载态不再忽隐忽现', () => {
vi.useFakeTimers();
try {
stubListMetrics({ scrollTop: 0, scrollHeight: 1200, clientHeight: 400 });
const props = {
turns: [] as DirectChatTurn[],
historyHasMore: true,
historyError: null,
onLoadEarlierHistory: () => undefined,
};
const view = render(<Harness {...props} historyLoading />);
act(() => {
vi.advanceTimersByTime(150);
});
expect(view.queryByTestId('loading')).not.toBeNull();
// 第一页落地:加载态转假,但填充视口的判据下一帧又会起一次,两次之间只隔一次 effect 回流。
view.rerender(<Harness {...props} historyLoading={false} />);
act(() => {
vi.advanceTimersByTime(50);
});
view.rerender(<Harness {...props} historyLoading />);
// 小于隐藏延迟的空隙不卸载:加载行不会在两次翻页之间消失重现。
expect(view.queryByTestId('loading')).not.toBeNull();
// 真正结束(转假并超过隐藏延迟)才卸载。
view.rerender(<Harness {...props} historyLoading={false} />);
act(() => {
vi.advanceTimersByTime(301);
});
expect(view.queryByTestId('loading')).toBeNull();
} finally {
vi.useRealTimers();
}
});
});
describe('点「回到底部」后的程序化滚动', () => {
@@ -5969,7 +5969,9 @@ export function registerProjectWorkbenchFoundationTests() {
expect(composerRule?.[1]).not.toBeUndefined();
expect(composerRule?.[1]).toContain('position: relative;');
expect(composerRule?.[1]).toContain('gap: 8px;');
expect(composerRule?.[1]).toContain('z-index: 1;');
// 输入盒必须高于列表里的「回到底部」胶囊(胶囊 z-10),盒内向上展开的模型 / 附件菜单
// 才能盖住它;大小关系由 chatDialogFrameLayout.test.ts 的层叠用例守卫,这里钉字面值。
expect(composerRule?.[1]).toContain('z-index: 30;');
// 四边留白统一 16px、盒内内边距四边同为 12px(文字左内缩必须等于上内缩);
// 不允许再出现 `8px 12px 10px` / `10px 12px` 这类「左右一个值、上下另一个值」的写法。
expect(composerRule?.[1]).toContain('margin: 16px;');
@@ -22,6 +22,10 @@ const INPUT_SOURCE_PATH = repoPath(
const CHAT_COMPOSER_CONTROLS_PATH = repoPath(
'apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectComposer/ComposerControls.tsx',
);
/** 「回到底部」胶囊:z-index 写在 Tailwind 类上(`z-10`),styles.css 的层叠求值看不到。 */
const CAPSULE_PATH = repoPath(
'apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectScrollToBottomCapsule.tsx',
);
function lengthPx(rawValue: string, label: string): number {
const value = rawValue.trim();
@@ -329,10 +333,33 @@ describe('陶泥儿对话区:Codex 三段式(顶栏 / 唯一滚动区 / 文
);
expect(declaration(composer, 'border-radius')).toBe('14px');
// 对话内容左右留白 16px,消息之间 14px。
// 对话内容左右留白 16px。列表现在是 flex 列容器,`gap` 必须显式归 0:基础规则里那条
// `gap: 14px` 在块级容器上一直没生效,改成 flex 后若不归零,会和子项的 `margin-top`
// 叠成双倍间距;子项间距仍由 `> * + *` 的 margin-top 提供。
expect(paddingBox(list).left).toBe(16);
expect(paddingBox(list).right).toBe(16);
expect(declaration(list, 'gap')).toBe('14px');
expect(declaration(list, 'gap')).toBe('0');
});
it('「回到底部」胶囊不盖住输入盒弹层:胶囊层叠低于输入盒(宽屏)', () => {
// 胶囊是列表内的 sticky 浮标(Tailwind `z-10`),模型菜单(盒内 z-index: 20)与附件菜单
// (盒内 z-index: 3)都从输入盒向上展开、会盖到列表底部。输入盒自己是 `position: relative`
// + z-index 的层叠上下文,盒内菜单的 z-index 只在盒内有效,所以要让**盒子**高于胶囊,
// 菜单才能盖住胶囊;否则重叠处命中的会是胶囊(真机 hit-test 实测:盒 z-index:1 时命中胶囊)。
const capsuleClassName =
/className="([^"]*)"/u.exec(readFileSync(CAPSULE_PATH, 'utf8'))?.[1] ??
'';
const capsuleZIndex = Number(/\bz-(\d+)\b/u.exec(capsuleClassName)?.[1]);
const composer = desktopDeclarations(COMPOSER, COMPOSER_PLAIN);
const composerZIndex = Number(declaration(composer, 'z-index'));
expect(Number.isFinite(capsuleZIndex), '胶囊必须带 Tailwind z-* 类').toBe(
true,
);
expect(
Number.isFinite(composerZIndex),
'输入盒必须声明 z-index 来建立层叠上下文',
).toBe(true);
expect(composerZIndex).toBeGreaterThan(capsuleZIndex);
});
it('消息列表底部不留给浮层的空白,滚动到底不会多出一段空白(宽屏)', () => {