fix: game audio persistence and editor perf
- Keep game/campaign audio assets referenced (no prune) - Flush pending project save on quit/switch/export to avoid losing campaignAudios - Control: prevent game music restarts on scene changes; allow always-on controls; handle autoplay-after-scene-audio - Editor: reduce ReactFlow churn with stable scene card map; lazy/async image decode - Add contract/unit tests and update test script Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import type { Project } from '../../../shared/types';
|
||||
import type { AssetId, SceneId } from '../../../shared/types/ids';
|
||||
|
||||
import { buildNextSceneCardById } from './sceneCardById';
|
||||
|
||||
function minimalProject(overrides: Partial<Project>): Project {
|
||||
return {
|
||||
id: 'p1' as unknown as Project['id'],
|
||||
meta: {
|
||||
name: 'n',
|
||||
fileBaseName: 'f',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
createdWithAppVersion: '1',
|
||||
appVersion: '1',
|
||||
schemaVersion: 1 as unknown as Project['meta']['schemaVersion'],
|
||||
},
|
||||
scenes: {},
|
||||
assets: {},
|
||||
campaignAudios: [],
|
||||
currentSceneId: null,
|
||||
currentGraphNodeId: null,
|
||||
sceneGraphNodes: [],
|
||||
sceneGraphEdges: [],
|
||||
...overrides,
|
||||
} as unknown as Project;
|
||||
}
|
||||
|
||||
void test('buildNextSceneCardById: does not change refs when irrelevant fields change', () => {
|
||||
const sid = 's1' as SceneId;
|
||||
const base = minimalProject({
|
||||
scenes: {
|
||||
[sid]: {
|
||||
id: sid,
|
||||
title: 'T',
|
||||
description: 'A',
|
||||
media: { videos: [], audios: [{ assetId: 'a1' as AssetId, autoplay: false, loop: true }] },
|
||||
settings: { autoplayVideo: false, autoplayAudio: false, loopVideo: false, loopAudio: false },
|
||||
connections: [],
|
||||
layout: { x: 0, y: 0 },
|
||||
previewAssetId: null,
|
||||
previewAssetType: null,
|
||||
previewVideoAutostart: false,
|
||||
previewRotationDeg: 0,
|
||||
},
|
||||
} as unknown as Project['scenes'],
|
||||
});
|
||||
|
||||
const first = buildNextSceneCardById({}, base);
|
||||
const card1 = first[sid];
|
||||
assert.ok(card1);
|
||||
|
||||
const changedOnlyDescription = minimalProject({
|
||||
...base,
|
||||
scenes: {
|
||||
...base.scenes,
|
||||
[sid]: { ...base.scenes[sid], description: 'B' },
|
||||
},
|
||||
});
|
||||
const second = buildNextSceneCardById(first, changedOnlyDescription);
|
||||
|
||||
assert.equal(second, first, 'record identity should be reused');
|
||||
assert.equal(second[sid], card1, 'card identity should be reused');
|
||||
});
|
||||
|
||||
void test('buildNextSceneCardById: changes card when title changes', () => {
|
||||
const sid = 's1' as SceneId;
|
||||
const base = minimalProject({
|
||||
scenes: {
|
||||
[sid]: {
|
||||
id: sid,
|
||||
title: 'T',
|
||||
description: '',
|
||||
media: { videos: [], audios: [] },
|
||||
settings: { autoplayVideo: false, autoplayAudio: false, loopVideo: false, loopAudio: false },
|
||||
connections: [],
|
||||
layout: { x: 0, y: 0 },
|
||||
previewAssetId: null,
|
||||
previewAssetType: null,
|
||||
previewVideoAutostart: false,
|
||||
previewRotationDeg: 0,
|
||||
},
|
||||
} as unknown as Project['scenes'],
|
||||
});
|
||||
const first = buildNextSceneCardById({}, base);
|
||||
const card1 = first[sid];
|
||||
|
||||
const changedTitle = minimalProject({
|
||||
...base,
|
||||
scenes: {
|
||||
...base.scenes,
|
||||
[sid]: { ...base.scenes[sid], title: 'T2' },
|
||||
},
|
||||
});
|
||||
const second = buildNextSceneCardById(first, changedTitle);
|
||||
|
||||
assert.notEqual(second[sid], card1);
|
||||
});
|
||||
Reference in New Issue
Block a user