import { describe, expect, it } from "vitest"; import { createEditorStore } from "../../src/app/editor-store"; import { createCreateBoxBrushCommand } from "../../src/commands/create-box-brush-command"; import { createMoveBoxBrushCommand } from "../../src/commands/move-box-brush-command"; import { createRotateBoxBrushCommand } from "../../src/commands/rotate-box-brush-command"; import { createResizeBoxBrushCommand } from "../../src/commands/resize-box-brush-command"; import { createSetBoxBrushNameCommand } from "../../src/commands/set-box-brush-name-command"; import { createSetBoxBrushVolumeSettingsCommand } from "../../src/commands/set-box-brush-volume-settings-command"; describe("box brush commands", () => { it("creates a canonical box brush and supports undo/redo", () => { const store = createEditorStore(); store.executeCommand( createCreateBoxBrushCommand({ center: { x: 1.2, y: 1.1, z: -0.6 }, size: { x: 2.2, y: 2.7, z: 3.6 } }) ); const brush = Object.values(store.getState().document.brushes)[0]; expect(brush).toBeDefined(); expect(brush.kind).toBe("box"); expect(brush.center).toEqual({ x: 1, y: 1, z: -1 }); expect(brush.rotationDegrees).toEqual({ x: 0, y: 0, z: 0 }); expect(brush.size).toEqual({ x: 2, y: 3, z: 4 }); expect(Object.keys(brush.faces)).toEqual(["posX", "negX", "posY", "negY", "posZ", "negZ"]); expect(brush.faces.posX).toEqual({ materialId: null, uv: { offset: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, rotationQuarterTurns: 0, flipU: false, flipV: false } }); expect(store.getState().selection).toEqual({ kind: "brushes", ids: [brush.id] }); expect(store.undo()).toBe(true); expect(store.getState().document.brushes).toEqual({}); expect(store.redo()).toBe(true); expect(store.getState().document.brushes[brush.id]).toEqual(brush); }); it("moves and resizes a box brush through undoable commands", () => { const store = createEditorStore(); store.executeCommand(createCreateBoxBrushCommand()); const createdBrush = Object.values(store.getState().document.brushes)[0]; store.executeCommand( createMoveBoxBrushCommand({ brushId: createdBrush.id, center: { x: 2.4, y: 3.2, z: -1.7 } }) ); store.executeCommand( createResizeBoxBrushCommand({ brushId: createdBrush.id, size: { x: 4.2, y: 1.2, z: 0.2 } }) ); expect(store.getState().document.brushes[createdBrush.id].center).toEqual({ x: 2, y: 3, z: -2 }); expect(store.getState().document.brushes[createdBrush.id].size).toEqual({ x: 4, y: 1, z: 1 }); expect(store.undo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].size).toEqual({ x: 2, y: 2, z: 2 }); expect(store.undo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].center).toEqual({ x: 0, y: 1, z: 0 }); expect(store.redo()).toBe(true); expect(store.redo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].center).toEqual({ x: 2, y: 3, z: -2 }); expect(store.getState().document.brushes[createdBrush.id].size).toEqual({ x: 4, y: 1, z: 1 }); }); it("preserves floating-point move, rotate, and scale authoring when snapping is disabled", () => { const store = createEditorStore(); store.executeCommand( createCreateBoxBrushCommand({ center: { x: 1.25, y: 1.5, z: -0.75 }, size: { x: 2.5, y: 3.25, z: 4.75 }, snapToGrid: false }) ); const createdBrush = Object.values(store.getState().document.brushes)[0]; store.executeCommand( createMoveBoxBrushCommand({ brushId: createdBrush.id, center: { x: 2.125, y: 3.375, z: -1.625 }, snapToGrid: false }) ); store.executeCommand( createRotateBoxBrushCommand({ brushId: createdBrush.id, rotationDegrees: { x: 12.5, y: 37.5, z: -8.25 } }) ); store.executeCommand( createResizeBoxBrushCommand({ brushId: createdBrush.id, size: { x: 3.5, y: 1.75, z: 5.125 }, snapToGrid: false }) ); expect(store.getState().document.brushes[createdBrush.id]).toMatchObject({ center: { x: 2.125, y: 3.375, z: -1.625 }, rotationDegrees: { x: 12.5, y: 37.5, z: -8.25 }, size: { x: 3.5, y: 1.75, z: 5.125 } }); expect(store.undo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].size).toEqual({ x: 2.5, y: 3.25, z: 4.75 }); expect(store.undo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].rotationDegrees).toEqual({ x: 0, y: 0, z: 0 }); expect(store.undo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].center).toEqual({ x: 1.25, y: 1.5, z: -0.75 }); expect(store.redo()).toBe(true); expect(store.redo()).toBe(true); expect(store.redo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id]).toMatchObject({ center: { x: 2.125, y: 3.375, z: -1.625 }, rotationDegrees: { x: 12.5, y: 37.5, z: -8.25 }, size: { x: 3.5, y: 1.75, z: 5.125 } }); }); it("renames a box brush through an undoable command", () => { const store = createEditorStore(); store.executeCommand(createCreateBoxBrushCommand()); const createdBrush = Object.values(store.getState().document.brushes)[0]; store.executeCommand( createSetBoxBrushNameCommand({ brushId: createdBrush.id, name: "Entry Room" }) ); expect(store.getState().document.brushes[createdBrush.id].name).toBe("Entry Room"); expect(store.undo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].name).toBeUndefined(); expect(store.redo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].name).toBe("Entry Room"); }); it("updates box volume settings through an undoable command", () => { const store = createEditorStore(); store.executeCommand(createCreateBoxBrushCommand()); const createdBrush = Object.values(store.getState().document.brushes)[0]; store.executeCommand( createSetBoxBrushVolumeSettingsCommand({ brushId: createdBrush.id, volume: { mode: "light", light: { colorHex: "#ffd9a6", intensity: 1.75, padding: 0.45, falloff: "smoothstep" } } }) ); expect(store.getState().document.brushes[createdBrush.id].volume).toEqual({ mode: "light", light: { colorHex: "#ffd9a6", intensity: 1.75, padding: 0.45, falloff: "smoothstep" } }); expect(store.undo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].volume).toEqual({ mode: "none" }); expect(store.redo()).toBe(true); expect(store.getState().document.brushes[createdBrush.id].volume).toEqual({ mode: "light", light: { colorHex: "#ffd9a6", intensity: 1.75, padding: 0.45, falloff: "smoothstep" } }); }); });