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

117
tests/themes.test.js Normal file
View File

@@ -0,0 +1,117 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { COLORS } from '../src/renderer.js';
import { ThemeManager } from '../src/themes.js';
import { Scene, Color } from '../tests/__mocks__/three.js';
// Snapshot of original COLORS to restore between tests (shared mutable state)
const ORIGINAL_COLORS = {
wall: { ...COLORS.wall },
floor: { ...COLORS.floor },
ceiling: COLORS.ceiling,
door: COLORS.door,
window: COLORS.window,
windowFrame: COLORS.windowFrame,
grid: COLORS.grid,
selected: COLORS.selected
};
function restoreColors() {
Object.assign(COLORS.wall, ORIGINAL_COLORS.wall);
Object.assign(COLORS.floor, ORIGINAL_COLORS.floor);
COLORS.ceiling = ORIGINAL_COLORS.ceiling;
COLORS.door = ORIGINAL_COLORS.door;
COLORS.window = ORIGINAL_COLORS.window;
COLORS.windowFrame = ORIGINAL_COLORS.windowFrame;
COLORS.grid = ORIGINAL_COLORS.grid;
COLORS.selected = ORIGINAL_COLORS.selected;
}
function makeMockRenderer() {
const scene = new Scene();
// Add mock lights to the scene
scene.add({ isAmbientLight: true, intensity: 0.6, traverse: (fn) => fn({ isAmbientLight: true, intensity: 0.6 }) });
scene.add({ isDirectionalLight: true, intensity: 0.8, traverse: (fn) => fn({ isDirectionalLight: true, intensity: 0.8 }) });
return {
scene,
currentFloor: 0,
_clearFloor: vi.fn(),
showFloor: vi.fn()
};
}
describe('ThemeManager', () => {
let tm, mockRenderer;
beforeEach(() => {
restoreColors();
mockRenderer = makeMockRenderer();
tm = new ThemeManager(mockRenderer);
});
afterEach(() => {
restoreColors();
});
describe('getThemes', () => {
it('returns array of theme descriptors', () => {
const themes = tm.getThemes();
expect(themes.length).toBeGreaterThanOrEqual(4);
for (const theme of themes) {
expect(theme).toHaveProperty('id');
expect(theme).toHaveProperty('name');
expect(theme).toHaveProperty('swatch');
expect(typeof theme.id).toBe('string');
expect(typeof theme.name).toBe('string');
expect(theme.swatch).toMatch(/^#[0-9a-f]{6}$/i);
}
});
it('includes expected theme ids', () => {
const ids = tm.getThemes().map(t => t.id);
expect(ids).toContain('default');
expect(ids).toContain('modern');
expect(ids).toContain('warm');
expect(ids).toContain('dark');
expect(ids).toContain('scandinavian');
});
});
describe('constructor', () => {
it('starts with default theme', () => {
expect(tm.currentTheme).toBe('default');
});
});
describe('applyTheme', () => {
it('updates currentTheme property', () => {
tm.applyTheme('dark');
expect(tm.currentTheme).toBe('dark');
});
it('does nothing for invalid theme id', () => {
tm.applyTheme('nonexistent');
expect(tm.currentTheme).toBe('default');
});
it('mutates the shared COLORS object', () => {
const origExterior = COLORS.wall.exterior;
tm.applyTheme('dark');
expect(COLORS.wall.exterior).toBe(0x3a3a3a);
// Restore for other tests
tm.applyTheme('default');
expect(COLORS.wall.exterior).toBe(origExterior);
});
it('calls _clearFloor and showFloor on renderer', () => {
tm.applyTheme('modern');
expect(mockRenderer._clearFloor).toHaveBeenCalled();
expect(mockRenderer.showFloor).toHaveBeenCalledWith(0);
});
it('updates scene background', () => {
tm.applyTheme('dark');
expect(mockRenderer.scene.background._hex).toBe(0x222222);
});
});
});