From 811b51bd6a21514448c50c511b47f772a38d14c9 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Sat, 11 Apr 2026 13:26:46 +0200 Subject: [PATCH] Update tests for project document serialization and add project name handling --- .../serialization/local-draft-storage.test.ts | 8 +++- .../project-document-json.test.ts | 31 +++++++++++- tests/serialization/project-package.test.ts | 1 + ...p-project-persistence.integration.test.tsx | 47 ++++++++++++++++++- 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/tests/serialization/local-draft-storage.test.ts b/tests/serialization/local-draft-storage.test.ts index 65fce685..adf2e4c4 100644 --- a/tests/serialization/local-draft-storage.test.ts +++ b/tests/serialization/local-draft-storage.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { createBoxBrush } from "../../src/document/brushes"; import { + DEFAULT_PROJECT_NAME, SCENE_DOCUMENT_VERSION, createEmptyProjectDocument, createEmptyProjectScene, @@ -196,7 +197,10 @@ describe("local draft storage", () => { it("stores and restores all project scenes in autosave drafts", () => { const storage = new MemoryStorage(); const document = { - ...createEmptyProjectDocument({ sceneName: "Entry" }), + ...createEmptyProjectDocument({ + name: "Castle Campaign", + sceneName: "Entry" + }), activeSceneId: "scene-hall", scenes: { "scene-main": createEmptyProjectScene({ @@ -228,6 +232,7 @@ describe("local draft storage", () => { return; } + expect(result.document.name).toBe("Castle Campaign"); expect(result.document.activeSceneId).toBe("scene-hall"); expect(Object.keys(result.document.scenes)).toEqual([ "scene-main", @@ -255,6 +260,7 @@ describe("local draft storage", () => { return; } + expect(result.document.name).toBe(DEFAULT_PROJECT_NAME); expect(result.document.scenes[result.document.activeSceneId]?.name).toBe( "Legacy Draft" ); diff --git a/tests/serialization/project-document-json.test.ts b/tests/serialization/project-document-json.test.ts index a313a687..c07fd701 100644 --- a/tests/serialization/project-document-json.test.ts +++ b/tests/serialization/project-document-json.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "vitest"; import { + DEFAULT_PROJECT_NAME, + PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION, RUNNER_LOADING_SCREEN_SCENE_DOCUMENT_VERSION, SCENE_DOCUMENT_VERSION, createEmptyProjectDocument, @@ -16,7 +18,7 @@ import { } from "../../src/serialization/scene-document-json"; describe("project document JSON", () => { - it("round-trips authored scene loading overlay settings", () => { + it("round-trips the project name and authored scene loading overlay settings", () => { const cellarEntry = createSceneEntryEntity({ id: "entity-scene-entry-cellar-stairs", position: { @@ -37,7 +39,10 @@ describe("project document JSON", () => { targetEntryEntityId: cellarEntry.id }); const document = { - ...createEmptyProjectDocument({ sceneName: "Entry" }), + ...createEmptyProjectDocument({ + name: "Castle Project", + sceneName: "Entry" + }), activeSceneId: "scene-cellar", scenes: { "scene-main": createEmptyProjectScene({ @@ -63,6 +68,28 @@ describe("project document JSON", () => { expect(parseProjectDocumentJson(serializedDocument)).toEqual(document); }); + it("migrates pre-project-name multi-scene documents to Untitled Project", () => { + const migratedDocument = parseProjectDocumentJson( + JSON.stringify({ + version: PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION, + activeSceneId: "scene-main", + scenes: { + "scene-main": createEmptyProjectScene({ + id: "scene-main", + name: "Legacy Entry" + }) + }, + materials: createEmptyProjectDocument().materials, + textures: {}, + assets: {} + }) + ); + + expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION); + expect(migratedDocument.name).toBe(DEFAULT_PROJECT_NAME); + expect(migratedDocument.scenes["scene-main"]?.name).toBe("Legacy Entry"); + }); + it("migrates v23 project documents without Scene Entry and Scene Exit entities", () => { const legacyScene = createEmptyProjectScene({ id: "scene-main", diff --git a/tests/serialization/project-package.test.ts b/tests/serialization/project-package.test.ts index 71ad6690..9ecd4c49 100644 --- a/tests/serialization/project-package.test.ts +++ b/tests/serialization/project-package.test.ts @@ -136,6 +136,7 @@ describe("project package serialization", () => { ...createProjectDocument( createEmptySceneDocument({ name: "Portable Entry" }) ), + name: "Portable Campaign", activeSceneId: "scene-dungeon", scenes: { "scene-main": createEmptyProjectScene({ diff --git a/tests/unit/app-project-persistence.integration.test.tsx b/tests/unit/app-project-persistence.integration.test.tsx index 10948fbc..f55d7531 100644 --- a/tests/unit/app-project-persistence.integration.test.tsx +++ b/tests/unit/app-project-persistence.integration.test.tsx @@ -94,7 +94,10 @@ import { App } from "../../src/app/App"; import { createEditorStore } from "../../src/app/editor-store"; describe("App project persistence controls", () => { + let clickedDownloads: string[]; + beforeEach(() => { + clickedDownloads = []; viewportHostInstances.length = 0; saveProjectPackageMock.mockClear(); loadProjectPackageMock.mockClear(); @@ -103,7 +106,11 @@ describe("App project persistence controls", () => { createObjectURL: vi.fn(() => "blob:project"), revokeObjectURL: vi.fn() }); - vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => undefined); + vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation( + function (this: HTMLAnchorElement) { + clickedDownloads.push(this.download); + } + ); }); afterEach(() => { @@ -147,4 +154,42 @@ describe("App project persistence controls", () => { ); }); }); + + it("uses the project name rather than the active scene name for saved packages", async () => { + const store = createEditorStore(); + + render(); + + await waitFor(() => { + expect(viewportHostInstances.length).toBeGreaterThan(0); + }); + + fireEvent.change(screen.getByTestId("toolbar-scene-name"), { + target: { value: "Dungeon Scene" } + }); + fireEvent.blur(screen.getByTestId("toolbar-scene-name")); + + fireEvent.click(screen.getByRole("button", { name: "Save Project" })); + + await waitFor(() => { + expect(clickedDownloads).toEqual(["untitled-project.we3d"]); + }); + + fireEvent.change(screen.getByTestId("toolbar-project-name"), { + target: { value: "Castle Layout" } + }); + fireEvent.blur(screen.getByTestId("toolbar-project-name")); + + fireEvent.click(screen.getByRole("button", { name: "Save Project" })); + + await waitFor(() => { + expect(clickedDownloads).toEqual([ + "untitled-project.we3d", + "castle-layout.we3d" + ]); + }); + + expect(store.getState().document.name).toBe("Dungeon Scene"); + expect(store.getState().projectDocument.name).toBe("Castle Layout"); + }); });