Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d51111fc2c | |||
| 9176d7c9a4 |
+4
-3
@@ -618,7 +618,7 @@ Kurz-Tasks (je gleiche Struktur: implementieren im passenden builtin-Paket + Wor
|
||||
- [ ] E4 Templates: kino, bankett-round, stehtisch, podest, parlamentarisch, u-form (JSON in plugin)
|
||||
- [ ] E5 Export Sitzliste CSV/XLSX (xlsx lib oder csv-manuell)
|
||||
- [ ] E6 Gäste (optional, 2.1): Backend-Tabelle guests + Zuordnung + CSV-Import
|
||||
- [ ] E7 Event-Elemente polish: stage(height attr), curtain, spotlight(angle/color), barrier chain
|
||||
- [x] E7 Event-Elemente polish: stage(height attr), curtain, spotlight(angle/color), barrier chain *(2026-08-29: 3 neue ElementTypen curtain/spotlight/barrier mit Pixi-Drawer-Cases + Platzier-Tools (Options: stageHeight/angle+color/length), createStage properties.height; event-tools v2.2.0 mit 13 Tools; Log E7)*
|
||||
|
||||
## Phase F — Library & Import/Export
|
||||
- [x] F1 Backend library_folders/library_blocks + Migrationsfile + Tests (CRUD, scopes) *(umgesetzt als global_block_folders/global_blocks mit SqliteAdapter-Migration + CRUD-Tests; .wcadlib-Paket zusätzlich im Log F1)*
|
||||
@@ -667,6 +667,7 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of
|
||||
|
||||
| Datum | Task | Zusammenfassung | Tests | Commit | Notizen |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-08-29 | E7 | Event-Elemente-Polish: (1) ElementType um curtain/spotlight/barrier erweitert (cad.types.ts additiv); (2) Pixi-Drawer-Cases: curtain = wellige Polyline aus properties.points (24 Punkte, Sinus gedämpft, close=false; Fallback Linie), spotlight = Kegel-Polygon aus points (close=true, stroke=properties.color), barrier = Kreis-Paare aus properties.circles (12 Kreise, Fallback 5 entlang BBox); (3) Platzier-Tools via Klick: curtainTool (stageHeight-Option, Standard 120cm, Breite 400), spotlightTool (angle+color-Optionen, Länge 300, Öffnung ±20°, Grad-Winkel direkt — Konvention konsistent zu F6), barrierTool (length-Option, Standard 200, 6 Kettenglieder); (4) createStage um properties.height (Bühnenhöhe in cm) erweitert; event-tools v2.2.0, tools-Array jetzt 13 Einträge; Test-Helper fireDown auf etabliertes Pilot-Muster umgestellt (echtes CADDocument aus createInMemoryDoc statt Broken-Mock ohne transact — 7 Tests rot wegen Mock, nach Muster-Fix grün) | Frontend: 551/551 Tests (+8 eventPolish.test.ts), tsc 0, build OK | 9176d7c | **E7 COMPLETE — PHASE E VOLLSTÄNDIG**; nächster Schritt: Phase G (Layouts & Plot) ab G1 |
|
||||
| 2026-08-29 | D-VERIF | Phase-D-Checklisten-Diskrepanz geklärt: D5 (stretchTool), D6 (breakTool+joinTool), D7 (chamferTool/lengthenTool/explodeTool/alignTool), D8 (polylineEditTool) gegen Code verifiziert und abgehakt — alle im core-modify tools-Array (22 Tools) registriert, Log-Einträge D-fin/D-ext/D-ext3 dokumentieren Implementierung mit grünen Tests; D10 angepasst: array-rect/polar erledigt, array-path + Dialoge bleiben offen; tatsächlich noch offen: D1 (Ellipse-Tool), D2 (Spline-Tool), D3 (Point/XLine/Ray), D4 (MTEXT-Tool), D9 (GripEditing), D10-Rest (array-path), D11 (Snaps), D12 (BlockAttribute) | — (reine Verifikation/Doku, kein Code geändert) | — | Kein Commit nötig — ROADMAP-Doku |
|
||||
| 2026-08-29 | F8 | SVG-Import als Block: (1) parsePathData NEU — Tokenizer für d-Attribut mit allen Befehlen M/L/H/V/C/S/Q/T/A/Z (absolut+relativ), implizite Befehlswiederholung, M→L-Fortsetzung; kubische/quadratische Bézier geflattet (16/12 Segmente), Arc über Endpoint→Center-Parametrisierung (SVG Spec F.6.5, 32 Samples, Endpunkt exakt statt Float-Rundung), Z→geschlossen ohne duplizierten Endpunkt, Mehrfach-Subpaths; (2) Konventions-Fixes: rect x/y jetzt Zentrum (App-Konvention), circle el.x/el.y direkt Zentrum statt cx-r (Renderer lesen es so — gleiche Bugklasse wie DXF-Parser vor F6), ellipse jetzt echtes 64-Punkt-geschlossenes Polygon statt verstümmeltem Kreis max(rx,ry); (3) importSVGAsBlockPayload NEU: echte Geometrie als JSON-Array + Original-SVG als Thumbnail, null-Fallback auf {type:svg}-Raw wenn keine Geometrie; BlockLibraryTree SVG-Import (Toolbar + Folder-Drop) nutzt Geometrie-Payload bevorzugt | Frontend: 543/543 Tests (+11 svgImportF8.test.ts), tsc 0, build OK | 09948bc | **F8 COMPLETE — PHASE F VOLLSTÄNDIG ABGESCHLOSSEN** (Library & Import/Export: F1–F8 alle grün); nächster Schritt: Phase-D-Checklisten-Diskrepanz klären, dann E7 |
|
||||
| 2026-08-29 | F7 | .wcadlib als echtes ZIP-Paket: (1) Backend GET /api/global-blocks/export-zip — JSZip generiert manifest.json (format wcadlib-zip v1, exported_at, counts), blocks.json (Ordner + Blöcke mit thumbnail-Referenz) und thumbs/<blockId>.svg je SVG-Thumbnail; content-type application/zip + content-disposition attachment; (2) POST /api/global-blocks/import-zip — JSZip.loadAsync, Validierung (kein ZIP → 400, blocks.json fehlt → 400, invalides JSON → 400, manifest.format≠wcadlib-zip → 400), Ordner-Import mit Eltern-zuerst-Sortierung + ID-Mapping, Thumbnail aus thumbs/ referenzgelesen, 201 mit imported_blocks/imported_folders; (3) Fastify addContentTypeParser application/zip + octet-stream + x-zip-compressed als Buffer (Server-Global); (4) Frontend exportLibraryZip/importLibraryZip in api.ts (Blob-Download, Upload mit content-type application/zip), zwei neue Toolbar-Buttons (ZIP-Box-Icons) + hidden file-input in BlockLibraryTree; F1-JSON-Variante bleibt parallel bestehen | BE: 272/272 Tests (+5 globalBlocksZip.test.ts), BE tsc 0; FE: 532/532, FE tsc 0, build OK | d311820 | F7 COMPLETE — ZIP-Roundtrip mit echten write→read-Roundtrip-Tests (Export→Import→DB-Verifikation) |
|
||||
@@ -731,5 +732,5 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of
|
||||
- `frontend/src/utils/__tests__/format.test.ts` liegt in src statt tests/ — vereinheitlichen (kleiner Task in I2)
|
||||
|
||||
---
|
||||
*Nächster offener Schritt: **Phase-D-Diskrepanz klären** (ungehakte D-Items gegen Code verifizieren: D5/D6/D7/D8/D10 abhaken falls implementiert; D1/D2/D3/D4/D9/D11/D12 ggf. nachziehen), dann **E7** (Event-Elemente-Polish). Danach: Phase G (Layouts/PDF), H (Modi/i18n), I (Release).*
|
||||
*Status 2026-08-29: Phasen B+A+C+D+E+F komplett (F1–F8: Library, DXF-Roundtrip, ZIP-Paket, SVG-Geometrie-Import), BUG-1-Kollisionsklasse geschlossen. FE 543/543 Tests, BE 272/272, tsc/Build grün. Alle Commits auf Forgejo main.*
|
||||
*Nächster offener Schritt: **G1** (Layout-Datenmodell: Yjs Map layoutId→{name, viewport{center,scale}, frameBlockRef}). Danach G2 LayoutBar+Viewport-Widget, G3 Titelblock, G4 PDF-Export, G5 Print-Stylesheet. Restlich offen: D1–D4/D9/D10-path/D11/D12 (CAD-Zeichen-Tools), Phase H, Phase I.*
|
||||
*Status 2026-08-29: Phasen B+A+C+D+E+F komplett (E7 Event-Polish: curtain/spotlight/barrier; F1–F8 Library/Import/Export), BUG-1-Kollisionsklasse geschlossen. FE 551/551 Tests, BE 272/272, tsc/Build grün. Alle Commits auf Forgejo main.*
|
||||
|
||||
@@ -280,15 +280,156 @@ export const arcRowTool: ToolExtensionV2 = {
|
||||
};
|
||||
|
||||
/** V2-Plugin: Event-Domain (Tasks A4.9 + E1). */
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Task E7: Event-Elemente-Polish — curtain, spotlight, barrier
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/** Vorhang: wellige Polyline (Standardbreite 400) über der Bühne. */
|
||||
export const curtainTool: ToolExtensionV2 = {
|
||||
manifest: {
|
||||
id: 'curtain',
|
||||
label: 'Vorhang',
|
||||
icon: '╭',
|
||||
ribbonTab: 'insert',
|
||||
tags: ['basic'],
|
||||
description: 'Vorhang entlang der Bühnen-Oberkante platzieren',
|
||||
},
|
||||
optionsSchema: [
|
||||
{ key: 'stageHeight', label: 'Höhe (cm)', type: 'number' },
|
||||
],
|
||||
handlers: {
|
||||
down(e, ctx) {
|
||||
const c = ctx as FullToolContext;
|
||||
if (!c.doc) return;
|
||||
const { layerId } = optsOf(ctx);
|
||||
const stageHeight = (ctx.options.stageHeight as number | undefined) ?? 120;
|
||||
const width = 400;
|
||||
// Wellenform: 24 Punkte, Sinus gedämpft an den Enden
|
||||
const pts: Pt[] = [];
|
||||
const SEG = 24;
|
||||
for (let i = 0; i <= SEG; i++) {
|
||||
const t = i / SEG;
|
||||
const x = e.world.x - width / 2 + width * t;
|
||||
const y = e.world.y + Math.sin(t * Math.PI) * (stageHeight / 4);
|
||||
pts.push({ x, y });
|
||||
}
|
||||
const el: CADElement = {
|
||||
id: `curtain-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
type: 'curtain',
|
||||
layerId,
|
||||
x: e.world.x,
|
||||
y: e.world.y,
|
||||
width,
|
||||
height: stageHeight,
|
||||
properties: { points: pts, stageHeight, stroke: '#7a4a8f', strokeWidth: 2 },
|
||||
};
|
||||
c.doc.transact(() => { c.doc!.addElement(el); });
|
||||
ctx.setStatus(`Vorhang platziert (${width} breit, ${stageHeight} cm hoch)`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Scheinwerfer: Lichtkegel in angle-Richtung mit Farbe. */
|
||||
export const spotlightTool: ToolExtensionV2 = {
|
||||
manifest: {
|
||||
id: 'spotlight',
|
||||
label: 'Scheinwerfer',
|
||||
icon: '▶',
|
||||
ribbonTab: 'insert',
|
||||
tags: ['basic'],
|
||||
description: 'Scheinwerfer-Kegel platzieren (Winkel + Farbe aus Optionen)',
|
||||
},
|
||||
optionsSchema: [
|
||||
{ key: 'angle', label: 'Winkel (°)', type: 'number' },
|
||||
{ key: 'color', label: 'Lichtfarbe', type: 'color' },
|
||||
],
|
||||
handlers: {
|
||||
down(e, ctx) {
|
||||
const c = ctx as FullToolContext;
|
||||
if (!c.doc) return;
|
||||
const { layerId } = optsOf(ctx);
|
||||
const angle = (ctx.options.angle as number | undefined) ?? 0;
|
||||
const color = (ctx.options.color as string | undefined) ?? '#ffcc00';
|
||||
const length = 300;
|
||||
const halfAngle = 20;
|
||||
const rad = (angle * Math.PI) / 180;
|
||||
const radHalf = (halfAngle * Math.PI) / 180;
|
||||
const p1 = { x: e.world.x + length * Math.cos(rad - radHalf), y: e.world.y + length * Math.sin(rad - radHalf) };
|
||||
const p2 = { x: e.world.x + length * Math.cos(rad + radHalf), y: e.world.y + length * Math.sin(rad + radHalf) };
|
||||
const pts: Pt[] = [
|
||||
{ x: e.world.x, y: e.world.y },
|
||||
p1,
|
||||
p2,
|
||||
{ x: e.world.x, y: e.world.y },
|
||||
];
|
||||
const el: CADElement = {
|
||||
id: `spotlight-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
type: 'spotlight',
|
||||
layerId,
|
||||
x: e.world.x,
|
||||
y: e.world.y,
|
||||
width: length,
|
||||
height: length,
|
||||
properties: { points: pts, angle, color },
|
||||
};
|
||||
c.doc.transact(() => { c.doc!.addElement(el); });
|
||||
ctx.setStatus(`Scheinwerfer platziert (${angle}°, ${color})`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Absperrung: Kette aus Kreis-Paaren entlang Standardlänge 200. */
|
||||
export const barrierTool: ToolExtensionV2 = {
|
||||
manifest: {
|
||||
id: 'barrier',
|
||||
label: 'Absperrung',
|
||||
icon: '⛴',
|
||||
ribbonTab: 'insert',
|
||||
tags: ['basic'],
|
||||
description: 'Absperr-Kette (Kreis-Paare) platzieren',
|
||||
},
|
||||
optionsSchema: [
|
||||
{ key: 'length', label: 'Länge', type: 'number' },
|
||||
],
|
||||
handlers: {
|
||||
down(e, ctx) {
|
||||
const c = ctx as FullToolContext;
|
||||
if (!c.doc) return;
|
||||
const { layerId } = optsOf(ctx);
|
||||
const length = (ctx.options.length as number | undefined) ?? 200;
|
||||
const circles: Array<{ x: number; y: number; r: number }> = [];
|
||||
const pairs = 6;
|
||||
for (let i = 0; i < pairs; i++) {
|
||||
const t = (i + 0.5) / pairs;
|
||||
const cx = e.world.x - length / 2 + length * t;
|
||||
circles.push({ x: cx, y: e.world.y - 10, r: 8 });
|
||||
circles.push({ x: cx, y: e.world.y + 10, r: 8 });
|
||||
}
|
||||
const el: CADElement = {
|
||||
id: `barrier-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
type: 'barrier',
|
||||
layerId,
|
||||
x: e.world.x,
|
||||
y: e.world.y,
|
||||
width: length,
|
||||
height: 20,
|
||||
properties: { circles, color: '#c0392b' },
|
||||
};
|
||||
c.doc.transact(() => { c.doc!.addElement(el); });
|
||||
ctx.setStatus(`Absperrung platziert (${length} lang, ${pairs} Kettenglieder)`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const eventToolsV2Plugin: PluginV2 = {
|
||||
manifest: {
|
||||
id: 'event-tools',
|
||||
name: 'Event-Bestuhlung',
|
||||
version: '2.1.0',
|
||||
version: '2.2.0',
|
||||
author: 'web-cad team',
|
||||
description: 'Stühle, Reihen (Drag), Blöcke (Drag), Tische, Bühne (V2).',
|
||||
description: 'Stühle, Reihen (Drag), Blöcke (Drag), Tische, Bühne, Vorhang, Scheinwerfer, Absperrung (V2).',
|
||||
category: 'elements',
|
||||
enabledByDefault: true,
|
||||
},
|
||||
tools: [chairTool, seatingRowToolDrag, seatingBlockToolDrag, tableTool, stageTool, arcRowTool, kinoTemplateTool, banquetTemplateTool, standingTableTemplateTool, podiumTemplateTool],
|
||||
tools: [chairTool, seatingRowToolDrag, seatingBlockToolDrag, tableTool, stageTool, arcRowTool, kinoTemplateTool, banquetTemplateTool, standingTableTemplateTool, podiumTemplateTool, curtainTool, spotlightTool, barrierTool],
|
||||
};
|
||||
|
||||
@@ -236,6 +236,66 @@ export function elementToPrimitives(el: CADElement): ShapePrimitive[] {
|
||||
}];
|
||||
}
|
||||
|
||||
case 'curtain': {
|
||||
// Vorhang (Task E7): wellige Polyline entlang der Breite.
|
||||
const pts = Array.isArray(props.points)
|
||||
? (props.points as unknown[]).map(asPoint)
|
||||
: [];
|
||||
if (pts.length >= 2) {
|
||||
return [{ kind: 'polyline', points: pts, close: false, stroke, strokeWidth: sw }];
|
||||
}
|
||||
return [{
|
||||
kind: 'line',
|
||||
from: { x: el.x - el.width / 2, y: el.y },
|
||||
to: { x: el.x + el.width / 2, y: el.y },
|
||||
stroke,
|
||||
strokeWidth: sw,
|
||||
}];
|
||||
}
|
||||
|
||||
case 'spotlight': {
|
||||
// Scheinwerfer (Task E7): Kegel-Polygon in properties.color.
|
||||
const pts = Array.isArray(props.points)
|
||||
? (props.points as unknown[]).map(asPoint)
|
||||
: [];
|
||||
if (pts.length >= 3) {
|
||||
const col = (props.color as string | undefined) ?? '#ffcc00';
|
||||
return [{ kind: 'polyline', points: pts, close: true, stroke: col, strokeWidth: sw }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
case 'barrier': {
|
||||
// Absperrung (Task E7): Kette aus Kreis-Paaren (properties.circles).
|
||||
const circles = Array.isArray(props.circles)
|
||||
? (props.circles as Array<{ x: number; y: number; r: number }>)
|
||||
: [];
|
||||
const col = (props.color as string | undefined) ?? '#c0392b';
|
||||
if (circles.length > 0) {
|
||||
return circles.map((c) => ({
|
||||
kind: 'circle' as const,
|
||||
center: { x: c.x, y: c.y },
|
||||
radius: c.r,
|
||||
fill: null,
|
||||
stroke: col,
|
||||
strokeWidth: 1,
|
||||
}));
|
||||
}
|
||||
const out: ShapePrimitive[] = [];
|
||||
const N = 5;
|
||||
for (let i = 0; i < N; i++) {
|
||||
out.push({
|
||||
kind: 'circle',
|
||||
center: { x: el.x - el.width / 2 + (el.width * (i + 0.5)) / N, y: el.y },
|
||||
radius: Math.min(6, el.width / (N * 2.5)),
|
||||
fill: null,
|
||||
stroke: col,
|
||||
strokeWidth: 1,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -359,6 +359,7 @@ export class SeatingService {
|
||||
stroke: '#1a2e3f',
|
||||
strokeWidth: 2,
|
||||
label: cfg.label,
|
||||
height: cfg.height, // Bühnenhöhe in cm (Task E7: stage height attr)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ export type ElementType =
|
||||
| 'line' | 'circle' | 'arc' | 'rect' | 'polygon'
|
||||
| 'polyline' | 'text' | 'dimension' | 'block_instance' | 'chair'
|
||||
| 'seating-row' | 'seating-block' | 'table' | 'stage'
|
||||
| 'leader' | 'revcloud' | 'image';
|
||||
| 'leader' | 'revcloud' | 'image'
|
||||
| 'curtain' | 'spotlight' | 'barrier';
|
||||
|
||||
export type LineType = 'solid' | 'dashed' | 'dotted';
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Task E7 – Event-Elemente-Polish: curtain, spotlight, barrier,
|
||||
* stage-height-Attribut.
|
||||
*
|
||||
* Prüft die Tool-Platzierung (Element-Form + Options), die
|
||||
* Pixi-Drawer-Primitives und die height-Durchreichung für stage.
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
curtainTool, spotlightTool, barrierTool,
|
||||
} from '../src/plugins/builtin/event-tools';
|
||||
import { createInMemoryDoc } from './helpers/inMemoryDoc';
|
||||
import { CADDocument } from '../src/kernel/document/CADDocument';
|
||||
import { elementToPrimitives } from '../src/render/pixi/elementDrawers';
|
||||
import { SeatingService } from '../src/services/seatingService';
|
||||
import type { CADElement, Pt } from '../src/types/cad.types';
|
||||
|
||||
function fireDown(tool: any, world: Pt, options: Record<string, unknown> = {}) {
|
||||
const { ydoc } = createInMemoryDoc();
|
||||
const doc = new CADDocument(ydoc);
|
||||
const ctx = {
|
||||
doc,
|
||||
options,
|
||||
setStatus: () => {},
|
||||
setPreview: () => {},
|
||||
} as any;
|
||||
tool.handlers.down({ world } as any, ctx);
|
||||
return { added: doc.getAllElements(), doc };
|
||||
}
|
||||
|
||||
describe('E7: curtain', () => {
|
||||
it('curtainTool platziert curtain-Element mit Wellen-Polyline und stageHeight', () => {
|
||||
const { added } = fireDown(curtainTool, { x: 100, y: 50 }, { stageHeight: 120 });
|
||||
expect(added.length).toBe(1);
|
||||
const el = added[0];
|
||||
expect(el.type).toBe('curtain');
|
||||
expect(el.width).toBe(400); // Standard-Breite
|
||||
expect(el.properties.stageHeight).toBe(120);
|
||||
const pts = el.properties.points as Pt[];
|
||||
expect(Array.isArray(pts)).toBe(true);
|
||||
expect(pts.length).toBeGreaterThan(20); // Wellenpunkte
|
||||
});
|
||||
|
||||
it('Drawer: curtain → polyline-Primitive (geschlossen=false)', () => {
|
||||
const { added } = fireDown(curtainTool, { x: 100, y: 50 }, { stageHeight: 120 });
|
||||
const prims = elementToPrimitives(added[0]);
|
||||
expect(prims.length).toBe(1);
|
||||
expect(prims[0].kind).toBe('polyline');
|
||||
expect((prims[0] as any).close).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('E7: spotlight', () => {
|
||||
it('spotlightTool platziert Lichtkegel-Polygon mit angle und color', () => {
|
||||
const { added } = fireDown(spotlightTool, { x: 0, y: 0 }, { angle: 45, color: '#ffcc00' });
|
||||
expect(added.length).toBe(1);
|
||||
const el = added[0];
|
||||
expect(el.type).toBe('spotlight');
|
||||
expect(el.properties.angle).toBe(45);
|
||||
expect(el.properties.color).toBe('#ffcc00');
|
||||
const pts = el.properties.points as Pt[];
|
||||
expect(pts.length).toBe(4); // Kegel: Ursprung + zwei Basis-Ecken + zurück
|
||||
});
|
||||
|
||||
it('Drawer: spotlight → Polygon mit Füllung aus color', () => {
|
||||
const { added } = fireDown(spotlightTool, { x: 0, y: 0 }, { angle: 45, color: '#ffcc00' });
|
||||
const prims = elementToPrimitives(added[0]);
|
||||
expect(prims.length).toBe(1);
|
||||
expect(prims[0].kind).toBe('polyline');
|
||||
expect((prims[0] as any).close).toBe(true);
|
||||
expect((prims[0] as any).stroke.toLowerCase()).toContain('#ffcc00');
|
||||
});
|
||||
|
||||
it('Kegel-Ausrichtung: angle=0 zeigt nach rechts (Richtung +x)', () => {
|
||||
const { added } = fireDown(spotlightTool, { x: 0, y: 0 }, { angle: 0, color: '#fff' });
|
||||
const pts = added[0].properties.points as Pt[];
|
||||
expect(pts[1].x).toBeGreaterThan(100); // Basis-Ecke weiter rechts als Ursprung
|
||||
expect(pts[2].x).toBeGreaterThan(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('E7: barrier', () => {
|
||||
it('barrierTool platziert Kette aus Kreis-Paaren entlang Standardlänge', () => {
|
||||
const { added } = fireDown(barrierTool, { x: 0, y: 0 }, {});
|
||||
expect(added.length).toBe(1);
|
||||
const el = added[0];
|
||||
expect(el.type).toBe('barrier');
|
||||
expect(el.width).toBe(200); // Standard-Länge
|
||||
});
|
||||
|
||||
it('Drawer: barrier → mehrere circle-Primitives (Kettenglieder)', () => {
|
||||
const { added } = fireDown(barrierTool, { x: 0, y: 0 }, {});
|
||||
const prims = elementToPrimitives(added[0]);
|
||||
expect(prims.length).toBeGreaterThan(4);
|
||||
expect(prims.every((p) => p.kind === 'circle')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('E7: stage height', () => {
|
||||
it('createStage nimmt height-Option auf (properties.height)', () => {
|
||||
const stage = new SeatingService().createStage(100, 100, 'layer-0', { width: 400, height: 60 });
|
||||
expect(stage.type).toBe('stage');
|
||||
expect(stage.properties.height).toBe(60);
|
||||
expect(stage.width).toBe(400);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user