floor-planner/tests/cut-list.test.ts
2026-09-11 16:09:28 +01:00

119 lines
3.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { defaultProject } from '../src/lib/defaults';
import { flooringCuts, groupFlooringCuts } from '../src/lib/cut-list';
import { planFlooring } from '../src/lib/flooring';
import type { FlooringPiece, FlooringPlan } from '../src/lib/types';
function piece(id: string, changes: Partial<FlooringPiece> = {}): FlooringPiece {
return {
id,
roomId: 'room-1',
row: 1,
column: 1,
polygon: [],
length: 600,
width: 190,
stockId: 'B1',
reused: false,
cut: true,
shaped: false,
template: [],
factoryLeft: true,
factoryRight: false,
ripEdge: null,
...changes,
};
}
function plan(pieces: FlooringPiece[]): FlooringPlan {
return {
pieces,
rows: [],
stocks: [],
boardCount: 0,
cuts: 0,
wasteArea: 0,
wastePercent: 0,
reusedCount: 0,
coveredArea: 0,
issues: [],
};
}
describe('flooring cut batches', () => {
it('combines matching cuts across rows and stocks, preserving every placement exactly once', () => {
const pieces = [
piece('F1'),
piece('F2', { row: 4, stockId: 'B4', reused: true }),
piece('F3', { cut: false, length: 1200 }),
piece('F4', { cut: false, reused: true }),
piece('F5', { roomId: 'room-2' }),
];
const source = plan(pieces),
before = structuredClone(source);
const groups = groupFlooringCuts(source);
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F1', 'F2'], ['F4'], ['F5']]);
expect(
groups
.flat()
.map((p) => p.id)
.sort(),
).toEqual(
flooringCuts(source)
.map((p) => p.id)
.sort(),
);
expect(source).toEqual(before);
expect(groupFlooringCuts(source)).toEqual(groups);
});
it('keeps different factory ends, rip edges and thousandth-millimetre dimensions separate', () => {
const groups = groupFlooringCuts(
plan([
piece('F1'),
piece('F2', { factoryLeft: false, factoryRight: true }),
piece('F3', { ripEdge: 'top' }),
piece('F4', { ripEdge: 'bottom' }),
piece('F5', { length: 600.001 }),
piece('F6', { width: 190.001 }),
]),
);
expect(groups).toHaveLength(6);
});
it('groups shapes only when their complete measured templates match', () => {
const template = [
{ x: 0, y: 0 },
{ x: 600, y: 0 },
{ x: 600, y: 190 },
{ x: 0, y: 100 },
];
const groups = groupFlooringCuts(
plan([
piece('F1', { shaped: true, template }),
piece('F2', { shaped: true, template: structuredClone(template) }),
piece('F3', { shaped: true, template: [...template.slice(0, 3), { x: 0, y: 100.001 }] }),
piece('F4'),
]),
);
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F1', 'F2'], ['F3'], ['F4']]);
});
it('reduces the default room to a few batches while keeping all cut and offcut codes', () => {
const project = defaultProject();
const floor = planFlooring(project.rooms, project.flooring, project.tolerance);
const groups = groupFlooringCuts(floor);
expect(groups.length).toBeLessThanOrEqual(7);
expect(groups.length).toBeLessThan(flooringCuts(floor).length / 2);
expect(
groups
.flat()
.map((p) => p.id)
.sort(),
).toEqual(
flooringCuts(floor)
.map((p) => p.id)
.sort(),
);
expect(new Set(groups.flat().map((p) => p.id)).size).toBe(flooringCuts(floor).length);
});
});