154 lines
5.4 KiB
TypeScript
154 lines
5.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { defaultProject, rectangle } from '../src/lib/defaults';
|
|
import { focusLabel, findBoardMatches } from '../src/lib/board-tracking';
|
|
import { cutData } from '../src/lib/cut-list';
|
|
import type { FlooringPiece, FlooringPlan, SkirtingPiece, SkirtingPlan } from '../src/lib/types';
|
|
|
|
const project = defaultProject();
|
|
project.rooms.push(rectangle('room-2', 'Office'));
|
|
function floorPiece(id: string, stockId: string, roomId = 'room-1', cut = true): FlooringPiece {
|
|
const length = cut ? 400 : 1200;
|
|
return {
|
|
id,
|
|
stockId,
|
|
roomId,
|
|
cut,
|
|
length,
|
|
width: 190,
|
|
blankLength: length,
|
|
blankWidth: 190,
|
|
row: 1,
|
|
polygon: [],
|
|
template: [
|
|
{ x: 0, y: 0 },
|
|
{ x: length, y: 0 },
|
|
{ x: length, y: 190 },
|
|
{ x: 0, y: 190 },
|
|
],
|
|
shaped: false,
|
|
reused: false,
|
|
factoryLeft: false,
|
|
factoryRight: false,
|
|
ripEdge: null,
|
|
};
|
|
}
|
|
const flooring: FlooringPlan = {
|
|
pieces: [
|
|
floorPiece('F1', 'B1'),
|
|
floorPiece('F12', 'B1', 'room-2'),
|
|
floorPiece('F10', 'B10'),
|
|
floorPiece('FU1', 'B3', 'room-2', false),
|
|
],
|
|
rows: [],
|
|
stocks: [
|
|
{ id: 'B1', pieceIds: ['F1', 'F12'], remaining: 394 },
|
|
{ id: 'B10', pieceIds: ['F10'], remaining: 797 },
|
|
{ id: 'B3', pieceIds: ['FU1'], remaining: 0 },
|
|
],
|
|
boardCount: 3,
|
|
cuts: 3,
|
|
wasteArea: 0,
|
|
wastePercent: 0,
|
|
reusedCount: 0,
|
|
coveredArea: 0,
|
|
issues: [],
|
|
};
|
|
function skirtPiece(id: string, stockId: string, roomId = 'room-1', length = 900): SkirtingPiece {
|
|
const template: SkirtingPiece['template'] = [
|
|
{ x: 0, y: 0 },
|
|
{ x: length, y: 0 },
|
|
{ x: length, y: 18 },
|
|
{ x: 0, y: 18 },
|
|
];
|
|
return {
|
|
id,
|
|
stockId,
|
|
roomId,
|
|
length,
|
|
blankLength: length,
|
|
cut: length !== 2400,
|
|
backLength: length,
|
|
wallId: 'w1',
|
|
wallIndex: 0,
|
|
start: { x: 0, y: 0 },
|
|
end: { x: length, y: 0 },
|
|
leftMitre: 0,
|
|
rightMitre: 0,
|
|
leftType: 'square',
|
|
rightType: 'square',
|
|
template,
|
|
installedTemplate: template,
|
|
visibleLength: length,
|
|
};
|
|
}
|
|
const skirting: SkirtingPlan = {
|
|
pieces: [
|
|
skirtPiece('A2', 'S6'),
|
|
skirtPiece('B1', 'S6', 'room-2', 1497),
|
|
skirtPiece('B10', 'S60'),
|
|
skirtPiece('SU1', 'S7', 'room-2', 2400),
|
|
],
|
|
stocks: [
|
|
{ id: 'S6', length: 2400, used: 2397, waste: 3, pieceIds: ['A2', 'B1'] },
|
|
{ id: 'S60', length: 2400, used: 900, waste: 1500, pieceIds: ['B10'] },
|
|
{ id: 'S7', length: 2400, used: 2400, waste: 0, pieceIds: ['SU1'] },
|
|
],
|
|
stockCount: 3,
|
|
cuts: 3,
|
|
waste: 1503,
|
|
issues: [],
|
|
};
|
|
|
|
const find = (query: string) => findBoardMatches(project, flooring, skirting, query);
|
|
const cuts = cutData(flooring, skirting, project.flooring);
|
|
|
|
describe('board and cut location', () => {
|
|
it('matches complete cut codes and multiple codes without interpreting stock commands', () => {
|
|
expect([...find('f1').pieceKeys]).toEqual(['flooring:F1']);
|
|
expect([...find('F1 + F12').pieceKeys]).toEqual(['flooring:F1', 'flooring:F12']);
|
|
expect([...find('B1').pieceKeys]).toEqual(['skirting:B1']);
|
|
expect(find('floor:B1').pieceKeys.size).toBe(0);
|
|
});
|
|
it('keeps identical display labels in different materials distinct', () => {
|
|
const skirt = { ...skirting, pieces: [...skirting.pieces, skirtPiece('F1', 'S8', 'room-2')] };
|
|
const result = findBoardMatches(project, flooring, skirt, 'F1');
|
|
expect([...result.pieceKeys]).toEqual(['flooring:F1', 'skirting:F1']);
|
|
expect(result.materials).toEqual(new Set(['flooring', 'skirting']));
|
|
const data = cutData(flooring, skirt, project.flooring);
|
|
expect(data.selections.get('flooring:F1')?.piece.roomId).toBe('room-1');
|
|
expect(data.selections.get('skirting:F1')?.piece.roomId).toBe('room-2');
|
|
});
|
|
it('locates all pieces of a source board across rooms without setting search text', () => {
|
|
const focus = { kind: 'board', material: 'flooring', id: 'B1' } as const;
|
|
const result = findBoardMatches(project, flooring, skirting, '', focus);
|
|
expect([...result.pieceKeys]).toEqual(['flooring:F1', 'flooring:F12']);
|
|
expect(result.roomIds).toEqual(['room-1', 'room-2']);
|
|
expect(result.boardKeys).toEqual(new Set(['floor:B1']));
|
|
expect(focusLabel(focus, cuts)).toBe('F1 + F12');
|
|
});
|
|
it('locates uncut boards and searches room names including code-like names', () => {
|
|
const focus = { kind: 'board', material: 'flooring', id: 'B3' } as const;
|
|
expect([...findBoardMatches(project, flooring, skirting, '', focus).pieceKeys]).toEqual([
|
|
'flooring:FU1',
|
|
]);
|
|
expect(focusLabel(focus, cuts)).toBe('Uncut floorboard');
|
|
expect(find(' OfFiCe ').roomIds).toEqual(['room-2']);
|
|
const renamed = { ...project, rooms: project.rooms.map((room) => ({ ...room, name: 'A101' })) };
|
|
expect(findBoardMatches(renamed, flooring, skirting, 'A101').pieceKeys.size).toBe(8);
|
|
});
|
|
it('keeps a focused piece distinct from a focused source board', () => {
|
|
expect([
|
|
...findBoardMatches(project, flooring, skirting, '', {
|
|
kind: 'piece',
|
|
material: 'flooring',
|
|
id: 'F12',
|
|
}).pieceKeys,
|
|
]).toEqual(['flooring:F12']);
|
|
});
|
|
it('returns no matches for empty or unknown input and does not mutate plans', () => {
|
|
const before = structuredClone({ project, flooring, skirting });
|
|
expect(find(' ').pieceKeys.size).toBe(0);
|
|
expect(find('missing').pieceKeys.size).toBe(0);
|
|
expect({ project, flooring, skirting }).toEqual(before);
|
|
});
|
|
});
|