WIP: AGC 资源画布与替换改造 V3.0 #316
@@ -199,6 +199,38 @@ function submitChat(value: string) {
|
||||
fireEvent.click(screen.getByRole('button', { name: '发送' }));
|
||||
}
|
||||
|
||||
// 同一张资源卡上有两个按钮:打开详情与 V3 新增的 @ 引用按钮,二者的可访问名都包含
|
||||
// 资源文件名,因此按文件名正则查询会同时命中两个元素。资源详情按钮的可访问名模板固定为
|
||||
// `打开资源详情:<分类标签> <资源文件名>`(分类标签内不含空格),这里按模板做精确匹配。
|
||||
function escapeRegExpLiteral(value: string) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function resourceDetailButtonName(label: string) {
|
||||
return new RegExp(`^打开资源详情:\\S+ ${escapeRegExpLiteral(label)}$`);
|
||||
}
|
||||
|
||||
function getResourceDetailButton(label: string) {
|
||||
return screen.getByRole('button', { name: resourceDetailButtonName(label) });
|
||||
}
|
||||
|
||||
function findResourceDetailButton(
|
||||
label: string,
|
||||
options?: { timeout?: number; interval?: number },
|
||||
) {
|
||||
return screen.findByRole(
|
||||
'button',
|
||||
{ name: resourceDetailButtonName(label) },
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
function queryResourceDetailButton(label: string) {
|
||||
return screen.queryByRole('button', {
|
||||
name: resourceDetailButtonName(label),
|
||||
});
|
||||
}
|
||||
|
||||
function emptyProjectPolicy() {
|
||||
return {
|
||||
path: '.agent/policy.json',
|
||||
@@ -1094,8 +1126,10 @@ export {
|
||||
describe,
|
||||
emptyProjectPolicy,
|
||||
expect,
|
||||
findResourceDetailButton,
|
||||
fireEvent,
|
||||
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||||
getResourceDetailButton,
|
||||
it,
|
||||
mockRoleAgentReply,
|
||||
nativeClipboardMock,
|
||||
@@ -1103,6 +1137,7 @@ export {
|
||||
pickProjectFromLauncher,
|
||||
ProjectDevelopmentView,
|
||||
projectSupervisorResponseStream,
|
||||
queryResourceDetailButton,
|
||||
React,
|
||||
readFileSync,
|
||||
render,
|
||||
|
||||
@@ -9,10 +9,13 @@ import {
|
||||
createGameCreationAppManifest,
|
||||
createProjectSupervisorRuntimeHarness,
|
||||
expect,
|
||||
findResourceDetailButton,
|
||||
fireEvent,
|
||||
getResourceDetailButton,
|
||||
it,
|
||||
nativeClipboardMock,
|
||||
pickProjectFromLauncher,
|
||||
queryResourceDetailButton,
|
||||
React,
|
||||
render,
|
||||
renderAppAt,
|
||||
@@ -312,14 +315,12 @@ export function registerClientHomeTests() {
|
||||
pickProjectFromLauncher(projectPath);
|
||||
const runButton = await screen.findByRole('tab', { name: '运行' });
|
||||
expect(runButton.getAttribute('data-unavailable')).toBe('true');
|
||||
expect(screen.queryByRole('button', { name: /live-hero\.png/ })).toBeNull();
|
||||
expect(queryResourceDetailButton('live-hero.png')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '同步最新 manifest' }));
|
||||
|
||||
await openResourceBookCategory('美术资源');
|
||||
expect(
|
||||
await screen.findByRole('button', { name: /live-hero\.png/ }),
|
||||
).not.toBeNull();
|
||||
expect(await findResourceDetailButton('live-hero.png')).not.toBeNull();
|
||||
await openResourceBookCategory('项目版本');
|
||||
expect(
|
||||
await screen.findByRole('button', { name: /版本 1/ }),
|
||||
@@ -460,9 +461,7 @@ export function registerClientHomeTests() {
|
||||
pickProjectFromLauncher(projectPath);
|
||||
const runButton = await screen.findByRole('tab', { name: '运行' });
|
||||
expect(runButton.getAttribute('data-unavailable')).toBe('true');
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /runtime-live-hero\.png/ }),
|
||||
).toBeNull();
|
||||
expect(queryResourceDetailButton('runtime-live-hero.png')).toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(runtimeHarness.listen).toHaveBeenCalledWith(
|
||||
'game-creator-manifest-invalidated',
|
||||
@@ -488,11 +487,9 @@ export function registerClientHomeTests() {
|
||||
|
||||
await openResourceBookCategory('美术资源');
|
||||
expect(
|
||||
await screen.findByRole(
|
||||
'button',
|
||||
{ name: /runtime-live-hero\.png/ },
|
||||
{ timeout: 5_000 },
|
||||
),
|
||||
await findResourceDetailButton('runtime-live-hero.png', {
|
||||
timeout: 5_000,
|
||||
}),
|
||||
).not.toBeNull();
|
||||
await openResourceBookCategory('项目版本');
|
||||
expect(
|
||||
@@ -665,18 +662,14 @@ export function registerClientHomeTests() {
|
||||
fireEvent.click(screen.getByRole('button', { name: '项目组' }));
|
||||
pickProjectFromLauncher(secondProjectPath);
|
||||
await openResourceBookCategory('美术资源');
|
||||
expect(
|
||||
await screen.findByRole('button', { name: /second\.png/ }),
|
||||
).not.toBeNull();
|
||||
expect(await findResourceDetailButton('second.png')).not.toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
resolveStaleRefresh(staleFirstManifest);
|
||||
await staleRefresh;
|
||||
});
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /stale-first\.png/ }),
|
||||
).toBeNull();
|
||||
expect(screen.getByRole('button', { name: /second\.png/ })).not.toBeNull();
|
||||
expect(queryResourceDetailButton('stale-first.png')).toBeNull();
|
||||
expect(getResourceDetailButton('second.png')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('starts from the client home and opens a project in the same window', async () => {
|
||||
|
||||
@@ -22,14 +22,17 @@ import {
|
||||
createProjectSupervisorRuntimeHarness,
|
||||
emptyProjectPolicy,
|
||||
expect,
|
||||
findResourceDetailButton,
|
||||
fireEvent,
|
||||
GAME_CREATION_AGENT_RUN_SCHEMA_VERSION,
|
||||
type GameCreationAgentRunTrace,
|
||||
getResourceDetailButton,
|
||||
it,
|
||||
mockRoleAgentReply,
|
||||
pickProjectFromLauncher,
|
||||
ProjectDevelopmentView,
|
||||
projectSupervisorResponseStream,
|
||||
queryResourceDetailButton,
|
||||
React,
|
||||
readFileSync,
|
||||
render,
|
||||
@@ -710,7 +713,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
),
|
||||
).toEqual(['设计文档', '美术资源', '音乐音效', '项目版本']);
|
||||
expect(graphResourceIds).toContain('asset:section-code');
|
||||
expect(screen.queryByRole('button', { name: /section\.js/ })).toBeNull();
|
||||
expect(queryResourceDetailButton('section.js')).toBeNull();
|
||||
expect(screen.queryByRole('region', { name: '游戏代码' })).toBeNull();
|
||||
expect(outline.querySelectorAll('small')).toHaveLength(0);
|
||||
expect(screen.getByRole('region', { name: '设计文档' })).not.toBeNull();
|
||||
@@ -1282,7 +1285,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
.getByLabelText(/资源(?:依赖|类型)视图/)
|
||||
.classList.contains('game-resource-canvas--paged'),
|
||||
).toBe(false);
|
||||
expect(screen.queryByRole('button', { name: /game\.js/ })).toBeNull();
|
||||
expect(queryResourceDetailButton('game.js')).toBeNull();
|
||||
expect(screen.queryByRole('region', { name: '游戏代码' })).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1604,9 +1607,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
const showResourcePage = (label: string) => openResourceBookCategory(label);
|
||||
|
||||
await showResourcePage('美术资源');
|
||||
let heroDetailButton = await screen.findByRole('button', {
|
||||
name: /打开资源详情:美术资源 hero\.png/,
|
||||
});
|
||||
let heroDetailButton = await findResourceDetailButton('hero.png');
|
||||
const heroCard = heroDetailButton.closest('.game-resource-card');
|
||||
expect(heroCard?.textContent).not.toContain('hero.png');
|
||||
expect(heroCard?.textContent).not.toContain('assets/hero.png');
|
||||
@@ -1724,9 +1725,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
expect(screen.queryByRole('dialog', { name: 'theme.mp3' })).toBeNull();
|
||||
|
||||
await showResourcePage('美术资源');
|
||||
heroDetailButton = await screen.findByRole('button', {
|
||||
name: /打开资源详情:美术资源 hero\.png/,
|
||||
});
|
||||
heroDetailButton = await findResourceDetailButton('hero.png');
|
||||
fireEvent.click(heroDetailButton);
|
||||
const heroFocus = await screen.findByRole('dialog', { name: 'hero.png' });
|
||||
expect(within(heroFocus).getByText('assets/hero.png')).not.toBeNull();
|
||||
@@ -1769,11 +1768,11 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
const search = screen.getByLabelText('搜索项目资源');
|
||||
fireEvent.change(search, { target: { value: 'assets/hero.png' } });
|
||||
await waitFor(() => expect(pause.mock.instances).toContain(typeVideo));
|
||||
expect(screen.getByRole('button', { name: /hero\.png/ })).not.toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /intro\.mp4/ })).toBeNull();
|
||||
const typeHeroCard = screen
|
||||
.getByRole('button', { name: /hero\.png/ })
|
||||
.closest('.game-resource-card');
|
||||
expect(getResourceDetailButton('hero.png')).not.toBeNull();
|
||||
expect(queryResourceDetailButton('intro.mp4')).toBeNull();
|
||||
const typeHeroCard = getResourceDetailButton('hero.png').closest(
|
||||
'.game-resource-card',
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
typeHeroCard?.querySelector('.game-resource-card-visual > img'),
|
||||
@@ -1899,9 +1898,9 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
document.querySelectorAll('.game-resource-card-visual > img'),
|
||||
).toHaveLength(48);
|
||||
});
|
||||
const wideImageCard = screen
|
||||
.getByRole('button', { name: /image-0.png/ })
|
||||
.closest<HTMLElement>('.game-resource-card');
|
||||
const wideImageCard = getResourceDetailButton(
|
||||
'image-0.png',
|
||||
).closest<HTMLElement>('.game-resource-card');
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wideImageCard?.style.getPropertyValue('--resource-card-width'),
|
||||
@@ -1935,7 +1934,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
await openResourceBookCategory('美术资源');
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('button', { name: /next\.png/ })).not.toBeNull(),
|
||||
expect(getResourceDetailButton('next.png')).not.toBeNull(),
|
||||
);
|
||||
await waitFor(() => expect(observer.observedCount()).toBe(1));
|
||||
act(() => observer.triggerVisible());
|
||||
@@ -2059,18 +2058,18 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
name: /美术资源/,
|
||||
}),
|
||||
);
|
||||
const playerCard = screen
|
||||
.getByRole('button', { name: /player\.png/ })
|
||||
.closest('.game-resource-card');
|
||||
const playerCard = getResourceDetailButton('player.png').closest(
|
||||
'.game-resource-card',
|
||||
);
|
||||
expect(playerCard?.classList.contains('is-relation-version-binding')).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '按依赖' }));
|
||||
await waitFor(() => {
|
||||
const dependencyPlayerCard = screen
|
||||
.getByRole('button', { name: /player\.png/ })
|
||||
.closest('.game-resource-card');
|
||||
const dependencyPlayerCard = getResourceDetailButton(
|
||||
'player.png',
|
||||
).closest('.game-resource-card');
|
||||
expect(
|
||||
dependencyPlayerCard?.classList.contains('is-relation-version-binding'),
|
||||
).toBe(true);
|
||||
@@ -2427,7 +2426,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
|
||||
await openResourceBookCategory('设计文档');
|
||||
fireEvent.click(await screen.findByRole('button', { name: /design\.md/ }));
|
||||
fireEvent.click(await findResourceDetailButton('design.md'));
|
||||
const documentFocus = await screen.findByRole('dialog', {
|
||||
name: 'design.md',
|
||||
});
|
||||
@@ -2439,7 +2438,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
expect(within(documentFocus).getByText('图片:远程图片')).not.toBeNull();
|
||||
expect(documentFocus.hasAttribute('aria-modal')).toBe(false);
|
||||
await openResourceBookCategory('美术资源');
|
||||
fireEvent.click(screen.getByRole('button', { name: /icon\.svg/ }));
|
||||
fireEvent.click(getResourceDetailButton('icon.svg'));
|
||||
expect(screen.queryByRole('dialog', { name: 'design.md' })).toBeNull();
|
||||
const artDetails = await screen.findByRole('dialog', { name: 'icon.svg' });
|
||||
expect(within(artDetails).getByText('assets/icon.svg')).not.toBeNull();
|
||||
@@ -2504,7 +2503,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
|
||||
await openResourceBookCategory('设计文档');
|
||||
fireEvent.click(screen.getByRole('button', { name: /blocked\.md/ }));
|
||||
fireEvent.click(getResourceDetailButton('blocked.md'));
|
||||
expect((await screen.findByRole('alert')).textContent).toBe(
|
||||
'当前项目策略要求先确认读取文档,确认后请关闭详情并重试',
|
||||
);
|
||||
@@ -2585,11 +2584,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
|
||||
await openResourceBookCategory('音乐音效');
|
||||
fireEvent.click(
|
||||
await screen.findByRole('button', {
|
||||
name: '打开资源详情:音乐音效 focus.mp3',
|
||||
}),
|
||||
);
|
||||
fireEvent.click(await findResourceDetailButton('focus.mp3'));
|
||||
const audioDetails = await screen.findByRole('dialog', {
|
||||
name: 'focus.mp3',
|
||||
});
|
||||
@@ -2904,12 +2899,8 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
fireEvent.change(search, { target: { value: '' } });
|
||||
|
||||
let sourceCard = screen.getByRole('button', {
|
||||
name: /spec-source\.json/,
|
||||
});
|
||||
const targetCard = screen.getByRole('button', {
|
||||
name: /ui-dependency\.json/,
|
||||
});
|
||||
let sourceCard = getResourceDetailButton('spec-source.json');
|
||||
const targetCard = getResourceDetailButton('ui-dependency.json');
|
||||
const referenceSelector =
|
||||
'[data-edge-kind="asset-reference"]' +
|
||||
'[data-source-resource-id="asset:dependency-spec"]' +
|
||||
@@ -2984,7 +2975,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
within(targetFocus).getByRole('button', { name: '收起资源' }),
|
||||
);
|
||||
overlay = await screen.findByTestId('resource-dependency-overlay-document');
|
||||
sourceCard = screen.getByRole('button', { name: /spec-source\.json/ });
|
||||
sourceCard = getResourceDetailButton('spec-source.json');
|
||||
await waitFor(() =>
|
||||
expect(overlay.querySelector(referenceSelector)).not.toBeNull(),
|
||||
);
|
||||
@@ -3730,11 +3721,9 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
await waitFor(() => expect(layoutReads).toBe(1));
|
||||
await openResourceBookCategory('设计文档');
|
||||
const cards = [0, 1, 2].map((depth) =>
|
||||
screen
|
||||
.getByRole('button', {
|
||||
name: new RegExp(`truncated-depth-${depth}\\.json`, 'u'),
|
||||
})
|
||||
.closest('.game-resource-card'),
|
||||
getResourceDetailButton(`truncated-depth-${depth}.json`).closest(
|
||||
'.game-resource-card',
|
||||
),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(cards[0]?.getAttribute('style')).toContain('--resource-x: 0px');
|
||||
@@ -3962,9 +3951,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
|
||||
await openResourceBookCategory('美术资源');
|
||||
const cardButton = await screen.findByRole('button', {
|
||||
name: '打开资源详情:美术资源 clickable.png',
|
||||
});
|
||||
const cardButton = await findResourceDetailButton('clickable.png');
|
||||
fireEvent.pointerDown(cardButton, {
|
||||
pointerId: 7,
|
||||
button: 0,
|
||||
@@ -4049,9 +4036,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
|
||||
await openResourceBookCategory('美术资源');
|
||||
const cardButton = await screen.findByRole('button', {
|
||||
name: '打开资源详情:美术资源 cancelable.png',
|
||||
});
|
||||
const cardButton = await findResourceDetailButton('cancelable.png');
|
||||
const card = cardButton.closest<HTMLElement>('.game-resource-card');
|
||||
expect(card).not.toBeNull();
|
||||
Object.defineProperties(card!, {
|
||||
@@ -4534,7 +4519,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
fireEvent.change(screen.getByLabelText('搜索项目资源'), {
|
||||
target: { value: 'hero.png' },
|
||||
});
|
||||
fireEvent.click(await screen.findByRole('button', { name: /hero\.png/ }));
|
||||
fireEvent.click(await findResourceDetailButton('hero.png'));
|
||||
const heroDetails = screen.getByRole('dialog', { name: 'hero.png' });
|
||||
expect(within(heroDetails).getByText('assets/hero.png')).not.toBeNull();
|
||||
expect(within(heroDetails).getByText('image/png')).not.toBeNull();
|
||||
@@ -4587,9 +4572,9 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
|
||||
await openResourceBookCategory('美术资源');
|
||||
const candidate = await screen.findByRole('button', {
|
||||
name: /ui-prototype\.png(待视觉验收)/,
|
||||
});
|
||||
const candidate = await findResourceDetailButton(
|
||||
'ui-prototype.png(待视觉验收)',
|
||||
);
|
||||
const candidateCard = candidate.closest('.game-resource-card');
|
||||
expect(candidateCard?.textContent).not.toContain('画板 · 候选界面图');
|
||||
expect(candidateCard?.textContent).not.toContain('assets/ui-prototype.png');
|
||||
|
||||
@@ -13,6 +13,7 @@ import ProjectDevelopmentView from '../src/view/project-development';
|
||||
import {
|
||||
act,
|
||||
createGameCreationAppManifest,
|
||||
findResourceDetailButton,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
@@ -476,7 +477,7 @@ describe('project resource live canvas integration', () => {
|
||||
const { deriveCalls } = installTauri({ failFirstDerive: true });
|
||||
render(<DerivedWorkbench />);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /rules\.md/ }));
|
||||
fireEvent.click(await findResourceDetailButton('rules.md'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '编辑资源' }));
|
||||
expect(await screen.findByText('编辑现有资源')).not.toBeNull();
|
||||
fireEvent.change(screen.getByLabelText('编辑提示词'), {
|
||||
|
||||
Reference in New Issue
Block a user