恢复被 #193 误删的策划前端全部样式,并加上组件到样式的守门测试 #195
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ import type { ProjectSupervisorComponentProps } from '../../src/features/app-she
|
||||
import { WorkspaceLauncherShell } from '../../src/features/app-shell/WorkspaceLauncher';
|
||||
import {
|
||||
act,
|
||||
agentRuntimeUserInputRequest,
|
||||
App,
|
||||
cleanup,
|
||||
createGameCreationAppManifest,
|
||||
@@ -1611,6 +1612,93 @@ export function registerHomeProjectCreationTests() {
|
||||
).toHaveLength(1);
|
||||
},
|
||||
);
|
||||
|
||||
it('surfaces the planning clarification card after 做方案 creates the project from home', async () => {
|
||||
// 上面那条只断言到「run 起来了、source 对」。真实故障恰好落在它之后:plan 根 run
|
||||
// 停在 waiting-for-user-input 并带回澄清请求,而工作台一直停在前端本地的占位文案,
|
||||
// 澄清卡永远不出现。策划链路现有用例全部走「打开已有项目 + 直接注入 initialRuntime」,
|
||||
// 正好绕开首页自动建项目这条路,所以这个缺口一直没人守。
|
||||
const projectPath = '/tmp/home-planning-clarification';
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'local-project-draft',
|
||||
'home-planning-clarification',
|
||||
);
|
||||
const supervisorHarness = createProjectSupervisorRuntimeHarness({
|
||||
projectPath,
|
||||
expectedRunProfile: 'standard',
|
||||
});
|
||||
let planRootRunId = '';
|
||||
const invoke = vi.fn(
|
||||
async (command: string, args?: Record<string, unknown>) => {
|
||||
if (command === 'pick_local_project_directory') {
|
||||
return projectPath;
|
||||
}
|
||||
if (command === 'is_local_project_directory_non_empty') {
|
||||
return false;
|
||||
}
|
||||
if (command === 'create_automatic_local_game_project') {
|
||||
return {
|
||||
projectPath,
|
||||
manifestPath: `${projectPath}/.agent/manifest.json`,
|
||||
manifest,
|
||||
};
|
||||
}
|
||||
if (command === 'start_game_creator_supervisor_runtime_task') {
|
||||
planRootRunId = String(args?.runId ?? '');
|
||||
}
|
||||
const result = await supervisorHarness.invoke(command, args);
|
||||
if (command !== 'read_game_creator_agent_runtime' || !planRootRunId) {
|
||||
return result;
|
||||
}
|
||||
// 后端此刻的真实形态:pending 是 user.input_request,读命令把澄清请求投影在
|
||||
// 结果的**顶层**(`AgentRuntimeResult.user_input_request`,与 `state` 平级),
|
||||
// 前端的 agentRuntimeStateFromResult 也优先读顶层。放进 state 会被顶层的 null
|
||||
// 盖掉,那是 fixture 写错,不是产品缺陷。
|
||||
const runtimeResult = result as { state: Record<string, unknown> };
|
||||
return {
|
||||
...runtimeResult,
|
||||
state: {
|
||||
...runtimeResult.state,
|
||||
status: 'waiting-for-user-input',
|
||||
phase: 'waiting-for-user-input',
|
||||
},
|
||||
userInputRequest: agentRuntimeUserInputRequest({
|
||||
agentId: 'project-supervisor',
|
||||
sessionId: supervisorHarness.sessionId,
|
||||
runId: planRootRunId,
|
||||
requestId: 'request-plan-round-1',
|
||||
actionId: 'action-plan-round-1',
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = {
|
||||
core: { invoke },
|
||||
event: { listen: supervisorHarness.listen },
|
||||
};
|
||||
renderLauncherAt('/?launcher', 'home', true);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '做方案' }));
|
||||
const promptInput = screen.getByLabelText('创作想法');
|
||||
nativeClipboardMock.text = '2D射击游戏';
|
||||
fireEvent.paste(promptInput);
|
||||
await waitFor(() => {
|
||||
expect(promptInput.textContent).toContain('2D射击游戏');
|
||||
});
|
||||
fireEvent.keyDown(promptInput, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
'start_game_creator_supervisor_runtime_task',
|
||||
expect.objectContaining({ source: PROJECT_SUPERVISOR_PLAN_SOURCE }),
|
||||
);
|
||||
});
|
||||
|
||||
const strip = await screen.findByLabelText('立项策划运行状态');
|
||||
expect(
|
||||
within(strip).getByText('首版角色规范图采用哪种美术方向?'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
it('refreshes Direct Codex art commits while the turn is still running and after a later failure', async () => {
|
||||
const projectPath =
|
||||
'C:\\Users\\tester\\Documents\\Genarrative GameAgent\\live-direct-art';
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import {
|
||||
agentRuntimeUserInputRequest,
|
||||
createPlanGddStateView,
|
||||
@@ -521,4 +524,91 @@ export function registerPlanGddApprovalTests() {
|
||||
expectSupervisorRuntimePanelAbsent();
|
||||
expect(screen.queryByText('策划子 Run 退出')).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps a stylesheet rule for every class the planning components reference', () => {
|
||||
// #193「Codex/agent chat layout fix」重写聊天样式时,把策划前端的选择器整段
|
||||
// 误删:组件(TSX)原样保留、样式全数消失,审批卡/阶段条/交付行裸奔,正文弹层
|
||||
// 失去 fixed 定位变成内联平铺。组件和它的样式分居两个文件,重构样式的人看不见
|
||||
// 使用方——这条测试就是那根缺失的连线:类名清单直接从组件源码里推导,组件加了
|
||||
// 新类而样式没跟上、或样式又被顺手清掉,这里都会红。
|
||||
const componentSources = [
|
||||
'src/features/project-workspace/GddApprovalCard.tsx',
|
||||
'src/features/project-workspace/PlanningLaneRuntimeStrip.tsx',
|
||||
]
|
||||
.map((path) =>
|
||||
readFileSync(
|
||||
resolve(process.cwd(), 'apps/ai-game-creator-shell', path),
|
||||
'utf8',
|
||||
),
|
||||
)
|
||||
.join('\n');
|
||||
const referencedClasses = new Set<string>();
|
||||
for (const match of componentSources.matchAll(
|
||||
/className=(?:"([^"]+)"|\{`([^`]+)`\})/g,
|
||||
)) {
|
||||
// 条件类长在插值里:`plan-gdd-surface${showCard ? ' …--with-card' : ''}`。
|
||||
|
lhk229 marked this conversation as resolved
Outdated
|
||||
// 把 `${…}` 整段丢掉等于把它们排除在守门之外,而它们恰恰是最容易被顺手删干净
|
||||
// 的一档——`--with-card` 挂着审批卡的行模板,没有它卡片底部会被外壳的
|
||||
// `overflow: hidden` 切掉。只取插值里的字符串字面量:三元的条件、变量名都不是
|
||||
// 类名,不能混进清单。
|
||||
const literal = (match[1] ?? match[2] ?? '').replace(
|
||||
/\$\{([^}]*)\}/g,
|
||||
(_whole, expression: string) =>
|
||||
[...expression.matchAll(/'([^']*)'|"([^"]*)"/g)]
|
||||
.map((piece) => piece[1] ?? piece[2] ?? '')
|
||||
.join(' '),
|
||||
);
|
||||
for (const name of literal.split(/\s+/)) {
|
||||
if (name) {
|
||||
referencedClasses.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(referencedClasses.size).toBeGreaterThan(10);
|
||||
const styles = readFileSync(
|
||||
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
|
||||
'utf8',
|
||||
);
|
||||
for (const name of referencedClasses) {
|
||||
// 子串匹配会把 `.gdd-approval-card__header` 当成 `.gdd-approval-card` 的证据:
|
||||
// 前缀类的规则被删光、只剩派生类时这里照样绿。要求类名后面不能再跟类名字符,
|
||||
// 才是「存在这个类的精确 selector」。
|
||||
const exactSelector = new RegExp(
|
||||
`\\.${name.replace(/[^\w-]/g, '\\$&')}(?![\\w-])`,
|
||||
);
|
||||
expect(
|
||||
exactSelector.test(styles),
|
||||
`styles.css 缺少 .${name} 的精确 selector`,
|
||||
).toBe(true);
|
||||
}
|
||||
// 正文弹层必须是浮层:backdrop 一旦丢掉 fixed 定位,整个 GDD 会内联平铺进
|
||||
// 消息流里——这正是误删当时最刺眼的症状。
|
||||
expect(styles).toMatch(
|
||||
/\.gdd-approval-card__dialog-backdrop\s*\{[^}]*position:\s*fixed/s,
|
||||
);
|
||||
// 做方案的单栏工作台不在上面两个组件文件里(类名由
|
||||
// view/project-development/index.tsx 拼出),显式钉住:没有这两条规则时,
|
||||
// 策划项目一打开就是左边一整片空资源画布。它们和策划样式死在 #193 同一刀里。
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-layout\.is-conversation-only\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\)/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-layout\.is-conversation-only \.game-workbench-stage\s*\{[^}]*display:\s*none/s,
|
||||
);
|
||||
// 工作台里的会话列是 `overflow: hidden` 的定高列。策划链路比另外两条多出窄条
|
||||
// (澄清卡 / 失败恢复),只有把这一列排成 flex 列、窄条不参与压缩,它才落在可视
|
||||
// 区里;否则组件照常渲染却被静默裁到线外,屏幕上什么都没有。命中判据必须包含窄条
|
||||
// 自身——只认 `.plan-gdd-surface` 时,`planGddState` 还没 hydrate 出来的那一格里
|
||||
// 澄清卡照样被裁。
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-chat\s+\.project-supervisor-conversation:has\([^)]*\.planning-lane-runtime-strip[^)]*\)\s*\{[^}]*display:\s*flex/s,
|
||||
);
|
||||
// `--with-card` 有两条规则,上面的存在性检查只要还剩一条就绿。承重的是这一条:
|
||||
// 策划面是 `display: grid` + `overflow: hidden` 的外壳,审批卡的 `max-height`
|
||||
// 和内滚要靠这个行模板才有边界。只删它、留下那条分隔线,表现是批准后的交付行
|
||||
// 连同路径和两个按钮被切在壳外。
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-chat\s+\.plan-gdd-surface--with-card\s*\{[^}]*grid-template-rows:\s*auto minmax\(0, 1fr\)/s,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user
[P2] 这个类名守门会漏掉条件类并产生子串误报。这里把模板中的
${...}整段替换为空,因此plan-gdd-surface--with-card永远不会进入referencedClasses;后面的styles.includes(.+ name)也不能证明存在该类的精确 selector。当前删除.plan-gdd-surface--with-card .plan-gdd-stage-progress的唯一规则时,本测试仍可通过。请显式覆盖条件类,并用 CSS selector AST/精确 selector 匹配,或按状态渲染后做 computed-style/布局断言;补一个删除该唯一规则就会失败的负向回归。已修复