floor-planner/tests/cut-list.test.ts
Andras Schmelczer 8bf3e3b705
Some checks failed
Deploy to Pages / build (push) Failing after 10m45s
Simplify
2026-09-11 23:27:29 +01:00

111 lines
3.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { defaultProject, rectangle } from '../src/lib/defaults';
import {
flooringCuts,
groupFlooringCuts,
groupSkirtingCuts,
skirtingCuts,
} from '../src/lib/cut-list';
import { planFlooring } from '../src/lib/flooring';
import { planSkirting } from '../src/lib/skirting';
import type { FlooringPiece, FlooringPlan } from '../src/lib/types';
function piece(id: string, stockId: string, changes: Partial<FlooringPiece> = {}): FlooringPiece {
const length = changes.length ?? 600;
const width = changes.width ?? 190;
return {
id,
roomId: 'room-1',
row: 1,
polygon: [],
length,
width,
blankLength: length,
blankWidth: width,
stockId,
reused: false,
cut: true,
shaped: false,
template: [
{ x: 0, y: 0 },
{ x: length, y: 0 },
{ x: length, y: width },
{ x: 0, y: width },
],
factoryLeft: false,
factoryRight: false,
ripEdge: null,
...changes,
};
}
function plan(pieces: FlooringPiece[]): FlooringPlan {
return {
pieces,
rows: [],
stocks: ['B1', 'B2', 'B3'].map((id) => ({
id,
pieceIds: pieces.filter((p) => p.stockId === id).map((p) => p.id),
remaining: 0,
})),
boardCount: 3,
cuts: 0,
wasteArea: 0,
wastePercent: 0,
reusedCount: 0,
coveredArea: 0,
issues: [],
};
}
describe('cuts grouped by purchased board', () => {
it('keeps different cuts and cross-room offcuts together, separating identical cuts from different boards', () => {
const source = plan([
piece('F1', 'B2'),
piece('F2', 'B1'),
piece('F3', 'B2', { roomId: 'room-2', row: 4, length: 397, reused: true }),
piece('FU1', 'B3', { length: 1200, cut: false }),
]);
const before = structuredClone(source);
const groups = groupFlooringCuts(source);
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F2'], ['F1', 'F3']]);
expect(source).toEqual(before);
expect(groupFlooringCuts(source)).toEqual(groups);
});
it('includes ready-to-use offcuts in their original board group', () => {
const groups = groupFlooringCuts(
plan([piece('F1', 'B1'), piece('F2', 'B1', { cut: false, reused: true })]),
);
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F1', 'F2']]);
});
it('includes every flooring and skirting cut once in stock order across multiple rooms', () => {
const project = defaultProject();
project.rooms.push(rectangle('room-2', 'Study', 3200, 3100));
const floor = planFlooring(project.rooms, project.flooring, project.tolerance);
const skirt = planSkirting(project.rooms, project.skirting, project.tolerance);
for (const [groups, pieces, stocks] of [
[groupFlooringCuts(floor), flooringCuts(floor), floor.stocks],
[groupSkirtingCuts(skirt), skirtingCuts(skirt), skirt.stocks],
] as const) {
expect(
groups
.flat()
.map((p) => p.id)
.sort(),
).toEqual(pieces.map((p) => p.id).sort());
expect(new Set(groups.flat().map((p) => p.id)).size).toBe(pieces.length);
const used = new Set(pieces.map((p) => p.stockId));
expect(groups.map((group) => group[0].stockId)).toEqual(
stocks.filter((s) => used.has(s.id)).map((s) => s.id),
);
for (const group of groups) expect(new Set(group.map((p) => p.stockId)).size).toBe(1);
}
expect(
[...groupFlooringCuts(floor), ...groupSkirtingCuts(skirt)].some(
(group) => new Set(group.map((p) => p.roomId)).size > 1,
),
).toBe(true);
});
});