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",
);
});
});