119 lines
3.5 KiB
TypeScript
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);
|
|
});
|
|
});
|