Files

138 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Task H5 Flächenberechnung Polygon (m²) + measure-area Tool.
*
* polygonArea: Shoelace-Formel (Absolutwert, CW/CCW egal),
* formatArea: Einheiten-Formatierung analog formatDistance,
* measureAreaTool: Klick-Sequenz, Live-Fläche im Status, ENTER
* committet ab 3 Punkten, cancel setzt zurück.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { polygonArea } from '../src/tools/modification/geometry';
import { formatArea } from '../src/utils/format';
import { measureAreaTool } from '../src/plugins/builtin/core-measure';
import type { CADElement } from '../src/types/cad.types';
describe('H5: polygonArea (Shoelace)', () => {
it('Rechteck 100×50 = 5000 Welt-Einheiten²', () => {
const pts = [
{ x: 0, y: 0 }, { x: 100, y: 0 },
{ x: 100, y: 50 }, { x: 0, y: 50 },
];
expect(polygonArea(pts)).toBeCloseTo(5000, 6);
});
it('Dreieck 100×50/2 = 2500', () => {
const pts = [{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 0, y: 50 }];
expect(polygonArea(pts)).toBeCloseTo(2500, 6);
});
it('Uhrzeigersinn und gegen Uhrzeigersinn ergeben denselben Absolutwert', () => {
const ccw = [{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 100 }, { x: 0, y: 100 }];
const cw = [...ccw].reverse();
expect(polygonArea(cw)).toBeCloseTo(polygonArea(ccw), 6);
expect(polygonArea(cw)).toBeCloseTo(10000, 6);
});
it('weniger als 3 Punkte → 0; kollineare Punkte → 0', () => {
expect(polygonArea([{ x: 0, y: 0 }, { x: 1, y: 1 }])).toBe(0);
expect(polygonArea([{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 200, y: 0 }])).toBe(0);
});
});
describe('H5: formatArea', () => {
it('mm: 5000 → "5000 mm²" (scaleFactor 1)', () => {
expect(formatArea(5000, 'mm', 1)).toBe('5000 mm²');
});
it('cm: 5000mm² → "50.0 cm²"', () => {
expect(formatArea(5000, 'cm', 1)).toBe('50.0 cm²');
});
it('m: 5000000mm² → "5.00 m²"', () => {
expect(formatArea(5000000, 'm', 1)).toBe('5.00 m²');
});
it('Flächen skalieren QUADRATISCH: 5 WE² bei scaleFactor 1000 = 5000000 mm²', () => {
// 1 Welt-Einheit = 1000mm → 1 WE² = 1.000.000 mm²
expect(formatArea(5, 'mm', 1000)).toBe('5000000 mm²');
});
});
describe('H5: measureAreaTool', () => {
// Module-State areaPts leakt zwischen Tests — vor jedem Test resetten:
beforeEach(() => {
measureAreaTool.handlers.cancel?.({ setStatus: () => {} } as never);
});
function fireSeq(events: Array<{ type: 'down' | 'move' | 'key' | 'cancel'; x?: number; y?: number; key?: string }>, options: Record<string, unknown> = {}) {
const statuses: string[] = [];
const ctx: any = {
options,
setStatus: (m: string) => statuses.push(m),
setPreview: (_el: CADElement | null) => {},
doc: null,
};
for (const ev of events) {
if (ev.type === 'down') measureAreaTool.handlers.down!({ world: { x: ev.x!, y: ev.y! } } as never, ctx);
if (ev.type === 'move') measureAreaTool.handlers.move!({ world: { x: ev.x!, y: ev.y! } } as never, ctx);
if (ev.type === 'key' && ev.key) measureAreaTool.handlers.key?.({ key: ev.key } as never, ctx);
if (ev.type === 'cancel') measureAreaTool.handlers.cancel?.(ctx);
}
return statuses;
}
it('erster Klick startet Messung, Status fordert weitere Punkte', () => {
const s = fireSeq([{ type: 'down', x: 0, y: 0 }]);
expect(s[0]).toContain('Fläche');
expect(s[s.length - 1]).toContain('klicken');
});
it('Live-Fläche im Status ab 3 Punkten bei move', () => {
const s = fireSeq([
{ type: 'down', x: 0, y: 0 },
{ type: 'down', x: 100, y: 0 },
{ type: 'down', x: 100, y: 50 },
{ type: 'move', x: 0, y: 50 },
]);
expect(s.some((m) => m.includes('5000'))).toBe(true);
expect(s[s.length - 1]).toContain('mm²');
});
it('ENTER committet ab 3 Punkten mit Endfläche im Status', () => {
const s = fireSeq([
{ type: 'down', x: 0, y: 0 },
{ type: 'down', x: 100, y: 0 },
{ type: 'down', x: 100, y: 100 },
{ type: 'key', key: 'Enter' },
]);
// Dreieck (0,0),(100,0),(100,100): Shoelace = 1/2 · 100 · 100 = 5000
expect(s[s.length - 1]).toContain('5000');
expect(s[s.length - 1]).toContain('3 Punkte');
});
it('ENTER mit weniger als 3 Punkten committet NICHT (Status erklärt)', () => {
const s = fireSeq([
{ type: 'down', x: 0, y: 0 },
{ type: 'down', x: 100, y: 0 },
{ type: 'key', key: 'Enter' },
]);
expect(s[s.length - 1]).toContain('3');
});
it('cancel setzt Messung zurück', () => {
const s = fireSeq([
{ type: 'down', x: 0, y: 0 },
{ type: 'down', x: 50, y: 50 },
{ type: 'cancel' },
{ type: 'down', x: 0, y: 0 },
]);
// Nach cancel startet eine frische Messung (kein altes Polygon)
expect(s[s.length - 1]).toContain('klicken');
});
it('Manifest: id measure-area, tags basic, ribbonTab canvas', () => {
expect(measureAreaTool.manifest.id).toBe('measure-area');
expect(measureAreaTool.manifest.ribbonTab).toBe('canvas');
expect((measureAreaTool.manifest.tags ?? [])).toContain('basic');
});
});