Add test suite with 132 unit tests across all modules

Covers DesignState (40 tests), HouseRenderer (19), InteractionManager (24),
ThemeManager (8), ExportManager (11), and CatalogPanel (30). Uses vitest
with THREE.js mocks for browser-free testing.
This commit is contained in:
m
2026-02-07 16:34:36 +01:00
parent bc94d41f2b
commit 8ac5b3f1f9
13 changed files with 2058 additions and 0 deletions

199
tests/__mocks__/three.js Normal file
View File

@@ -0,0 +1,199 @@
// Minimal THREE.js mock for unit testing
export class Vector2 {
constructor(x = 0, y = 0) { this.x = x; this.y = y; }
set(x, y) { this.x = x; this.y = y; return this; }
copy(v) { this.x = v.x; this.y = v.y; return this; }
clone() { return new Vector2(this.x, this.y); }
}
export class Vector3 {
constructor(x = 0, y = 0, z = 0) { this.x = x; this.y = y; this.z = z; }
set(x, y, z) { this.x = x; this.y = y; this.z = z; return this; }
copy(v) { this.x = v.x; this.y = v.y; this.z = v.z; return this; }
clone() { return new Vector3(this.x, this.y, this.z); }
sub(v) { this.x -= v.x; this.y -= v.y; this.z -= v.z; return this; }
add(v) { this.x += v.x; this.y += v.y; this.z += v.z; return this; }
multiplyScalar(s) { this.x *= s; this.y *= s; this.z *= s; return this; }
equals(v) { return this.x === v.x && this.y === v.y && this.z === v.z; }
}
export class Euler {
constructor(x = 0, y = 0, z = 0) { this.x = x; this.y = y; this.z = z; }
copy(e) { this.x = e.x; this.y = e.y; this.z = e.z; return this; }
}
export class Color {
constructor(c) { this._hex = typeof c === 'number' ? c : 0; }
setHex(h) { this._hex = h; return this; }
clone() { return new Color(this._hex); }
}
export class Object3D {
constructor() {
this.children = [];
this.parent = null;
this.position = new Vector3();
this.rotation = new Euler();
this.scale = new Vector3(1, 1, 1);
this.userData = {};
}
add(child) { this.children.push(child); child.parent = this; }
remove(child) {
const i = this.children.indexOf(child);
if (i >= 0) this.children.splice(i, 1);
child.parent = null;
}
traverse(fn) {
fn(this);
for (const child of this.children) child.traverse(fn);
}
}
export class Group extends Object3D {}
export class Scene extends Object3D {
constructor() {
super();
this.background = new Color(0);
}
}
export class Mesh extends Object3D {
constructor(geometry, material) {
super();
this.geometry = geometry;
this.material = material;
this.isMesh = true;
this.castShadow = false;
this.receiveShadow = false;
}
}
export class Sprite extends Object3D {
constructor(material) {
super();
this.material = material;
}
}
export class LineSegments extends Object3D {
constructor(geometry, material) {
super();
this.geometry = geometry;
this.material = material;
}
}
export class BoxGeometry {
constructor(w, h, d) { this.parameters = { width: w, height: h, depth: d }; }
dispose() {}
}
export class PlaneGeometry {
constructor(w, h) { this.parameters = { width: w, height: h }; }
dispose() {}
}
export class CylinderGeometry {
constructor(rT, rB, h, s) { this.parameters = { radiusTop: rT, radiusBottom: rB, height: h, radialSegments: s }; }
dispose() {}
}
export class EdgesGeometry {
constructor(geo) { this._source = geo; }
dispose() {}
}
export class MeshStandardMaterial {
constructor(opts = {}) {
Object.assign(this, opts);
this.emissive = new Color(0);
}
clone() { const m = new MeshStandardMaterial(); Object.assign(m, this); m.emissive = this.emissive.clone(); return m; }
dispose() {}
}
export class MeshBasicMaterial {
constructor(opts = {}) { Object.assign(this, opts); }
dispose() {}
}
export class SpriteMaterial {
constructor(opts = {}) { Object.assign(this, opts); }
dispose() {}
}
export class LineBasicMaterial {
constructor(opts = {}) { Object.assign(this, opts); }
dispose() {}
}
export class CanvasTexture {
constructor() {}
dispose() {}
}
export class Plane {
constructor(normal, constant) { this.normal = normal; this.constant = constant; }
}
export class Raycaster {
constructor() { this.ray = { intersectPlane: () => new Vector3() }; }
setFromCamera() {}
intersectObject() { return []; }
intersectObjects() { return []; }
}
export class PerspectiveCamera extends Object3D {
constructor(fov, aspect, near, far) {
super();
this.fov = fov;
this.aspect = aspect;
this.near = near;
this.far = far;
}
lookAt() {}
updateProjectionMatrix() {}
}
export class AmbientLight extends Object3D {
constructor(color, intensity) {
super();
this.color = new Color(color);
this.intensity = intensity;
this.isAmbientLight = true;
}
}
export class DirectionalLight extends Object3D {
constructor(color, intensity) {
super();
this.color = new Color(color);
this.intensity = intensity;
this.isDirectionalLight = true;
this.castShadow = false;
this.shadow = {
mapSize: { width: 0, height: 0 },
camera: { left: 0, right: 0, top: 0, bottom: 0, near: 0, far: 0 }
};
}
}
export class GridHelper extends Object3D {
constructor() { super(); }
}
export class WebGLRenderer {
constructor() {
this.domElement = { addEventListener: () => {}, removeEventListener: () => {}, getBoundingClientRect: () => ({ left: 0, top: 0, width: 800, height: 600 }), toDataURL: () => 'data:image/png;base64,fake' };
this.shadowMap = { enabled: false };
}
setSize() {}
setPixelRatio() {}
getPixelRatio() { return 1; }
getSize(v) { v.set(800, 600); }
render() {}
}
export const DoubleSide = 2;