111 lines
3.6 KiB
TypeScript
111 lines
3.6 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,
|
|
column: 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);
|
|
});
|
|
});
|