This commit is contained in:
Andras Schmelczer 2026-09-11 16:58:56 +01:00
commit 9689bfcc18
23 changed files with 1815 additions and 955 deletions

View file

@ -2,7 +2,14 @@ import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';
import { FlooringPreview, SkirtingPreview } from '../src/components/CutPreview';
import { exactMm, flooringTemplate, skirtingEndBoxes } from '../src/lib/cut-preview';
import { CutDialog } from '../src/components/CutDialog';
import { defaultProject } from '../src/lib/defaults';
import {
dimensionedOutline,
exactMm,
flooringTemplate,
skirtingEndBoxes,
} from '../src/lib/cut-preview';
import type { FlooringPiece, Point, SkirtingPiece } from '../src/lib/types';
const floor: FlooringPiece = {
@ -54,7 +61,10 @@ describe('cut shape previews', () => {
const markup = renderToStaticMarkup(createElement(FlooringPreview, { piece: floor }));
expect(markup).toContain('aria-label="Flooring shape F1"');
expect(markup).toContain('preserveAspectRatio="xMidYMid meet"');
expect(markup).toContain('Length 600.001 mm × width 190 mm');
expect(markup).toContain('600.001 × 190 mm');
expect(markup).toContain('>600.001</text>');
expect(markup).toContain('>190</text>');
expect(markup.match(/class="dimension-label"/g)).toHaveLength(2);
expect(exactMm(600.001)).toBe('600.001');
});
@ -81,10 +91,133 @@ describe('cut shape previews', () => {
);
expect(markup).toContain('points="18,0 1182,0 1200,18 0,18"');
expect(markup.match(/preserveAspectRatio="xMidYMid meet"/g)).toHaveLength(3);
expect(markup).toContain('Full length 1,200 mm × thickness 18 mm');
expect(markup).toContain('1,200 × 18 mm');
expect(markup).toContain('>1,164</text>');
expect(markup).toContain('>1,200</text>');
expect(markup).not.toContain('exaggerat');
});
it('labels every edge of a 320 by 100 mm notch without collisions or tiny mobile text', () => {
const notch = [
{ x: 0, y: 0 },
{ x: 880, y: 0 },
{ x: 880, y: 100 },
{ x: 1200, y: 100 },
{ x: 1200, y: 190 },
{ x: 0, y: 190 },
];
const original = structuredClone(notch);
const preview = dimensionedOutline(notch);
expect(preview.dimensions.map((dimension) => dimension.length)).toEqual([
880, 100, 320, 90, 1200, 190,
]);
expect((preview.font * 280) / preview.box.width).toBeGreaterThanOrEqual(12);
preview.dimensions.forEach((dimension, i) => {
const box = dimension.labelBox;
expect(box.x).toBeGreaterThan(preview.box.x);
expect(box.y).toBeGreaterThan(preview.box.y);
expect(box.x + box.width).toBeLessThan(preview.box.x + preview.box.width);
expect(box.y + box.height).toBeLessThan(preview.box.y + preview.box.height);
const projection = (point: Point) =>
point.x * dimension.normal.x + point.y * dimension.normal.y;
const nearestLabel = Math.min(
...[
{ x: box.x, y: box.y },
{ x: box.x + box.width, y: box.y },
{ x: box.x, y: box.y + box.height },
{ x: box.x + box.width, y: box.y + box.height },
].map(projection),
);
expect(nearestLabel, 'Dimension text stays outside the shape').toBeGreaterThan(
Math.max(...notch.map(projection)),
);
for (const other of preview.dimensions.slice(i + 1)) {
const b = other.labelBox;
expect(
box.x + box.width <= b.x ||
b.x + b.width <= box.x ||
box.y + box.height <= b.y ||
b.y + b.height <= box.y,
).toBe(true);
}
});
expect(notch).toEqual(original);
expect(dimensionedOutline(notch)).toEqual(preview);
const markup = renderToStaticMarkup(
createElement(FlooringPreview, {
piece: { ...floor, length: 1200, shaped: true, template: notch },
}),
);
expect(markup).toContain('>320</text>');
expect(markup).toContain('>100</text>');
expect(markup).not.toMatch(/>[1-6]<\/text>/);
});
it('measures angled edges along the cut and retains the original outline', () => {
const points = [
{ x: 0, y: 0 },
{ x: 800, y: 0 },
{ x: 920, y: 160 },
{ x: 0, y: 160 },
];
const preview = dimensionedOutline(points);
expect((preview.font * 280) / preview.box.width).toBeGreaterThanOrEqual(12);
expect(preview.dimensions[1].length).toBe(200);
expect(preview.dimensions[1].text).toBe('200');
const angle = preview.dimensions[1].angle!;
expect(angle).toBeCloseTo(53.130102, 6);
const markedAngle = (Number(exactMm(angle)) * Math.PI) / 180;
expect(200 * Math.cos(markedAngle)).toBeCloseTo(120, 2);
expect(200 * Math.sin(markedAngle)).toBeCloseTo(160, 2);
expect(preview.dimensions[1].start).toEqual({ x: 800, y: 0 });
expect(preview.dimensions[1].end).toEqual({ x: 920, y: 160 });
const reversed = dimensionedOutline([...points].reverse());
expect(reversed.dimensions.map((dimension) => dimension.length).sort()).toEqual(
preview.dimensions.map((dimension) => dimension.length).sort(),
);
expect(preview.dimensions.filter((dimension) => dimension.angle !== undefined)).toHaveLength(1);
const markup = renderToStaticMarkup(
createElement(FlooringPreview, {
piece: { ...floor, length: 920, width: 160, shaped: true, template: points },
}),
);
expect(markup).toContain('>53.13°</tspan>');
expect(markup).toContain('angles from horizontal');
});
it('keeps dialog instructions useful without source codes or numbered coordinate lists', () => {
const markup = renderToStaticMarkup(
createElement(CutDialog, {
floor: { ...floor, factoryLeft: true, ripEdge: 'top' },
project: defaultProject(),
close: () => {},
}),
);
expect(markup).toContain('Living room · Row 1');
expect(markup).toContain('Keep the left factory end; trim from the right.');
expect(markup).toContain('Rip the top long edge; keep the bottom factory edge.');
expect(markup).not.toContain('Source');
expect(markup).not.toContain('coordinates');
expect(markup).not.toContain('cut-edge-lengths');
});
it('shows skirting dimensions and mitre directions without a second measurement list', () => {
const markup = renderToStaticMarkup(
createElement(CutDialog, {
skirt,
project: defaultProject(),
close: () => {},
}),
);
expect(markup).toContain('Living room · Wall 1');
expect(markup).toContain('>1,164</text>');
expect(markup).toContain('>1,200</text>');
expect(markup).toContain('45° external');
expect(markup).not.toContain('Source');
expect(markup).not.toContain('coordinates');
expect(markup).not.toContain('<dl');
});
it('keeps the complete end cut visible even at shallow mitre angles', () => {
const shallow = [
{ x: 0, y: 0 },