/** * Task D4 – MTEXT-Tool: Mehrzeiliger Text mit/ohne Rahmen. * * Klick platziert mehrzeiligen Text (Inhalt aus Options, Zeilen- * umbrueche), fontSize-Option, optionale Rahmen-Option erzeugt ein * rect-Element in derselben Transaktion (1 Undo-Schritt). */ import { describe, it, expect } from 'vitest'; import { mtextTool } from '../src/plugins/builtin/core-annotate'; import { CADDocument } from '../src/kernel/document/CADDocument'; import { createInMemoryDoc } from './helpers/inMemoryDoc'; import type { CADElement, Pt } from '../src/types/cad.types'; function mkCtx(options: Record = {}) { const { ydoc } = createInMemoryDoc(); const doc = new CADDocument(ydoc); const statuses: string[] = []; const ctx: any = { doc, options, setStatus: (m: string) => statuses.push(m), setPreview: (_el: CADElement | null) => {}, }; return { ctx, doc, statuses }; } function pe(x: number, y: number): { world: Pt } { return { world: { x, y } } as never; } describe('D4: mtextTool', () => { it('platziert mehrzeiligen Text mit Zeilenumbruechen aus den Options', () => { const { ctx, doc } = mkCtx({ text: 'Zeile 1\nZeile 2\nZeile 3', fontSize: 14 }); mtextTool.handlers.down!(pe(100, 50), ctx); const els = doc.getAllElements(); expect(els).toHaveLength(1); const el = els[0]; expect(el.type).toBe('text'); expect(el.x).toBe(100); expect(el.y).toBe(50); expect(el.properties.text).toBe('Zeile 1\nZeile 2\nZeile 3'); expect(el.properties.fontSize).toBe(14); expect(el.properties.mtext).toBe(true); }); it('Rahmen-Option erzeugt zusaetzliches rect in derselben Transaktion (1 Undo)', () => { const { ctx, doc } = mkCtx({ text: 'Mit Rahmen', frame: true }); mtextTool.handlers.down!(pe(0, 0), ctx); const els = doc.getAllElements(); expect(els).toHaveLength(2); const texts = els.filter((e) => e.type === 'text'); const rects = els.filter((e) => e.type === 'rect'); expect(texts).toHaveLength(1); expect(rects).toHaveLength(1); expect(rects[0].x).toBeCloseTo(0, 5); expect(rects[0].y).toBeCloseTo(0, 5); doc.undo(); expect(doc.getAllElements()).toHaveLength(0); }); it('ohne frame-Option: nur Text, kein rect', () => { const { ctx, doc } = mkCtx({ text: 'Ohne Rahmen' }); mtextTool.handlers.down!(pe(0, 0), ctx); expect(doc.getAllElements().filter((e) => e.type === 'rect')).toHaveLength(0); }); it('Standard-Text wenn Options leer: MTEXT-Platzhalter leer (Editor bearbeitet spaeter)', () => { const { ctx, doc } = mkCtx({}); mtextTool.handlers.down!(pe(10, 10), ctx); const el = doc.getAllElements()[0]; expect(el.properties.text).toBe(''); expect(el.properties.fontSize).toBe(12); // Default }); it('OptionsSchema: text/number/checkbox-Felder', () => { const schema = mtextTool.optionsSchema ?? []; const keys = schema.map((f) => f.key); expect(keys).toContain('text'); expect(keys).toContain('fontSize'); expect(keys).toContain('frame'); const textField = schema.find((f) => f.key === 'text'); expect(textField!.type).toBe('text'); const frameField = schema.find((f) => f.key === 'frame'); expect(frameField!.type).toBe('checkbox'); }); it('Rahmenhoehe waechst mit Zeilenzahl', () => { const { ctx: ctx1, doc: doc1 } = mkCtx({ text: 'eine Zeile', frame: true }); mtextTool.handlers.down!(pe(0, 0), ctx1); const { ctx: ctx3, doc: doc3 } = mkCtx({ text: 'eins\nzwei\ndrei', frame: true }); mtextTool.handlers.down!(pe(0, 0), ctx3); const rect1 = doc1.getAllElements().find((e) => e.type === 'rect')!; const rect3 = doc3.getAllElements().find((e) => e.type === 'rect')!; expect(rect3.height).toBeGreaterThan(rect1.height); }); it('Manifest: id=mtext, tags basic', () => { expect(mtextTool.manifest.id).toBe('mtext'); expect((mtextTool.manifest.tags ?? [])).toContain('basic'); }); });