完成资源管理阶段七验收
补齐视频与资源读取失败空态测试 完成全量前后端和 Rust CI 验收 修复 macOS/BSD 跨平台门禁与测试隔离 更新阶段七验收与共享项目记忆
This commit is contained in:
@@ -885,6 +885,10 @@ function readBrowserDom(url) {
|
||||
|
||||
function resolveChromeBin() {
|
||||
for (const candidate of [
|
||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
||||
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
||||
'/opt/google/chrome/chrome',
|
||||
'/usr/bin/google-chrome',
|
||||
'/usr/bin/google-chrome-stable',
|
||||
'/usr/bin/chromium',
|
||||
|
||||
@@ -2057,7 +2057,7 @@ mod canvas_generation_tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn recovery_scan_resumes_accepted_generation_on_default_worker_stack() {
|
||||
let temporary = tempfile::tempdir().expect("create accepted scan project");
|
||||
let temporary = crate::tests::canonical_test_tempdir("accepted-generation-scan-");
|
||||
let root = temporary.path();
|
||||
init_local_game_project_at(root, "accepted-scan", "恢复扫描测试")
|
||||
.expect("init accepted scan project");
|
||||
|
||||
+2
-2
@@ -736,7 +736,7 @@ mod external_generation_state_tests {
|
||||
|
||||
#[test]
|
||||
fn prepared_generation_state_reuses_identity_and_only_accepted_can_resume() {
|
||||
let temporary = tempfile::tempdir().expect("create generation ledger project");
|
||||
let temporary = crate::tests::canonical_test_tempdir("external-generation-ledger-");
|
||||
let root = temporary.path();
|
||||
init_local_game_project_at(root, "generation-ledger", "生成账本测试")
|
||||
.expect("init project");
|
||||
@@ -846,7 +846,7 @@ mod external_generation_state_tests {
|
||||
|
||||
#[test]
|
||||
fn legacy_completed_generation_persists_only_allowlisted_safe_download_fields() {
|
||||
let temporary = tempfile::tempdir().expect("create legacy generation ledger project");
|
||||
let temporary = crate::tests::canonical_test_tempdir("legacy-generation-ledger-");
|
||||
let root = temporary.path();
|
||||
init_local_game_project_at(root, "legacy-generation-ledger", "旧同步生成账本测试")
|
||||
.expect("init project");
|
||||
|
||||
+1
-1
@@ -790,7 +790,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn generation_cleanup_failure_preserves_pending_identity_anchor() {
|
||||
let temporary = tempfile::tempdir().expect("create pending cleanup project");
|
||||
let temporary = crate::tests::canonical_test_tempdir("pending-generation-cleanup-");
|
||||
let root = temporary.path();
|
||||
let run_id = "generation-cleanup-order-run";
|
||||
init_local_game_project_at(root, "generation-cleanup-order", "生成账本清理顺序测试")
|
||||
|
||||
+1
-1
@@ -425,7 +425,7 @@ mod tests {
|
||||
const ORDINARY_NOTICE: &str = "除下方有界仓库启动上下文、当前 Session 未压缩对话尾部或历史压缩摘要外,项目记忆、资产和源码正文不会预加载";
|
||||
const MEMORY_MARKER: &str = "supervisor-preloaded-context-marker";
|
||||
|
||||
let directory = tempfile::tempdir().expect("temp project directory");
|
||||
let directory = crate::tests::canonical_test_tempdir("provider-request-project-");
|
||||
let root = directory.path().join("project");
|
||||
init_local_game_project_at(&root, "project-1", "项目总控预加载说明测试")
|
||||
.expect("project init");
|
||||
|
||||
+4
-16
@@ -64,14 +64,8 @@ async fn game_chat_absolute_deadline_returns_an_in_flight_result_before_expiry()
|
||||
|
||||
#[tokio::test]
|
||||
async fn game_chat_absolute_deadline_preserves_external_generation_reconciliation() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"genarrative-game-chat-deadline-reconciliation-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("system clock")
|
||||
.as_nanos()
|
||||
));
|
||||
let temporary = crate::tests::canonical_test_tempdir("game-chat-deadline-reconciliation-");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "deadline-reconciliation", "硬截止收尾测试")
|
||||
.expect("project init");
|
||||
bind_game_creator_agent_runtime_run_profile_at(
|
||||
@@ -271,14 +265,8 @@ async fn game_chat_absolute_deadline_preserves_external_generation_reconciliatio
|
||||
|
||||
#[test]
|
||||
fn game_chat_absolute_deadline_still_cleans_local_action_recovery() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"genarrative-game-chat-deadline-local-cleanup-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("system clock")
|
||||
.as_nanos()
|
||||
));
|
||||
let temporary = crate::tests::canonical_test_tempdir("game-chat-deadline-local-cleanup-");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "deadline-local-cleanup", "硬截止本地清理测试")
|
||||
.expect("project init");
|
||||
let mut runtime = start_game_creator_agent_runtime_task_at(
|
||||
|
||||
@@ -767,7 +767,7 @@ fn standard_specialist_empty_plan_has_no_deterministic_final_reply_fallback() {
|
||||
fn autonomous_manifest_waiting_context_persists_without_finishing_parent_run() {
|
||||
const RUN_ID: &str = "autonomous-manifest-waiting-parent";
|
||||
const TASK: &str = "生成完整小游戏并完成项目任务图";
|
||||
let temporary = tempfile::tempdir().expect("create manifest waiting root");
|
||||
let temporary = crate::tests::canonical_test_tempdir("manifest-waiting-");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "manifest-waiting-project", TASK)
|
||||
.expect("init manifest waiting project");
|
||||
@@ -981,7 +981,7 @@ async fn autonomous_supervisor_converged_final_reply_deserialize_commits_fallbac
|
||||
const TASK: &str = "生成一个可完成静态检查和双视口试玩的塔防游戏";
|
||||
const TEST_KEY: &str = "autonomous-final-reply-fallback-key";
|
||||
|
||||
let temporary = tempfile::tempdir().expect("create autonomous fallback root");
|
||||
let temporary = crate::tests::canonical_test_tempdir("autonomous-fallback-");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "autonomous-fallback-project", TASK)
|
||||
.expect("init autonomous fallback project");
|
||||
|
||||
@@ -1221,7 +1221,7 @@ mod orphaned_external_generation_recovery_tests {
|
||||
|
||||
#[test]
|
||||
fn recovery_scan_preserves_active_generation_orphan_then_cleans_terminal_legacy_orphan() {
|
||||
let temporary = tempfile::tempdir().expect("create orphan generation recovery project");
|
||||
let temporary = crate::tests::canonical_test_tempdir("orphan-generation-recovery-");
|
||||
let root = temporary.path();
|
||||
let run_id = "orphan-generation-recovery-run";
|
||||
init_local_game_project_at(root, "orphan-generation-recovery", "孤儿生成账本恢复测试")
|
||||
|
||||
@@ -827,10 +827,11 @@ mod tests {
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("system clock should be after epoch")
|
||||
.as_nanos();
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"genarrative-context-window-boundary-{}-{unique}",
|
||||
let temporary = crate::tests::canonical_test_tempdir(&format!(
|
||||
"context-window-boundary-{}-{unique}-",
|
||||
std::process::id()
|
||||
));
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "project-1", "上下文窗口边界恢复项目")
|
||||
.expect("project init");
|
||||
let mut runtime = start_game_creator_agent_runtime_task_at(
|
||||
|
||||
@@ -1931,7 +1931,10 @@ mod tests {
|
||||
}
|
||||
|
||||
fn mcp_test_project(label: &str) -> PathBuf {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
let temp_root = std::env::temp_dir()
|
||||
.canonicalize()
|
||||
.expect("canonicalize MCP test temp root");
|
||||
let root = temp_root.join(format!(
|
||||
"game-creator-mcp-{label}-{}-{}",
|
||||
std::process::id(),
|
||||
MCP_TEST_PROJECT_COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
|
||||
@@ -1606,5 +1606,12 @@ async fn project_supervisor_resume_rechecks_delegate_policy_after_delivery_reser
|
||||
.expect("read barrier after rejecting reserved delivery")
|
||||
.is_clear());
|
||||
|
||||
let released = wait_for_agent_runtime_lane_release_async(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(released.state.run_id, parent_run_id);
|
||||
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
@@ -120,6 +120,16 @@ fn unique_project_path() -> PathBuf {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn canonical_test_tempdir(prefix: &str) -> tempfile::TempDir {
|
||||
let temp_root = std::env::temp_dir()
|
||||
.canonicalize()
|
||||
.expect("canonicalize test temp root");
|
||||
tempfile::Builder::new()
|
||||
.prefix(prefix)
|
||||
.tempdir_in(temp_root)
|
||||
.expect("create test temp directory under canonical root")
|
||||
}
|
||||
|
||||
fn agent_goal_sidecar_path_for_test(root: &Path, agent_id: &str, session_id: &str) -> PathBuf {
|
||||
let path_key = |value: &str| {
|
||||
format!("{:x}", Sha256::digest(value.as_bytes()))
|
||||
|
||||
@@ -56,7 +56,6 @@ import {
|
||||
type ProjectResource,
|
||||
type ProjectResourceCategory,
|
||||
projectResourcesFromReadModels,
|
||||
type ProjectVersionResourceSummary,
|
||||
} from './resourceProjectionModel';
|
||||
import { useProjectResourceCanvasLayout } from './useProjectResourceCanvasLayout';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type {
|
||||
GameIterationVersion,
|
||||
GameCreationAppManifest,
|
||||
GameIterationVersion,
|
||||
ProjectResourceCanvasSection,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
|
||||
|
||||
@@ -931,7 +931,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
expect(restoredCard?.getAttribute('aria-pressed')).toBe('true');
|
||||
});
|
||||
|
||||
it('loads registered documents, art media, and audio inside the central focus state', async () => {
|
||||
it('loads registered documents, art media, video, and audio with safe failure states inside central focus', async () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'workbench-resource-media',
|
||||
'资源媒体测试',
|
||||
@@ -951,6 +951,13 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
localPath: 'assets/icon.svg',
|
||||
source: { kind: 'generated' },
|
||||
},
|
||||
{
|
||||
id: 'art-video',
|
||||
kind: 'animation',
|
||||
mediaType: 'video/mp4',
|
||||
localPath: 'assets/intro.mp4',
|
||||
source: { kind: 'generated' },
|
||||
},
|
||||
{
|
||||
id: 'audio-bgm',
|
||||
kind: 'bgm',
|
||||
@@ -958,6 +965,13 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
localPath: 'assets/bgm.mp3',
|
||||
source: { kind: 'generated' },
|
||||
},
|
||||
{
|
||||
id: 'blocked-document',
|
||||
kind: 'design-document',
|
||||
mediaType: 'text/markdown',
|
||||
localPath: 'game/blocked.md',
|
||||
source: { kind: 'generated' },
|
||||
},
|
||||
);
|
||||
let layoutRevision = 0;
|
||||
const invoke = vi.fn(
|
||||
@@ -990,6 +1004,9 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
};
|
||||
}
|
||||
if (command === 'read_local_project_text_preview') {
|
||||
if (args?.relativePath === 'game/blocked.md') {
|
||||
throw new Error('项目权限策略要求用户确认:file.read');
|
||||
}
|
||||
expect(args).toMatchObject({
|
||||
projectPath: '/tmp/workbench-resource-media',
|
||||
relativePath: 'game/design.md',
|
||||
@@ -1004,6 +1021,14 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
}
|
||||
if (command === 'read_local_project_media_preview') {
|
||||
if (args?.category === 'art') {
|
||||
if (args?.relativePath === 'assets/intro.mp4') {
|
||||
return {
|
||||
path: 'assets/intro.mp4',
|
||||
mediaType: 'video/mp4',
|
||||
byteLen: 128,
|
||||
dataUrl: 'data:video/mp4;base64,AAAAIGZ0eXA=',
|
||||
};
|
||||
}
|
||||
return {
|
||||
path: 'assets/icon.svg',
|
||||
mediaType: 'image/svg+xml',
|
||||
@@ -1065,6 +1090,22 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /intro\.mp4/ }));
|
||||
const video = (await screen.findByLabelText(
|
||||
'intro.mp4 视频预览',
|
||||
)) as HTMLVideoElement;
|
||||
expect(video.controls).toBe(true);
|
||||
expect(video.preload).toBe('metadata');
|
||||
expect(video.getAttribute('src')).toBe(
|
||||
'data:video/mp4;base64,AAAAIGZ0eXA=',
|
||||
);
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('region', { name: 'intro.mp4' })).getByRole(
|
||||
'button',
|
||||
{ name: '收起资源' },
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /bgm\.mp3/ }));
|
||||
const audio = (await screen.findByLabelText(
|
||||
'bgm.mp3 音频播放器',
|
||||
@@ -1076,6 +1117,19 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
'read_local_project_media_preview',
|
||||
expect.objectContaining({ category: 'audio' }),
|
||||
);
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('region', { name: 'bgm.mp3' })).getByRole(
|
||||
'button',
|
||||
{ name: '收起资源' },
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /blocked\.md/ }));
|
||||
expect((await screen.findByRole('alert')).textContent).toBe(
|
||||
'当前项目策略要求先确认读取资源',
|
||||
);
|
||||
expect(screen.getByLabelText('陶泥儿 Agent 对话')).not.toBeNull();
|
||||
expect(screen.getByLabelText('子 Agent 状态栏')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders, filters, highlights, and destroys the resource dependency overlay without pointer previews', async () => {
|
||||
|
||||
Reference in New Issue
Block a user