/** * Task D12 - BlockAttribute: attrDefs + attrValues + Rendering. * * BlockDefinition.attrDefs[]: definiert Attribut-Slots (tag/prompt/ * default). Instanz (block_instance) traegt attrValues. Der Render- * Adapter (elementToPrimitives) zeichnet die Attr-Werte als Text- * Primitive an der Instanz-Position. */ import { describe, it, expect } from 'vitest'; import { CADDocument } from '../src/kernel/document/CADDocument'; import { createInMemoryDoc } from './helpers/inMemoryDoc'; import { elementToPrimitives } from '../src/render/pixi/elementDrawers'; import type { CADElement, BlockDefinition } from '../src/types/cad.types'; const BLOCK: BlockDefinition = { id: 'blk-1', name: 'Titelblock', description: '', category: 'frame', elements: [], attrDefs: [ { tag: 'PROJEKT', prompt: 'Projektname', defaultValue: 'Unbenannt' }, { tag: 'AUTOR', prompt: 'Bearbeiter', defaultValue: 'WebCAD' }, ], }; function instance(attrValues: Record): CADElement { return { id: 'inst-1', type: 'block_instance', layerId: 'layer-0', x: 100, y: 100, width: 0, height: 0, properties: { blockId: 'blk-1', scale: 1, attrValues }, } as unknown as CADElement; } // ─── Typ-Ebene: attrDefs auf BlockDefinition ──────────────── describe('D12: BlockDefinition.attrDefs', () => { it('attrDefs-Feld existiert mit tag/prompt/defaultValue', () => { expect(BLOCK.attrDefs).toBeDefined(); expect(BLOCK.attrDefs!.length).toBe(2); expect(BLOCK.attrDefs![0].tag).toBe('PROJEKT'); expect(BLOCK.attrDefs![0].prompt).toBe('Projektname'); expect(BLOCK.attrDefs![0].defaultValue).toBe('Unbenannt'); }); it('CADDocument speichert Block mit attrDefs (getBlock liefert sie zurueck)', () => { const { ydoc } = createInMemoryDoc(); const doc = new CADDocument(ydoc); doc.addBlock(BLOCK); const loaded = doc.getBlock('blk-1'); expect(loaded!.attrDefs).toHaveLength(2); expect(loaded!.attrDefs![1].tag).toBe('AUTOR'); }); }); // ─── Rendering: elementToPrimitives mit attrDefs ──────────── describe('D12: RenderAdapter zeichnet Attr-Texte', () => { it('Instanz mit attrValues erzeugt text-Primitives pro Attribut', () => { const el = instance({ PROJEKT: 'Halle A', AUTOR: 'L. Admin' }); const prims = elementToPrimitives(el, BLOCK.attrDefs); const texts = prims.filter((p) => p.kind === 'text'); expect(texts).toHaveLength(2); const proj = texts.find((t) => (t as { text: string }).text.includes('PROJEKT')); expect(proj).toBeDefined(); expect((proj as { text: string }).text).toContain('Halle A'); const autor = texts.find((t) => (t as { text: string }).text.includes('AUTOR')); expect((autor as { text: string }).text).toContain('L. Admin'); }); it('Fehlender Wert faellt auf attrDefs.defaultValue zurueck', () => { const el = instance({}); const prims = elementToPrimitives(el, BLOCK.attrDefs); const texts = prims.filter((p) => p.kind === 'text'); const proj = texts.find((t) => (t as { text: string }).text.includes('PROJEKT')); expect((proj as { text: string }).text).toContain('Unbenannt'); }); it('Ohne attrDefs-Parameter: kein Attr-Text-Rendering (abwaertskompatibel)', () => { const el = instance({ PROJEKT: 'Halle A' }); const prims = elementToPrimitives(el); expect(prims.filter((p) => p.kind === 'text')).toHaveLength(0); }); it('Instanz ohne attrValues mit attrDefs: defaults werden gezeichnet', () => { const el: CADElement = { id: 'inst-2', type: 'block_instance', layerId: 'layer-0', x: 0, y: 0, width: 0, height: 0, properties: { blockId: 'blk-1', scale: 1 }, } as unknown as CADElement; const prims = elementToPrimitives(el, BLOCK.attrDefs); const texts = prims.filter((p) => p.kind === 'text'); expect(texts).toHaveLength(2); }); it('Text-Positionen gestaffelt unterhalb der Instanz-Position', () => { const el = instance({ PROJEKT: 'A', AUTOR: 'B' }); const prims = elementToPrimitives(el, BLOCK.attrDefs); const texts = prims.filter((p) => p.kind === 'text') as Array<{ kind: 'text'; at: { x: number; y: number }; text: string }>; expect(texts[0].at.x).toBe(100); expect(texts[0].at.y).toBeCloseTo(100 - 8, 0); expect(texts[1].at.y).toBeLessThan(texts[0].at.y); }); });