floor-planner/tests/board-tracking.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

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);
});
});