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.
200 lines
5.1 KiB
JavaScript
200 lines
5.1 KiB
JavaScript
// 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;
|