Enhance ViewportCanvas unit tests for time transport controls and pointer events

This commit is contained in:
2026-04-27 19:23:33 +02:00
parent 1b3709a81b
commit a27381ee26

View File

@@ -1,4 +1,4 @@
import { act, render, screen, waitFor } from "@testing-library/react";
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
@@ -120,6 +120,7 @@ describe("ViewportCanvas", () => {
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
@@ -805,6 +806,260 @@ describe("ViewportCanvas", () => {
).toHaveTextContent("terrain · paint · layer 3");
});
it("shows the viewport time transport only on the active viewport panel", () => {
const sceneDocument = createEmptySceneDocument();
const commonProps = {
world: sceneDocument.world,
sceneDocument,
editorSimulationController: new EditorSimulationController(),
projectAssets: sceneDocument.assets,
loadedModelAssets: {},
loadedImageAssets: {},
whiteboxSelectionMode: "object" as const,
whiteboxSnapEnabled: true,
whiteboxSnapStep: 1,
viewportGridVisible: true,
selection: { kind: "none" } as EditorSelection,
activeSelectionId: null,
terrainBrushState: null,
toolMode: "select" as const,
toolPreview: { kind: "none" } as ViewportToolPreview,
transformSession: createInactiveTransformSession(),
cameraState: createDefaultViewportPanelCameraState(),
viewMode: "perspective" as const,
displayMode: "normal" as const,
layoutMode: "quad" as const,
focusRequestId: 0,
focusSelection: { kind: "none" } as EditorSelection,
onSelectionChange: vi.fn(),
onTerrainBrushCommit: vi.fn(() => true),
onCommitCreation: vi.fn(() => true),
onCameraStateChange: vi.fn(),
onToolPreviewChange: vi.fn(),
onTransformSessionChange: vi.fn(),
onTransformCommit: vi.fn(),
onTransformCancel: vi.fn(),
onPlayEditorSimulation: vi.fn(),
onPauseEditorSimulation: vi.fn(),
onStepEditorSimulation: vi.fn()
};
const { rerender } = render(
<ViewportCanvas panelId="topLeft" isActivePanel {...commonProps} />
);
expect(screen.getByTestId("viewport-time-transport-topLeft")).toBeVisible();
rerender(
<ViewportCanvas
panelId="topRight"
isActivePanel={false}
{...commonProps}
/>
);
expect(
screen.queryByTestId("viewport-time-transport-topRight")
).not.toBeInTheDocument();
});
it("routes viewport time transport play and pause to editor simulation handlers", () => {
const sceneDocument = createEmptySceneDocument();
const onPlayEditorSimulation = vi.fn();
const onPauseEditorSimulation = vi.fn();
const renderCanvas = (editorSimulationPlaying: boolean) => (
<ViewportCanvas
panelId="topLeft"
world={sceneDocument.world}
sceneDocument={sceneDocument}
editorSimulationController={new EditorSimulationController()}
editorSimulationPlaying={editorSimulationPlaying}
projectAssets={sceneDocument.assets}
loadedModelAssets={{}}
loadedImageAssets={{}}
whiteboxSelectionMode="object"
whiteboxSnapEnabled
whiteboxSnapStep={1}
viewportGridVisible={true}
selection={{ kind: "none" }}
activeSelectionId={null}
terrainBrushState={null}
toolMode="select"
toolPreview={{ kind: "none" }}
transformSession={createInactiveTransformSession()}
cameraState={createDefaultViewportPanelCameraState()}
viewMode="perspective"
displayMode="normal"
layoutMode="single"
isActivePanel
focusRequestId={0}
focusSelection={{ kind: "none" }}
onSelectionChange={vi.fn()}
onTerrainBrushCommit={vi.fn(() => true)}
onCommitCreation={vi.fn(() => true)}
onCameraStateChange={vi.fn()}
onToolPreviewChange={vi.fn()}
onTransformSessionChange={vi.fn()}
onTransformCommit={vi.fn()}
onTransformCancel={vi.fn()}
onPlayEditorSimulation={onPlayEditorSimulation}
onPauseEditorSimulation={onPauseEditorSimulation}
onStepEditorSimulation={vi.fn()}
/>
);
const { rerender } = render(renderCanvas(false));
const playToggle = screen.getByTestId("viewport-time-play-toggle-topLeft");
expect(playToggle).toHaveTextContent(">");
fireEvent.click(playToggle);
expect(onPlayEditorSimulation).toHaveBeenCalledTimes(1);
expect(onPauseEditorSimulation).not.toHaveBeenCalled();
rerender(renderCanvas(true));
const pauseToggle = screen.getByTestId("viewport-time-play-toggle-topLeft");
expect(pauseToggle).toHaveTextContent("II");
fireEvent.click(pauseToggle);
expect(onPauseEditorSimulation).toHaveBeenCalledTimes(1);
});
it("steps viewport time once on click and repeatedly while held", () => {
vi.useFakeTimers();
const sceneDocument = createEmptySceneDocument();
const onStepEditorSimulation = vi.fn();
render(
<ViewportCanvas
panelId="topLeft"
world={sceneDocument.world}
sceneDocument={sceneDocument}
editorSimulationController={new EditorSimulationController()}
projectAssets={sceneDocument.assets}
loadedModelAssets={{}}
loadedImageAssets={{}}
whiteboxSelectionMode="object"
whiteboxSnapEnabled
whiteboxSnapStep={1}
viewportGridVisible={true}
selection={{ kind: "none" }}
activeSelectionId={null}
terrainBrushState={null}
toolMode="select"
toolPreview={{ kind: "none" }}
transformSession={createInactiveTransformSession()}
cameraState={createDefaultViewportPanelCameraState()}
viewMode="perspective"
displayMode="normal"
layoutMode="single"
isActivePanel
focusRequestId={0}
focusSelection={{ kind: "none" }}
onSelectionChange={vi.fn()}
onTerrainBrushCommit={vi.fn(() => true)}
onCommitCreation={vi.fn(() => true)}
onCameraStateChange={vi.fn()}
onToolPreviewChange={vi.fn()}
onTransformSessionChange={vi.fn()}
onTransformCommit={vi.fn()}
onTransformCancel={vi.fn()}
onPlayEditorSimulation={vi.fn()}
onPauseEditorSimulation={vi.fn()}
onStepEditorSimulation={onStepEditorSimulation}
/>
);
fireEvent.click(screen.getByTestId("viewport-time-rewind-topLeft"));
expect(onStepEditorSimulation).toHaveBeenLastCalledWith(-0.25);
expect(onStepEditorSimulation).toHaveBeenCalledTimes(1);
const fastForward = screen.getByTestId("viewport-time-forward-topLeft");
fireEvent.pointerDown(fastForward, { button: 0, pointerId: 1 });
expect(onStepEditorSimulation).toHaveBeenLastCalledWith(0.25);
expect(onStepEditorSimulation).toHaveBeenCalledTimes(2);
act(() => {
vi.advanceTimersByTime(124);
});
expect(onStepEditorSimulation).toHaveBeenCalledTimes(2);
act(() => {
vi.advanceTimersByTime(1);
});
expect(onStepEditorSimulation).toHaveBeenCalledTimes(3);
fireEvent.pointerUp(fastForward, { pointerId: 1 });
act(() => {
vi.advanceTimersByTime(250);
});
expect(onStepEditorSimulation).toHaveBeenCalledTimes(3);
});
it("stops viewport time repeat stepping on pointer cancel and blur", () => {
vi.useFakeTimers();
const sceneDocument = createEmptySceneDocument();
const onStepEditorSimulation = vi.fn();
render(
<ViewportCanvas
panelId="topLeft"
world={sceneDocument.world}
sceneDocument={sceneDocument}
editorSimulationController={new EditorSimulationController()}
projectAssets={sceneDocument.assets}
loadedModelAssets={{}}
loadedImageAssets={{}}
whiteboxSelectionMode="object"
whiteboxSnapEnabled
whiteboxSnapStep={1}
viewportGridVisible={true}
selection={{ kind: "none" }}
activeSelectionId={null}
terrainBrushState={null}
toolMode="select"
toolPreview={{ kind: "none" }}
transformSession={createInactiveTransformSession()}
cameraState={createDefaultViewportPanelCameraState()}
viewMode="perspective"
displayMode="normal"
layoutMode="single"
isActivePanel
focusRequestId={0}
focusSelection={{ kind: "none" }}
onSelectionChange={vi.fn()}
onTerrainBrushCommit={vi.fn(() => true)}
onCommitCreation={vi.fn(() => true)}
onCameraStateChange={vi.fn()}
onToolPreviewChange={vi.fn()}
onTransformSessionChange={vi.fn()}
onTransformCommit={vi.fn()}
onTransformCancel={vi.fn()}
onPlayEditorSimulation={vi.fn()}
onPauseEditorSimulation={vi.fn()}
onStepEditorSimulation={onStepEditorSimulation}
/>
);
const rewind = screen.getByTestId("viewport-time-rewind-topLeft");
fireEvent.pointerDown(rewind, { button: 0, pointerId: 2 });
fireEvent.pointerCancel(rewind, { pointerId: 2 });
act(() => {
vi.advanceTimersByTime(250);
});
expect(onStepEditorSimulation).toHaveBeenCalledTimes(1);
const fastForward = screen.getByTestId("viewport-time-forward-topLeft");
fireEvent.pointerDown(fastForward, { button: 0, pointerId: 3 });
fireEvent.blur(fastForward);
act(() => {
vi.advanceTimersByTime(250);
});
expect(onStepEditorSimulation).toHaveBeenCalledTimes(2);
});
it("does not refocus the viewport when the scene document changes without a new focus request", async () => {
const baseSceneDocument = createEmptySceneDocument();
const focusedTerrain = createTerrain({