Files
Genarrative/apps/ai-game-creator-shell/tests/resourceCanvasGenerationFloatingPanel.test.tsx
T
k88936 18252e24b8
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
合并主线并保留资源kind枚举收口
合并 origin/master 的 AGC 画布参考与项目能力更新

解决 canvas 生成恢复与任务模型冲突并继续使用共享 GameCreationAppAssetKind

保留 run_id、generationKind 与外部协议字符串的独立边界
2026-09-19 01:50:09 +08:00

154 lines
5.5 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.
// @vitest-environment jsdom
import {
cleanup,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, describe, expect, test, vi } from 'vitest';
import type { GameCreationAppAssetManifestEntry } from '../../../packages/shared/src/contracts/gameCreationApp';
import { ResourceCanvasAssetGenerationPanelView } from '../src/features/resource-canvas/ResourceCanvasAssetGenerationPanelView';
import type { ResourceCanvasAssetToolAction } from '../src/features/resource-canvas/resourceCanvasBottomToolbarModel';
import { typeGenerationPrompt } from './resourceGenerationPromptTestUtils';
afterEach(cleanup);
const imageAction: ResourceCanvasAssetToolAction = {
id: 'generate-image',
route: 'asset',
label: '生成图片',
assetKind: 'image',
audioKind: null,
assetName: 'AI 生成图片',
promptPlaceholder: '今天想生成什么画面?',
adjustableDimensions: true,
aspectRatio: '1:1',
imageSize: '1K',
requiresIconSpecReference: false,
writesIconSpecReference: false,
};
function asset(
id: string,
label: string,
mediaType = 'image/png',
): GameCreationAppAssetManifestEntry {
return {
id,
kind: 'image',
mediaType,
localPath: `assets/${label}.png`,
source: { kind: 'canvas' },
};
}
/** 取面板里的引用输入区(含 `@` 触发器按钮),把操作局限在它自己身上。 */
function referenceInputScope(ariaLabel: string) {
const root = screen
.getByLabelText(ariaLabel)
.closest('.resource-reference-input');
if (!root) {
throw new Error(`资源引用输入区不存在:${ariaLabel}`);
}
return within(root as HTMLElement);
}
describe('生成浮层里的引用选择(真实组件,不做 props mock', () => {
test('浮层不是模态,真实点击候选就能选中并随提交带走引用', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
const assets = [asset('asset-a', '素材-a'), asset('asset-b', '素材-b')];
render(
<ResourceCanvasAssetGenerationPanelView
action={imageAction}
variant="floating"
assets={assets}
projectPath="/tmp/project"
onSubmit={onSubmit}
onClose={() => undefined}
/>,
);
const panel = screen.getByRole('dialog', { name: '生成图片' });
/*
浮层形态不是模态:没有全屏遮罩、没有 aria-modal。`ThemedModal` 的焦点陷阱正是 P1 的成因——
引用选择器 portal 到 body,被陷阱挡在外面时候选项点了不生效(真实浏览器复现过)。
*/
expect(panel.getAttribute('aria-modal')).toBeNull();
expect(document.querySelector('[aria-modal="true"]')).toBeNull();
expect(document.querySelector('.fixed.inset-0')).toBeNull();
await typeGenerationPrompt(panel, '画一只猫');
await user.click(
referenceInputScope('生成提示词').getByRole('button', {
name: '插入素材引用',
}),
);
const picker = await screen.findByRole('dialog', { name: '选择素材' });
await user.click(within(picker).getByRole('option', { name: /素材-a/ }));
expect(within(picker).getByText('已选择 1 个')).not.toBeNull();
const insert = within(picker).getByRole('button', { name: '插入引用' });
expect((insert as HTMLButtonElement).disabled).toBe(false);
await user.click(insert);
// 选中的引用进了面板:计数可见、提交时随载荷带走。
await waitFor(() =>
expect(within(panel).getByText('参考图 1/5')).not.toBeNull(),
);
await user.click(within(panel).getByRole('button', { name: '生成图片' }));
expect(onSubmit).toHaveBeenCalledTimes(1);
const submitted = onSubmit.mock.calls[0]?.[0] as {
prompt: string;
references: { resourceId: string }[];
};
expect(
submitted.references.map((reference) => reference.resourceId),
).toEqual(['asset-a']);
expect(submitted.prompt).toContain('画一只猫');
});
test('搜索能收窄候选,键盘也能完成选择', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
const assets = [asset('asset-a', '素材-a'), asset('asset-b', '素材-b')];
render(
<ResourceCanvasAssetGenerationPanelView
action={imageAction}
variant="floating"
assets={assets}
projectPath="/tmp/project"
onSubmit={onSubmit}
onClose={() => undefined}
/>,
);
const panel = screen.getByRole('dialog', { name: '生成图片' });
await typeGenerationPrompt(panel, '画一只猫');
await user.click(
referenceInputScope('生成提示词').getByRole('button', {
name: '插入素材引用',
}),
);
const picker = await screen.findByRole('dialog', { name: '选择素材' });
expect(within(picker).getAllByRole('option')).toHaveLength(2);
// 搜索收窄:只剩「素材-b」这一条候选。
await user.clear(screen.getByLabelText('搜索全部画布素材'));
await user.type(screen.getByLabelText('搜索全部画布素材'), '素材-b');
await waitFor(() =>
expect(within(picker).getAllByRole('option')).toHaveLength(1),
);
// 键盘路径:Tab 进候选列表,Enter 选中(不依赖鼠标点击)。
const option = within(picker).getByRole('option', { name: /素材-b/ });
option.focus();
await user.keyboard('{Enter}');
await waitFor(() =>
expect(within(picker).getByText('已选择 1 个')).not.toBeNull(),
);
});
});