diff --git a/tests/unit/viewport-canvas.test.tsx b/tests/unit/viewport-canvas.test.tsx index 0f1f6747..8b6cff22 100644 --- a/tests/unit/viewport-canvas.test.tsx +++ b/tests/unit/viewport-canvas.test.tsx @@ -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( + + ); + + expect(screen.getByTestId("viewport-time-transport-topLeft")).toBeVisible(); + + rerender( + + ); + + 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) => ( + 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( + 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( + 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({