.
This commit is contained in:
parent
d8f2407ddf
commit
9689bfcc18
23 changed files with 1815 additions and 955 deletions
|
|
@ -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 },
|
||||
|
|
|
|||
Loading…
Reference in a new issue