mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(director): validate scene documents and preserve saved edits
This commit is contained in:
1 parent
c894d7b920
commit
18300e7e00
13 files changed
+1155
-404
No files matched your search
@@ -614,10 +614,11 @@ function installSceneRuntime(project = PROJECT_FIXTURE) {
|
||||
removeEventListener() {},
|
||||
body: { classList: noopClassList, appendChild() {} },
|
||||
};
|
||||
const stored = new Map([['godsEyeView.sceneProject.v2', JSON.stringify(project)]]);
|
||||
globalThis.localStorage = {
|
||||
getItem: () => JSON.stringify(project),
|
||||
setItem() {},
|
||||
removeItem() {},
|
||||
getItem: (key) => stored.get(key) ?? null,
|
||||
setItem: (key, value) => stored.set(key, value),
|
||||
removeItem: (key) => stored.delete(key),
|
||||
};
|
||||
|
||||
return () => {
|
||||
@@ -1397,3 +1398,96 @@ test('Scene load outcomes exclude superseded and disposed completions', async ()
|
||||
assert.equal(seen.length, count);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('invalid and unsupported imports retain the current project, selection and saved bytes', async () => {
|
||||
const { director, restore } = makeDirector();
|
||||
try {
|
||||
const project = director._project;
|
||||
const selected = director._selectedShotId;
|
||||
const saved = localStorage.getItem('godsEyeView.sceneProject.v2');
|
||||
for (const value of ['{}', '{"version":99,"scenes":[]}', '{"scenes":[{"shots":"bad"}]}']) {
|
||||
await director.importProjectFile({ name: 'bad.json', text: async () => value });
|
||||
assert.equal(director._project, project);
|
||||
assert.equal(director._selectedShotId, selected);
|
||||
assert.equal(localStorage.getItem('godsEyeView.sceneProject.v2'), saved);
|
||||
assert.match(director._presentation.status, /Import failed: \$/);
|
||||
}
|
||||
let read = false;
|
||||
await director.importProjectFile({ size: 6 * 1024 * 1024, text: async () => { read = true; } });
|
||||
assert.equal(read, false);
|
||||
assert.equal(director._project, project);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('newer imports win delayed file reads, and an empty project is preserved', async () => {
|
||||
const { director, restore } = makeDirector();
|
||||
try {
|
||||
let release;
|
||||
const older = director.importProjectFile({ name: 'old.json', text: () => new Promise((resolve) => { release = resolve; }) });
|
||||
await director.importProjectFile({ name: 'empty.json', text: async () => '{"version":3,"scenes":[]}' });
|
||||
release(JSON.stringify(PROJECT_FIXTURE));
|
||||
await older;
|
||||
assert.deepEqual(director._project.scenes, []);
|
||||
assert.equal(director._selectedSceneId, null);
|
||||
assert.deepEqual(JSON.parse(localStorage.getItem('godsEyeView.sceneProject.v2')).scenes, []);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('unsupported stored documents cannot be overwritten by fallback edits', async () => {
|
||||
const { director, restore } = makeDirector({ project: { version: 99, scenes: [] } });
|
||||
try {
|
||||
const saved = localStorage.getItem('godsEyeView.sceneProject.v2');
|
||||
assert.ok(director._storageReadError);
|
||||
director._project.scenes[0].title = 'Fallback edit';
|
||||
director._saveProject();
|
||||
assert.equal(localStorage.getItem('godsEyeView.sceneProject.v2'), saved);
|
||||
await director.importProjectFile({ name: 'valid.json', text: async () => JSON.stringify(PROJECT_FIXTURE) });
|
||||
assert.equal(director._storageReadError, null);
|
||||
assert.equal(JSON.parse(localStorage.getItem('godsEyeView.sceneProject.v2')).version, 3);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('valid import settles a cancelled load before replacing the project', async () => {
|
||||
const { director, styleManager, restore } = makeDirector();
|
||||
let release;
|
||||
styleManager.applyVisualState = () => new Promise((resolve) => { release = resolve; });
|
||||
try {
|
||||
const before = director._project;
|
||||
const load = director.loadShot('scene-1', 'shot-a');
|
||||
await settle();
|
||||
const importing = director.importProjectFile({ name: 'empty.json', text: async () => '{"version":3,"scenes":[]}' });
|
||||
await settle();
|
||||
assert.equal(director._project, before, 'old work still owns cleanup');
|
||||
release(true);
|
||||
await Promise.all([load, importing]);
|
||||
assert.deepEqual(director._project.scenes, []);
|
||||
assert.equal(director._pendingWork.size, 0);
|
||||
assert.equal(director.getPlaybackTimingState().activeTimers, 0);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('a delayed import cannot publish after disposal', async () => {
|
||||
const { director, restore } = makeDirector();
|
||||
let release;
|
||||
try {
|
||||
const before = director._project;
|
||||
const reading = director.importProjectFile({ name: 'empty.json', text: () => new Promise((resolve) => { release = resolve; }) });
|
||||
await director.destroy();
|
||||
release('{"version":3,"scenes":[]}');
|
||||
await reading;
|
||||
assert.equal(director._project, before);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('invalid authored edits cannot persist an unreadable project over a good save', () => {
|
||||
const { director, restore } = makeDirector();
|
||||
try {
|
||||
const before = localStorage.getItem('godsEyeView.sceneProject.v2');
|
||||
let notice;
|
||||
director._toastStorageError = (message) => { notice = message; };
|
||||
director._project.scenes[0].shots[0].camera.lat = 91;
|
||||
director._saveProject();
|
||||
assert.equal(localStorage.getItem('godsEyeView.sceneProject.v2'), before);
|
||||
assert.match(notice, /camera.lat/);
|
||||
} finally { restore(); }
|
||||
});
|
||||
Reference in new issue
Block a user