feat: complete house plan maker application
Full-featured house/apartment floor plan editor with: - Turborepo monorepo (React/Vite client, Fastify/Prisma server, shared Zod schemas) - 2D room editor with walls, doors, windows, furniture, electrical elements - 3D room preview with Three.js (auto-hide nearest walls, bird's eye default) - Wall projection views with interactive drag (elevation, position) - Apartment floor plan view with room positioning - Copy/paste, alignment tools, measurement tool, annotations - Item-attached annotations with leader lines (visible on projections) - Door open direction (LEFT/RIGHT/INWARD/OUTWARD) with swing arc - Floor type textures (wood, tile, concrete, laminate, herringbone) - Wall color picker for 3D view - Furniture: bed, desk, wardrobe, sofa, table, chair, shelf, nightstand, dresser, bookcase, TV (with stand toggle), AC unit - Furniture elevation support (wall-mounted items) - Auto-save with dirty state tracking, batch save API - Rotation-aware collision detection (SAT/OBB) with 3D elevation check - Rotation-aware hit testing - i18n (English/Russian) with locale-aware number formatting - Dark mode with system preference detection - Undo/redo, keyboard shortcuts, scale bar - PDF/PNG/JSON export and JSON import - Focus trap modal, toast notifications, tooltips - Responsive layout with overlay palettes
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createApartmentSchema, updateApartmentSchema } from '../schemas/apartment.schema.js';
|
||||
import { createRoomSchema, updateRoomSchema } from '../schemas/room.schema.js';
|
||||
|
||||
describe('createApartmentSchema', () => {
|
||||
it('validates a valid apartment', () => {
|
||||
const result = createApartmentSchema.parse({
|
||||
name: 'Test Apartment',
|
||||
address: '123 Main St',
|
||||
totalArea: 75.5,
|
||||
});
|
||||
expect(result.name).toBe('Test Apartment');
|
||||
expect(result.address).toBe('123 Main St');
|
||||
expect(result.totalArea).toBe(75.5);
|
||||
});
|
||||
|
||||
it('requires name', () => {
|
||||
expect(() => createApartmentSchema.parse({})).toThrow();
|
||||
});
|
||||
|
||||
it('rejects empty name', () => {
|
||||
expect(() => createApartmentSchema.parse({ name: '' })).toThrow();
|
||||
});
|
||||
|
||||
it('allows optional address and totalArea', () => {
|
||||
const result = createApartmentSchema.parse({ name: 'Test' });
|
||||
expect(result.name).toBe('Test');
|
||||
});
|
||||
|
||||
it('rejects negative totalArea', () => {
|
||||
expect(() => createApartmentSchema.parse({ name: 'Test', totalArea: -10 })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateApartmentSchema', () => {
|
||||
it('allows partial update', () => {
|
||||
const result = updateApartmentSchema.parse({ name: 'Updated' });
|
||||
expect(result.name).toBe('Updated');
|
||||
});
|
||||
|
||||
it('allows empty object', () => {
|
||||
const result = updateApartmentSchema.parse({});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createRoomSchema', () => {
|
||||
it('validates a valid room', () => {
|
||||
const result = createRoomSchema.parse({
|
||||
name: 'Bedroom',
|
||||
shape: [{ x: 0, y: 0 }, { x: 4, y: 0 }, { x: 4, y: 3 }, { x: 0, y: 3 }],
|
||||
width: 4,
|
||||
height: 3,
|
||||
wallHeight: 2.7,
|
||||
});
|
||||
expect(result.name).toBe('Bedroom');
|
||||
expect(result.shape).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('requires name', () => {
|
||||
expect(() => createRoomSchema.parse({})).toThrow();
|
||||
});
|
||||
|
||||
it('rejects empty name', () => {
|
||||
expect(() => createRoomSchema.parse({ name: '' })).toThrow();
|
||||
});
|
||||
|
||||
it('allows optional fields', () => {
|
||||
const result = createRoomSchema.parse({ name: 'Room' });
|
||||
expect(result.name).toBe('Room');
|
||||
});
|
||||
|
||||
it('rejects negative width', () => {
|
||||
expect(() => createRoomSchema.parse({ name: 'Room', width: -1 })).toThrow();
|
||||
});
|
||||
|
||||
it('rejects negative wallHeight', () => {
|
||||
expect(() => createRoomSchema.parse({ name: 'Room', wallHeight: -1 })).toThrow();
|
||||
});
|
||||
|
||||
it('allows zero plinthHeight', () => {
|
||||
const result = createRoomSchema.parse({ name: 'Room', plinthHeight: 0 });
|
||||
expect(result.plinthHeight).toBe(0);
|
||||
});
|
||||
|
||||
it('validates order as non-negative integer', () => {
|
||||
const result = createRoomSchema.parse({ name: 'Room', order: 0 });
|
||||
expect(result.order).toBe(0);
|
||||
expect(() => createRoomSchema.parse({ name: 'Room', order: -1 })).toThrow();
|
||||
expect(() => createRoomSchema.parse({ name: 'Room', order: 1.5 })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRoomSchema', () => {
|
||||
it('allows partial update', () => {
|
||||
const result = updateRoomSchema.parse({ name: 'Updated Room' });
|
||||
expect(result.name).toBe('Updated Room');
|
||||
});
|
||||
|
||||
it('allows empty object', () => {
|
||||
const result = updateRoomSchema.parse({});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user