auto-git:
[change] src/app/app.css [change] src/commands/set-scene-loading-screen-command.ts [change] src/document/migrate-scene-document.ts [change] src/document/scene-document-validation.ts [change] src/document/scene-document.ts [change] src/runner-web/RunnerCanvas.tsx [change] src/runtime-three/first-person-navigation-controller.ts [change] src/runtime-three/navigation-controller.ts [change] src/runtime-three/orbit-visitor-navigation-controller.ts [change] src/runtime-three/runtime-host.ts [change] tests/domain/editor-store.test.ts [change] tests/serialization/local-draft-storage.test.ts [change] tests/serialization/project-document-json.test.ts [change] tests/serialization/project-package.test.ts [change] tests/unit/runner-canvas.test.tsx [change] tests/unit/runtime-host.test.ts
This commit is contained in:
@@ -84,7 +84,9 @@ describe("EditorStore", () => {
|
||||
const secondSceneId = store.getState().activeSceneId;
|
||||
|
||||
expect(secondSceneId).not.toBe(firstSceneId);
|
||||
expect(Object.keys(store.getState().projectDocument.scenes)).toHaveLength(2);
|
||||
expect(Object.keys(store.getState().projectDocument.scenes)).toHaveLength(
|
||||
2
|
||||
);
|
||||
expect(store.getState().document.name).toBe("Scene 2");
|
||||
|
||||
store.executeCommand(createCreateBoxBrushCommand());
|
||||
@@ -269,9 +271,13 @@ describe("EditorStore", () => {
|
||||
expect(store.getState().whiteboxSelectionMode).toBe("object");
|
||||
expect(store.getState().viewportLayoutMode).toBe("single");
|
||||
expect(store.getState().activeViewportPanelId).toBe("topLeft");
|
||||
expect(store.getState().viewportPanels.topLeft.viewMode).toBe("perspective");
|
||||
expect(store.getState().viewportPanels.topLeft.viewMode).toBe(
|
||||
"perspective"
|
||||
);
|
||||
expect(store.getState().viewportPanels.topRight.viewMode).toBe("top");
|
||||
expect(store.getState().viewportPanels.topRight.displayMode).toBe("authoring");
|
||||
expect(store.getState().viewportPanels.topRight.displayMode).toBe(
|
||||
"authoring"
|
||||
);
|
||||
expect(store.getState().viewportQuadSplit).toEqual({
|
||||
x: 0.5,
|
||||
y: 0.5
|
||||
@@ -289,7 +295,9 @@ describe("EditorStore", () => {
|
||||
expect(store.getState().viewportLayoutMode).toBe("quad");
|
||||
expect(store.getState().activeViewportPanelId).toBe("bottomRight");
|
||||
expect(store.getState().viewportPanels.bottomRight.viewMode).toBe("front");
|
||||
expect(store.getState().viewportPanels.bottomRight.displayMode).toBe("normal");
|
||||
expect(store.getState().viewportPanels.bottomRight.displayMode).toBe(
|
||||
"normal"
|
||||
);
|
||||
expect(store.getState().viewportQuadSplit).toEqual({
|
||||
x: 0.38,
|
||||
y: 0.62
|
||||
@@ -482,7 +490,9 @@ describe("EditorStore", () => {
|
||||
})
|
||||
);
|
||||
|
||||
expect(store.getState().viewportTransientState.transformSession).toMatchObject({
|
||||
expect(
|
||||
store.getState().viewportTransientState.transformSession
|
||||
).toMatchObject({
|
||||
kind: "active",
|
||||
source: "keyboard",
|
||||
sourcePanelId: "bottomRight",
|
||||
|
||||
@@ -75,11 +75,16 @@ describe("local draft storage", () => {
|
||||
expect(result.document.version).toBe(SCENE_DOCUMENT_VERSION);
|
||||
expect(result.document).toEqual(createEmptyProjectDocument());
|
||||
expect(result.diagnostic).toContain("Stored autosave could not be loaded.");
|
||||
expect(result.diagnostic).toContain("Starting with a fresh empty document.");
|
||||
expect(result.diagnostic).toContain(
|
||||
"Starting with a fresh empty document."
|
||||
);
|
||||
});
|
||||
|
||||
it("reports browser storage access failures without throwing", () => {
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(window, "localStorage");
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(
|
||||
window,
|
||||
"localStorage"
|
||||
);
|
||||
|
||||
Object.defineProperty(window, "localStorage", {
|
||||
configurable: true,
|
||||
@@ -92,7 +97,9 @@ describe("local draft storage", () => {
|
||||
const result = getBrowserStorageAccess();
|
||||
|
||||
expect(result.storage).toBeNull();
|
||||
expect(result.diagnostic).toContain("Browser local storage is unavailable.");
|
||||
expect(result.diagnostic).toContain(
|
||||
"Browser local storage is unavailable."
|
||||
);
|
||||
expect(result.diagnostic).toContain("access denied");
|
||||
} finally {
|
||||
if (originalDescriptor !== undefined) {
|
||||
@@ -161,9 +168,9 @@ describe("local draft storage", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(
|
||||
result.document.scenes[result.document.activeSceneId]?.name
|
||||
).toBe("Viewport Draft");
|
||||
expect(result.document.scenes[result.document.activeSceneId]?.name).toBe(
|
||||
"Viewport Draft"
|
||||
);
|
||||
expect(result.viewportLayoutState).toMatchObject({
|
||||
layoutMode: "quad",
|
||||
activePanelId: "bottomRight",
|
||||
@@ -235,7 +242,10 @@ describe("local draft storage", () => {
|
||||
|
||||
it("loads older raw scene-document drafts without requiring viewport layout state", () => {
|
||||
const storage = new MemoryStorage();
|
||||
storage.setItem(DEFAULT_SCENE_DRAFT_STORAGE_KEY, serializeSceneDocument(createEmptySceneDocument({ name: "Legacy Draft" })));
|
||||
storage.setItem(
|
||||
DEFAULT_SCENE_DRAFT_STORAGE_KEY,
|
||||
serializeSceneDocument(createEmptySceneDocument({ name: "Legacy Draft" }))
|
||||
);
|
||||
|
||||
const result = loadSceneDocumentDraft(storage);
|
||||
|
||||
@@ -245,9 +255,9 @@ describe("local draft storage", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(
|
||||
result.document.scenes[result.document.activeSceneId]?.name
|
||||
).toBe("Legacy Draft");
|
||||
expect(result.document.scenes[result.document.activeSceneId]?.name).toBe(
|
||||
"Legacy Draft"
|
||||
);
|
||||
expect(result.viewportLayoutState).toBeNull();
|
||||
expect(result.message).toBe("Recovered latest autosave.");
|
||||
});
|
||||
@@ -263,9 +273,9 @@ describe("local draft storage", () => {
|
||||
|
||||
const result = loadOrCreateSceneDocument(storage);
|
||||
|
||||
expect(
|
||||
result.document.scenes[result.document.activeSceneId]?.name
|
||||
).toBe("Recovered Scene");
|
||||
expect(result.document.scenes[result.document.activeSceneId]?.name).toBe(
|
||||
"Recovered Scene"
|
||||
);
|
||||
expect(result.diagnostic).toBe("Recovered latest autosave.");
|
||||
});
|
||||
|
||||
|
||||
@@ -44,8 +44,10 @@ describe("project document JSON", () => {
|
||||
id: "scene-main",
|
||||
name: "Legacy Entry"
|
||||
});
|
||||
const { loadingScreen: _loadingScreen, ...legacySceneWithoutLoadingScreen } =
|
||||
legacyScene;
|
||||
const {
|
||||
loadingScreen: _loadingScreen,
|
||||
...legacySceneWithoutLoadingScreen
|
||||
} = legacyScene;
|
||||
void _loadingScreen;
|
||||
|
||||
const migratedDocument = parseProjectDocumentJson(
|
||||
|
||||
@@ -5,11 +5,21 @@ import { strToU8, unzipSync, Zip, ZipDeflate } from "fflate";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { loadAudioAssetFromStorage } from "../../src/assets/audio-assets";
|
||||
import { loadModelAssetFromStorage, importModelAssetFromFiles } from "../../src/assets/gltf-model-import";
|
||||
import {
|
||||
loadModelAssetFromStorage,
|
||||
importModelAssetFromFiles
|
||||
} from "../../src/assets/gltf-model-import";
|
||||
import { loadImageAssetFromStorage } from "../../src/assets/image-assets";
|
||||
import { createModelInstance } from "../../src/assets/model-instances";
|
||||
import { createInMemoryProjectAssetStorage, type ProjectAssetStorage } from "../../src/assets/project-asset-storage";
|
||||
import { createProjectAssetStorageKey, type AudioAssetRecord, type ImageAssetRecord } from "../../src/assets/project-assets";
|
||||
import {
|
||||
createInMemoryProjectAssetStorage,
|
||||
type ProjectAssetStorage
|
||||
} from "../../src/assets/project-asset-storage";
|
||||
import {
|
||||
createProjectAssetStorageKey,
|
||||
type AudioAssetRecord,
|
||||
type ImageAssetRecord
|
||||
} from "../../src/assets/project-assets";
|
||||
import {
|
||||
createEmptyProjectScene,
|
||||
createEmptySceneDocument,
|
||||
@@ -22,11 +32,24 @@ import {
|
||||
} from "../../src/serialization/project-package";
|
||||
import { serializeProjectDocument } from "../../src/serialization/scene-document-json";
|
||||
|
||||
const tinyGlbFixturePath = path.resolve(process.cwd(), "fixtures/assets/tiny-triangle.glb");
|
||||
const externalTriangleGltfPath = path.resolve(process.cwd(), "fixtures/assets/external-triangle/scene.gltf");
|
||||
const externalTriangleBinPath = path.resolve(process.cwd(), "fixtures/assets/external-triangle/triangle.bin");
|
||||
const tinyGlbFixturePath = path.resolve(
|
||||
process.cwd(),
|
||||
"fixtures/assets/tiny-triangle.glb"
|
||||
);
|
||||
const externalTriangleGltfPath = path.resolve(
|
||||
process.cwd(),
|
||||
"fixtures/assets/external-triangle/scene.gltf"
|
||||
);
|
||||
const externalTriangleBinPath = path.resolve(
|
||||
process.cwd(),
|
||||
"fixtures/assets/external-triangle/triangle.bin"
|
||||
);
|
||||
|
||||
function createTestFile(bytes: Uint8Array | Buffer, name: string, type: string): File {
|
||||
function createTestFile(
|
||||
bytes: Uint8Array | Buffer,
|
||||
name: string,
|
||||
type: string
|
||||
): File {
|
||||
const arrayBuffer = new ArrayBuffer(bytes.byteLength);
|
||||
new Uint8Array(arrayBuffer).set(bytes);
|
||||
|
||||
@@ -72,7 +95,9 @@ function buildZipArchive(entries: Record<string, Uint8Array>): Uint8Array {
|
||||
|
||||
zip.end();
|
||||
|
||||
const archiveBytes = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
|
||||
const archiveBytes = new Uint8Array(
|
||||
chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0)
|
||||
);
|
||||
let offset = 0;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
@@ -177,7 +202,11 @@ describe("project package serialization", () => {
|
||||
await storage.putAsset(imageAsset.storageKey, {
|
||||
files: {
|
||||
[imageAsset.sourceName]: {
|
||||
bytes: cloneArrayBuffer(strToU8("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1024\" height=\"512\"></svg>")),
|
||||
bytes: cloneArrayBuffer(
|
||||
strToU8(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="512"></svg>'
|
||||
)
|
||||
),
|
||||
mimeType: imageAsset.mimeType
|
||||
}
|
||||
}
|
||||
@@ -191,7 +220,10 @@ describe("project package serialization", () => {
|
||||
}
|
||||
});
|
||||
|
||||
const imageLoadListeners = new WeakMap<object, { load?: () => void; error?: () => void }>();
|
||||
const imageLoadListeners = new WeakMap<
|
||||
object,
|
||||
{ load?: () => void; error?: () => void }
|
||||
>();
|
||||
const mockImageWidth = 1024;
|
||||
const mockImageHeight = 512;
|
||||
|
||||
@@ -247,18 +279,32 @@ describe("project package serialization", () => {
|
||||
|
||||
const packageBytes = await saveProjectPackage(document, storage);
|
||||
const restoredStorage = createInMemoryProjectAssetStorage();
|
||||
const restoredDocument = await loadProjectPackage(packageBytes, restoredStorage);
|
||||
const restoredDocument = await loadProjectPackage(
|
||||
packageBytes,
|
||||
restoredStorage
|
||||
);
|
||||
|
||||
expect(restoredDocument).toEqual(document);
|
||||
|
||||
const restoredModel = await loadModelAssetFromStorage(restoredStorage, importedModel.asset);
|
||||
const restoredImage = await loadImageAssetFromStorage(restoredStorage, imageAsset);
|
||||
const restoredAudio = await loadAudioAssetFromStorage(restoredStorage, audioAsset);
|
||||
const restoredModel = await loadModelAssetFromStorage(
|
||||
restoredStorage,
|
||||
importedModel.asset
|
||||
);
|
||||
const restoredImage = await loadImageAssetFromStorage(
|
||||
restoredStorage,
|
||||
imageAsset
|
||||
);
|
||||
const restoredAudio = await loadAudioAssetFromStorage(
|
||||
restoredStorage,
|
||||
audioAsset
|
||||
);
|
||||
|
||||
expect(restoredModel.metadata.format).toBe("glb");
|
||||
expect(restoredModel.template.children.length).toBeGreaterThan(0);
|
||||
expect(restoredImage.metadata.width).toBe(imageAsset.metadata.width);
|
||||
expect(restoredAudio.metadata.durationSeconds).toBe(audioAsset.metadata.durationSeconds);
|
||||
expect(restoredAudio.metadata.durationSeconds).toBe(
|
||||
audioAsset.metadata.durationSeconds
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves multi-file gltf asset bundles inside the packaged assets directory", async () => {
|
||||
@@ -290,7 +336,10 @@ describe("project package serialization", () => {
|
||||
|
||||
await loadProjectPackage(packageBytes, restoredStorage);
|
||||
|
||||
const restoredModel = await loadModelAssetFromStorage(restoredStorage, importedModel.asset);
|
||||
const restoredModel = await loadModelAssetFromStorage(
|
||||
restoredStorage,
|
||||
importedModel.asset
|
||||
);
|
||||
|
||||
expect(restoredModel.metadata.format).toBe("gltf");
|
||||
expect(restoredModel.template.children.length).toBeGreaterThan(0);
|
||||
@@ -320,7 +369,9 @@ describe("project package serialization", () => {
|
||||
}
|
||||
});
|
||||
|
||||
await expect(saveProjectPackage(document, storage)).rejects.toThrow("Missing stored binary data for image asset missing.png.");
|
||||
await expect(saveProjectPackage(document, storage)).rejects.toThrow(
|
||||
"Missing stored binary data for image asset missing.png."
|
||||
);
|
||||
});
|
||||
|
||||
it("fails project load when scene.json is missing", async () => {
|
||||
@@ -328,7 +379,9 @@ describe("project package serialization", () => {
|
||||
"assets/readme.txt": strToU8("not a project")
|
||||
});
|
||||
|
||||
await expect(loadProjectPackage(packageBytes, null)).rejects.toThrow("project package is missing scene.json");
|
||||
await expect(loadProjectPackage(packageBytes, null)).rejects.toThrow(
|
||||
"project package is missing scene.json"
|
||||
);
|
||||
});
|
||||
|
||||
it("fails project load when a declared asset has no packaged files", async () => {
|
||||
@@ -357,7 +410,9 @@ describe("project package serialization", () => {
|
||||
[PROJECT_PACKAGE_SCENE_PATH]: strToU8(serializeProjectDocument(document))
|
||||
});
|
||||
|
||||
await expect(loadProjectPackage(packageBytes, createInMemoryProjectAssetStorage())).rejects.toThrow(
|
||||
await expect(
|
||||
loadProjectPackage(packageBytes, createInMemoryProjectAssetStorage())
|
||||
).rejects.toThrow(
|
||||
"project package is missing bundled files for image asset missing.svg"
|
||||
);
|
||||
});
|
||||
@@ -366,8 +421,13 @@ describe("project package serialization", () => {
|
||||
const document = createProjectDocument(
|
||||
createEmptySceneDocument({ name: "Portable Scene Without Storage" })
|
||||
);
|
||||
const packageBytes = await saveProjectPackage(document, createInMemoryProjectAssetStorage());
|
||||
const packageBytes = await saveProjectPackage(
|
||||
document,
|
||||
createInMemoryProjectAssetStorage()
|
||||
);
|
||||
|
||||
await expect(loadProjectPackage(packageBytes, null as ProjectAssetStorage | null)).resolves.toEqual(document);
|
||||
await expect(
|
||||
loadProjectPackage(packageBytes, null as ProjectAssetStorage | null)
|
||||
).resolves.toEqual(document);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,9 @@ describe("RunnerCanvas", () => {
|
||||
});
|
||||
|
||||
it("only shows the underwater overlay when the camera is submerged", async () => {
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(createEmptySceneDocument());
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(
|
||||
createEmptySceneDocument()
|
||||
);
|
||||
const onTelemetryChange = vi.fn();
|
||||
|
||||
render(
|
||||
@@ -81,12 +83,20 @@ describe("RunnerCanvas", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(runtimeHostInstances).toHaveLength(1);
|
||||
expect(runtimeHostInstances[0]?.setFirstPersonTelemetryHandler).toHaveBeenCalledTimes(1);
|
||||
expect(runtimeHostInstances[0]?.setSceneLoadStateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
runtimeHostInstances[0]?.setFirstPersonTelemetryHandler
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
runtimeHostInstances[0]?.setSceneLoadStateHandler
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const publishTelemetry = runtimeHostInstances[0]?.setFirstPersonTelemetryHandler.mock.calls[0]?.[0] as ((telemetry: FirstPersonTelemetry | null) => void) | undefined;
|
||||
const publishSceneLoadState = runtimeHostInstances[0]?.setSceneLoadStateHandler.mock.calls[0]?.[0] as
|
||||
const publishTelemetry = runtimeHostInstances[0]
|
||||
?.setFirstPersonTelemetryHandler.mock.calls[0]?.[0] as
|
||||
| ((telemetry: FirstPersonTelemetry | null) => void)
|
||||
| undefined;
|
||||
const publishSceneLoadState = runtimeHostInstances[0]
|
||||
?.setSceneLoadStateHandler.mock.calls[0]?.[0] as
|
||||
| ((state: RuntimeSceneLoadState) => void)
|
||||
| undefined;
|
||||
|
||||
@@ -114,7 +124,9 @@ describe("RunnerCanvas", () => {
|
||||
});
|
||||
});
|
||||
|
||||
expect(screen.queryByLabelText("Built-in scene runner")?.className).not.toContain("runner-canvas--underwater");
|
||||
expect(
|
||||
screen.queryByLabelText("Built-in scene runner")?.className
|
||||
).not.toContain("runner-canvas--underwater");
|
||||
expect(document.querySelector(".runner-canvas__underwater")).toBeNull();
|
||||
|
||||
act(() => {
|
||||
@@ -131,12 +143,16 @@ describe("RunnerCanvas", () => {
|
||||
});
|
||||
});
|
||||
|
||||
expect(screen.getByLabelText("Built-in scene runner").className).toContain("runner-canvas--underwater");
|
||||
expect(screen.getByLabelText("Built-in scene runner").className).toContain(
|
||||
"runner-canvas--underwater"
|
||||
);
|
||||
expect(document.querySelector(".runner-canvas__underwater")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("shows the loading overlay until the runtime host reports readiness", async () => {
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(createEmptySceneDocument());
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(
|
||||
createEmptySceneDocument()
|
||||
);
|
||||
|
||||
render(
|
||||
<RunnerCanvas
|
||||
@@ -160,28 +176,31 @@ describe("RunnerCanvas", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(runtimeHostInstances).toHaveLength(1);
|
||||
expect(runtimeHostInstances[0]?.setSceneLoadStateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
runtimeHostInstances[0]?.setSceneLoadStateHandler
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("runner-loading-overlay").className).not.toContain(
|
||||
"runner-canvas__loading-overlay--hidden"
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId("runner-loading-overlay").className
|
||||
).not.toContain("runner-canvas__loading-overlay--hidden");
|
||||
expect(screen.getByTestId("runner-loading-scene-name")).toHaveTextContent(
|
||||
"Dungeon Entry"
|
||||
);
|
||||
expect(screen.getByTestId("runner-loading-headline")).toHaveTextContent(
|
||||
"Preparing encounter"
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId("runner-loading-description")
|
||||
).toHaveTextContent("Enemies and triggers are being wired up.");
|
||||
expect(screen.getByTestId("runner-loading-description")).toHaveTextContent(
|
||||
"Enemies and triggers are being wired up."
|
||||
);
|
||||
expect(document.querySelector(".runner-canvas__crosshair")).toBeNull();
|
||||
expect(screen.getByTestId("runner-shell")).toHaveAttribute(
|
||||
"aria-busy",
|
||||
"true"
|
||||
);
|
||||
|
||||
const publishSceneLoadState = runtimeHostInstances[0]?.setSceneLoadStateHandler.mock.calls[0]?.[0] as
|
||||
const publishSceneLoadState = runtimeHostInstances[0]
|
||||
?.setSceneLoadStateHandler.mock.calls[0]?.[0] as
|
||||
| ((state: RuntimeSceneLoadState) => void)
|
||||
| undefined;
|
||||
|
||||
@@ -205,7 +224,9 @@ describe("RunnerCanvas", () => {
|
||||
});
|
||||
|
||||
it("keeps the overlay visible and shows load errors from the runtime host", async () => {
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(createEmptySceneDocument());
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(
|
||||
createEmptySceneDocument()
|
||||
);
|
||||
|
||||
render(
|
||||
<RunnerCanvas
|
||||
@@ -224,10 +245,13 @@ describe("RunnerCanvas", () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(runtimeHostInstances[0]?.setSceneLoadStateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
runtimeHostInstances[0]?.setSceneLoadStateHandler
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const publishSceneLoadState = runtimeHostInstances[0]?.setSceneLoadStateHandler.mock.calls[0]?.[0] as
|
||||
const publishSceneLoadState = runtimeHostInstances[0]
|
||||
?.setSceneLoadStateHandler.mock.calls[0]?.[0] as
|
||||
| ((state: RuntimeSceneLoadState) => void)
|
||||
| undefined;
|
||||
|
||||
@@ -238,9 +262,9 @@ describe("RunnerCanvas", () => {
|
||||
});
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("runner-loading-overlay").className).not.toContain(
|
||||
"runner-canvas__loading-overlay--hidden"
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId("runner-loading-overlay").className
|
||||
).not.toContain("runner-canvas__loading-overlay--hidden");
|
||||
expect(screen.getByTestId("runner-loading-error")).toHaveTextContent(
|
||||
"Runner scene failed to load: collision bootstrap exploded."
|
||||
);
|
||||
|
||||
@@ -34,7 +34,9 @@ describe("RuntimeHost", () => {
|
||||
});
|
||||
|
||||
it("delays controller activation until collision setup reports the scene as ready", async () => {
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(createEmptySceneDocument());
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(
|
||||
createEmptySceneDocument()
|
||||
);
|
||||
vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
||||
const collisionWorld = {
|
||||
dispose: vi.fn()
|
||||
|
||||
Reference in New Issue
Block a user