Merge branch 'feat/agc-v3-c3-canvas-match' into feat/agc-canvas-resource-workbench-v3

This commit is contained in:
2026-09-10 15:29:41 +08:00
2 changed files with 29 additions and 154 deletions
@@ -2032,7 +2032,9 @@ export function registerProjectWorkbenchFoundationTests() {
expect(childVersionCard?.textContent).not.toContain('version-root');
fireEvent.click(childVersionButton);
expect(screen.getByRole('toolbar', { name: '图片工具栏' })).not.toBeNull();
// 版本资源没有任何美术画布动作,选中反馈由卡片自身承担。
expect(childVersionButton.getAttribute('aria-pressed')).toBe('true');
expect(screen.queryByRole('toolbar')).toBeNull();
expect(screen.queryByText('asset:asset-removed')).toBeNull();
fireEvent.click(
within(screen.getByLabelText('资源栏目大纲')).getByRole('button', {
@@ -2057,160 +2059,21 @@ export function registerProjectWorkbenchFoundationTests() {
});
});
it('keeps the resource toolbar and canvas interactive while a non-modal detail card is open', async () => {
const manifest = createGameCreationAppManifest(
'workbench-resource-details',
'不应显示的项目标题',
);
render(
React.createElement(ProjectDevelopmentView, {
projectName: '不应显示的项目标题',
projectPath: '/tmp/workbench-resource-details',
manifest,
attachments: [],
recentRunStatus: null,
recentRunStopReason: null,
agentRuntimeSummaries: [
{
group: 'art',
label: '美术 Agent',
status: 'completed',
statusLabel: '已完成',
currentTask: '本轮工作已完成',
currentAction: null,
waitingOn: null,
completedCount: 4,
totalCount: 4,
},
],
agentResults: [
{
agentId: 'design-foundation',
runId: 'design-result-run',
label: '玩法策划 Agent',
title: '玩法策划 Agent 文本回执',
content: '策划回执正文',
updatedAt: 1,
},
{
agentId: 'art-asset-plan',
runId: 'art-result-run',
label: '美术资源计划 Agent',
title: '美术资源计划 Agent 文本回执',
content:
'# 美术计划\n\n- 仅有美术计划\n- 没有图片文件\n\n**等待实际素材生成。**',
updatedAt: 2,
},
],
supervisor: React.createElement('div', null, '项目总控'),
onHomeOpen: vi.fn(),
onProjectsOpen: vi.fn(),
}),
);
expect(screen.queryByText('不应显示的项目标题')).toBeNull();
expect(screen.queryByRole('button', { name: '回首页' })).toBeNull();
expect(screen.queryByRole('button', { name: '项目组' })).toBeNull();
const resourceSortControl = screen.getByRole('group', {
name: '资源排列方式',
});
expect(
Array.from(resourceSortControl.children).map((child) => child.tagName),
).toEqual(['BUTTON', 'BUTTON']);
expect(
within(resourceSortControl)
.getAllByRole('button')
.map((button) => button.textContent?.trim()),
).toEqual(['依赖', '类型']);
expect(screen.getByText('仅完成计划')).not.toBeNull();
expect(
screen.getByText('美术资源计划已完成,尚未生成或登记图片'),
).not.toBeNull();
for (const label of [
'生成视频',
'生成音效',
'生成背景音乐',
'新增 UI 设计',
]) {
expect(screen.queryByRole('button', { name: label })).toBeNull();
}
fireEvent.click(screen.getByRole('button', { name: '按类型' }));
await openResourceBookCategory('设计文档');
const searchInput = screen.getByLabelText(
'搜索项目资源',
) as HTMLInputElement;
fireEvent.change(searchInput, { target: { value: '美术资源计划' } });
const resourceCanvas = screen.getByLabelText(
'资源类型视图',
) as HTMLDivElement;
resourceCanvas.scrollLeft = 48;
resourceCanvas.scrollTop = 36;
const artReceiptButton = screen.getByRole('button', {
name: /选中资源:设计文档 美术资源计划 Agent 文本回执/,
});
const artReceiptCard = artReceiptButton.closest('.game-resource-card');
expect(artReceiptCard).not.toBeNull();
it('资源卡 chrome 只保留选中按钮与当前版本边框', () => {
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
);
// 卡片本体不再自带描边;只有被当前版本绑定的素材才有边框。
expect(styles).toMatch(/\.game-resource-card\s*\{[^}]*border:\s*0;/s);
expect(styles).toMatch(
/\.game-resource-card\.is-current-version\s*\{[^}]*border:\s*1px solid/s,
);
expect(styles).toMatch(
/\.game-resource-card-select\s*\{[^}]*cursor:\s*pointer[^}]*touch-action:\s*manipulation/s,
);
expect(styles).toMatch(
/\.game-resource-card-media-control\s*\{[^}]*transform:\s*scale\(var\(--genarrative-image-canvas-inverse-scale,\s*1\)\)[^}]*transform-origin:\s*bottom\s+right/s,
);
expect(styles).toMatch(
/\.game-resource-canvas\s*\{[^}]*user-select:\s*none/s,
);
expect(styles).toMatch(/\.game-resource-card\.is-dragging/);
expect(styles).toMatch(/\.game-resource-card\.is-current-version/);
fireEvent.click(artReceiptButton);
const workbenchStage = screen.getByLabelText('项目主视窗');
expect(workbenchStage.getAttribute('data-resource-view-state')).toBe(
'resources.selected.document',
);
expect(screen.getByLabelText('搜索项目资源')).not.toBeNull();
for (const label of [
'生成视频',
'生成音效',
'生成背景音乐',
'新增 UI 设计',
]) {
expect(screen.queryByRole('button', { name: label })).toBeNull();
}
expect(screen.getByRole('button', { name: '按依赖' })).not.toBeNull();
expect(
screen
.getByRole('button', { name: '按类型' })
.getAttribute('aria-pressed'),
).toBe('true');
expect(screen.getByLabelText('资源类型视图')).not.toBeNull();
expect(screen.getByLabelText('陶泥儿 Agent 对话')).not.toBeNull();
expect(screen.getByLabelText('子 Agent 状态栏')).not.toBeNull();
expect(
workbenchStage
.querySelector('.game-workbench-toolbar')
?.hasAttribute('inert'),
).toBe(false);
expect(screen.getByLabelText('资源类型视图').hasAttribute('inert')).toBe(
false,
);
// 选中资源后浮出的是画布工具条,不再是"资源详情"面板。
expect(screen.queryByRole('dialog')).toBeNull();
expect(artReceiptButton.getAttribute('aria-pressed')).toBe('true');
const receiptToolbar = screen.getByRole('toolbar', {
name: '图片工具栏',
});
// 分类与标签入口已从详情面板搬到选中工具条。
expect(
within(receiptToolbar).getByRole('button', { name: '分类与标签' }),
).not.toBeNull();
expect(styles).not.toMatch(/\.game-resource-card-open\s*\{/);
});
it('loads registered documents, art media, video, and audio with safe failure states inside central focus', async () => {
const manifest = createGameCreationAppManifest(
'workbench-resource-media',
@@ -2382,9 +2245,9 @@ export function registerProjectWorkbenchFoundationTests() {
await openResourceBookCategory('设计文档');
fireEvent.click(getResourceSelectButton('blocked.md'));
expect((await screen.findByRole('alert')).textContent).toBe(
'当前项目策略要求先确认读取文档,确认后请关闭详情并重试',
);
expect(
getResourceSelectButton('blocked.md').getAttribute('aria-pressed'),
).toBe('true');
expect(screen.getByLabelText('陶泥儿 Agent 对话')).not.toBeNull();
expect(screen.getByLabelText('子 Agent 状态栏')).not.toBeNull();
});
@@ -3742,7 +3605,8 @@ export function registerProjectWorkbenchFoundationTests() {
);
fireEvent.click(card!);
expect(screen.getByRole('toolbar', { name: '图片工具栏' })).not.toBeNull();
// 该卡片是 Agent 回执(没有正式素材身份),选中反馈由卡片自身承担。
expect(card?.querySelector('[aria-pressed="true"]')).not.toBeNull();
cleanup();
renderWorkbench();
@@ -3912,7 +3776,7 @@ export function registerProjectWorkbenchFoundationTests() {
fireEvent.pointerCancel(card, { pointerId: 41 });
fireEvent.click(card!);
expect(screen.getByRole('toolbar', { name: '图片工具栏' })).not.toBeNull();
expect(card?.querySelector('[aria-pressed="true"]')).not.toBeNull();
});
it('maps manifest asset kinds into stable type layout ordering', async () => {
@@ -125,7 +125,7 @@ function LiveWorkbench() {
);
}
function DerivedWorkbench() {
function DerivedWorkbench({ includeArt = false }: { includeArt?: boolean }) {
const initial = createGameCreationAppManifest(
'live-canvas-project',
'实时画布项目',
@@ -138,6 +138,17 @@ function DerivedWorkbench() {
localPath: 'docs/rules.md',
source: { kind: 'generated', resourceId: 'rules-resource' },
},
...(includeArt
? [
{
id: 'source-art',
kind: 'art-image',
mediaType: 'image/png',
localPath: 'assets/source-art.png',
source: { kind: 'generated', resourceId: 'art-resource' },
},
]
: []),
];
const [manifest, setManifest] = useState(initial);
canvasFixture.manifest = manifest;
@@ -477,7 +488,7 @@ describe('project resource live canvas integration', () => {
it('derives a new art asset non-destructively from the quick edit panel and reuses the original operation identity', async () => {
const { deriveCalls } = installTauri({ failFirstDerive: true });
render(<DerivedWorkbench />);
render(<DerivedWorkbench includeArt />);
fireEvent.click(screen.getByRole('button', { name: '打开美术资源' }));
fireEvent.click(await findResourceSelectButton('source-art.png'));