aboutsummaryrefslogtreecommitdiff
path: root/src/components/ModelControls.test.tsx
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-01-30 11:31:24 +0100
committerRuben Beltran del Rio <jj@r.bdr.sh>2026-01-30 13:05:48 +0100
commit0d4b61993cba37c180ac91e40a0fb362711de8de (patch)
treef41dd68420b51766c1ca6fc1ee2c20dc5df830d1 /src/components/ModelControls.test.tsx
Initial implementation
Diffstat (limited to 'src/components/ModelControls.test.tsx')
-rw-r--r--src/components/ModelControls.test.tsx204
1 files changed, 204 insertions, 0 deletions
diff --git a/src/components/ModelControls.test.tsx b/src/components/ModelControls.test.tsx
new file mode 100644
index 0000000..b38d460
--- /dev/null
+++ b/src/components/ModelControls.test.tsx
@@ -0,0 +1,204 @@
+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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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(<ModelControls />);
+ });
+
+ 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",
+ );
+ });
+});