import { describe, it, beforeEach, afterEach } from "node:test"; import assert from "node:assert"; import "../test-setup.ts"; import { createRoot, type Root } from "react-dom/client"; import { act } from "@testing-library/react"; import { ModelControls } from "./ModelControls.tsx"; import { useStore } from "../store/store.ts"; describe("ModelControls", () => { let container: HTMLDivElement; let root: Root; beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); root = createRoot(container); useStore.setState({ models: [], selectedModelId: null, cameraView: "perspective", backdropUrl: null, availableModels: [ { id: "servbot", name: "Servbot", url: "/models/servbot.gltf" }, ], }); }); afterEach(() => { act(() => { root.unmount(); }); container.remove(); }); it("should not render when no model is selected", async () => { await act(async () => { root.render(); }); const controls = container.querySelector(".model-controls"); assert.ok( !controls, "Should not render controls when no model is selected", ); }); it("should render when a model is selected", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const controls = container.querySelector(".model-controls"); assert.ok(controls, "Should render controls when model is selected"); }); it("should have position controls", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const positionXInput = container.querySelector('input[name="position-x"]'); const positionYInput = container.querySelector('input[name="position-y"]'); const positionZInput = container.querySelector('input[name="position-z"]'); assert.ok(positionXInput, "Should have position X input"); assert.ok(positionYInput, "Should have position Y input"); assert.ok(positionZInput, "Should have position Z input"); }); it("should have rotation controls", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const rotationXInput = container.querySelector('input[name="rotation-x"]'); const rotationYInput = container.querySelector('input[name="rotation-y"]'); const rotationZInput = container.querySelector('input[name="rotation-z"]'); assert.ok(rotationXInput, "Should have rotation X input"); assert.ok(rotationYInput, "Should have rotation Y input"); assert.ok(rotationZInput, "Should have rotation Z input"); }); it("should update position when input changes", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const positionXInput = container.querySelector( 'input[name="position-x"]', ) as HTMLInputElement; await act(async () => { const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, "value", )!.set!; nativeInputValueSetter.call(positionXInput, "5"); positionXInput.dispatchEvent(new Event("input", { bubbles: true })); }); const model = useStore.getState().models[0]; assert.strictEqual(model.position.x, 5); }); it("should have hue shift controls", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const hueRedInput = container.querySelector('input[name="hue-red"]'); const hueGreenInput = container.querySelector('input[name="hue-green"]'); const hueBlueInput = container.querySelector('input[name="hue-blue"]'); assert.ok(hueRedInput, "Should have red hue shift input"); assert.ok(hueGreenInput, "Should have green hue shift input"); assert.ok(hueBlueInput, "Should have blue hue shift input"); }); it("should update hue shift when slider changes", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const hueRedInput = container.querySelector( 'input[name="hue-red"]', ) as HTMLInputElement; await act(async () => { const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, "value", )!.set!; nativeInputValueSetter.call(hueRedInput, "0.5"); hueRedInput.dispatchEvent(new Event("input", { bubbles: true })); }); const model = useStore.getState().models[0]; assert.strictEqual(model.hueShift.red, 0.5); }); it("should have a delete button", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const deleteButton = container.querySelector("button.delete-model"); assert.ok(deleteButton, "Should have delete button"); }); it("should remove model when delete button is clicked", async () => { useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 }); const modelId = useStore.getState().models[0].id; useStore.getState().selectModel(modelId); await act(async () => { root.render(); }); const deleteButton = container.querySelector( "button.delete-model", ) as HTMLButtonElement; await act(async () => { deleteButton.click(); }); const models = useStore.getState().models; assert.strictEqual(models.length, 0, "Model should be removed"); assert.strictEqual( useStore.getState().selectedModelId, null, "Selection should be cleared", ); }); });