124 lines
4.2 KiB
TypeScript
124 lines
4.2 KiB
TypeScript
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 type { FlooringPiece, Point, SkirtingPiece } from '../src/lib/types';
|
||
|
||
const floor: FlooringPiece = {
|
||
id: 'F1',
|
||
roomId: 'room-1',
|
||
row: 1,
|
||
column: 1,
|
||
polygon: [],
|
||
length: 600.001,
|
||
width: 190,
|
||
stockId: 'B1',
|
||
reused: false,
|
||
cut: true,
|
||
shaped: false,
|
||
template: [],
|
||
};
|
||
const template: Point[] = [
|
||
{ x: 18, y: 0 },
|
||
{ x: 1182, y: 0 },
|
||
{ x: 1200, y: 18 },
|
||
{ x: 0, y: 18 },
|
||
];
|
||
const skirt: SkirtingPiece = {
|
||
id: 'A1',
|
||
roomId: 'room-1',
|
||
wallId: 'w1',
|
||
wallIndex: 0,
|
||
start: { x: 0, y: 0 },
|
||
end: { x: 1164, y: 0 },
|
||
length: 1200,
|
||
backLength: 1164,
|
||
leftMitre: 45,
|
||
rightMitre: 45,
|
||
leftType: 'external',
|
||
rightType: 'external',
|
||
stockId: 'S1',
|
||
template,
|
||
};
|
||
|
||
describe('cut shape previews', () => {
|
||
it('draws rectangular floor cuts too, keeping their precise measured dimensions', () => {
|
||
const points = flooringTemplate(floor);
|
||
expect(points).toEqual([
|
||
{ x: 0, y: 0 },
|
||
{ x: 600.001, y: 0 },
|
||
{ x: 600.001, y: 190 },
|
||
{ x: 0, y: 190 },
|
||
]);
|
||
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(exactMm(600.001)).toBe('600.001');
|
||
});
|
||
|
||
it('keeps skirting endpoints in original coordinates and gives both ends equal square windows', () => {
|
||
const original = structuredClone(template);
|
||
const boxes = skirtingEndBoxes(template, 18);
|
||
expect(boxes.left.width).toBe(boxes.left.height);
|
||
expect(boxes.right.width).toBe(boxes.right.height);
|
||
expect(boxes.left.width).toBe(boxes.right.width);
|
||
// Equal x/y scales preserve the measured 18-by-18 mm, 45-degree cut.
|
||
const scaleX = 120 / boxes.left.width,
|
||
scaleY = 120 / boxes.left.height;
|
||
const angle =
|
||
(Math.atan2(
|
||
Math.abs(template[3].y - template[0].y) * scaleY,
|
||
Math.abs(template[3].x - template[0].x) * scaleX,
|
||
) *
|
||
180) /
|
||
Math.PI;
|
||
expect(angle).toBeCloseTo(45, 8);
|
||
expect(template).toEqual(original);
|
||
const markup = renderToStaticMarkup(
|
||
createElement(SkirtingPreview, { piece: skirt, thickness: 18 }),
|
||
);
|
||
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).not.toContain('exaggerat');
|
||
});
|
||
|
||
it('keeps the complete end cut visible even at shallow mitre angles', () => {
|
||
const shallow = [
|
||
{ x: 0, y: 0 },
|
||
{ x: 2000, y: 0 },
|
||
{ x: 2100, y: 18 },
|
||
{ x: 1000, y: 18 },
|
||
];
|
||
const boxes = skirtingEndBoxes(shallow, 18);
|
||
for (const [name, indices] of [
|
||
['left', [0, 3]],
|
||
['right', [1, 2]],
|
||
] as const) {
|
||
const box = boxes[name];
|
||
for (const index of indices) {
|
||
expect(shallow[index].x).toBeGreaterThan(box.x);
|
||
expect(shallow[index].x).toBeLessThan(box.x + box.width);
|
||
expect(shallow[index].y).toBeGreaterThan(box.y);
|
||
expect(shallow[index].y).toBeLessThan(box.y + box.height);
|
||
}
|
||
}
|
||
});
|
||
|
||
it('makes thumbnails decorative while retaining true-scale SVGs and visible end breaks', () => {
|
||
const floorMarkup = renderToStaticMarkup(
|
||
createElement(FlooringPreview, { piece: floor, compact: true }),
|
||
);
|
||
const skirtMarkup = renderToStaticMarkup(
|
||
createElement(SkirtingPreview, { piece: skirt, thickness: 18, compact: true }),
|
||
);
|
||
expect(floorMarkup).toContain('aria-hidden="true"');
|
||
expect(skirtMarkup.match(/aria-hidden="true"/g)).toHaveLength(3);
|
||
expect(skirtMarkup).toContain('>//</span>');
|
||
expect(skirtMarkup.match(/preserveAspectRatio="xMidYMid meet"/g)).toHaveLength(2);
|
||
expect(floorMarkup).not.toContain('<figure');
|
||
expect(skirtMarkup).not.toContain('<figure');
|
||
});
|
||
});
|