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:
199
tests/__mocks__/three.js
Normal file
199
tests/__mocks__/three.js
Normal 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;
|
||||
Reference in New Issue
Block a user