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.
118 lines
3.5 KiB
JavaScript
118 lines
3.5 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|