..
This commit is contained in:
parent
9689bfcc18
commit
62a385e4c9
33 changed files with 2649 additions and 809 deletions
200
tests/board-tracking.test.ts
Normal file
200
tests/board-tracking.test.ts
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import { createElement } from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { defaultProject, rectangle } from '../src/lib/defaults';
|
||||
import { boardSearchLabel, findBoardMatches } from '../src/lib/board-tracking';
|
||||
import { CutList } from '../src/components/CutList';
|
||||
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 {
|
||||
return {
|
||||
id,
|
||||
stockId,
|
||||
roomId,
|
||||
cut,
|
||||
length: cut ? 400 : 1200,
|
||||
width: 190,
|
||||
row: 1,
|
||||
column: 1,
|
||||
polygon: [],
|
||||
template: [],
|
||||
shaped: false,
|
||||
reused: false,
|
||||
};
|
||||
}
|
||||
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 {
|
||||
return {
|
||||
id,
|
||||
stockId,
|
||||
roomId,
|
||||
length,
|
||||
backLength: length,
|
||||
wallId: 'w1',
|
||||
wallIndex: 0,
|
||||
start: { x: 0, y: 0 },
|
||||
end: { x: length, y: 0 },
|
||||
leftMitre: 0,
|
||||
rightMitre: 0,
|
||||
leftType: 'square',
|
||||
rightType: 'square',
|
||||
};
|
||||
}
|
||||
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 noop = () => {};
|
||||
function list(search = '', completedBoards: string[] = []) {
|
||||
return renderToStaticMarkup(
|
||||
createElement(CutList, {
|
||||
project,
|
||||
flooring,
|
||||
skirting,
|
||||
select: noop,
|
||||
search,
|
||||
setSearch: noop,
|
||||
completedBoards,
|
||||
toggleBoard: noop,
|
||||
locateBoard: noop,
|
||||
locatePiece: noop,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe('board tracking search', () => {
|
||||
it('matches cut and stock codes exactly instead of confusing F1 with F10/F12 or B1 with B10', () => {
|
||||
expect(find(' F1 ')).toEqual({
|
||||
pieceIds: ['F1'],
|
||||
roomIds: ['room-1'],
|
||||
boardKeys: ['floor:B1'],
|
||||
});
|
||||
expect(find('b1')).toEqual({
|
||||
pieceIds: ['F1', 'F12', 'B1'],
|
||||
roomIds: ['room-1', 'room-2'],
|
||||
boardKeys: ['floor:B1', 'skirting:S6'],
|
||||
});
|
||||
expect(find('S6').pieceIds).toEqual(['A2', 'B1']);
|
||||
expect(find('B10').pieceIds).toEqual(['F10', 'B10']);
|
||||
});
|
||||
|
||||
it('uses material-qualified source keys for unambiguous board location and includes all its rooms', () => {
|
||||
expect(find(' FLOOR:B1 ')).toEqual({
|
||||
pieceIds: ['F1', 'F12'],
|
||||
roomIds: ['room-1', 'room-2'],
|
||||
boardKeys: ['floor:B1'],
|
||||
});
|
||||
expect(find('skirting:S6')).toEqual({
|
||||
pieceIds: ['A2', 'B1'],
|
||||
roomIds: ['room-1', 'room-2'],
|
||||
boardKeys: ['skirting:S6'],
|
||||
});
|
||||
});
|
||||
|
||||
it('finds full uncut boards as well as cuts, and searches room names without case sensitivity', () => {
|
||||
expect(find('b3')).toEqual({ pieceIds: ['FU1'], roomIds: ['room-2'], boardKeys: ['floor:B3'] });
|
||||
expect(find('s7')).toEqual({
|
||||
pieceIds: ['SU1'],
|
||||
roomIds: ['room-2'],
|
||||
boardKeys: ['skirting:S7'],
|
||||
});
|
||||
expect(find(' OfFiCe ')).toEqual({
|
||||
pieceIds: ['F12', 'FU1', 'B1', 'SU1'],
|
||||
roomIds: ['room-2'],
|
||||
boardKeys: ['floor:B1', 'floor:B3', 'skirting:S6', 'skirting:S7'],
|
||||
});
|
||||
expect(find('living').roomIds).toEqual(['room-1']);
|
||||
});
|
||||
|
||||
it('returns empty matches for empty or unknown queries and never mutates the plans', () => {
|
||||
const before = structuredClone({ project, flooring, skirting });
|
||||
expect(find(' ')).toEqual({ pieceIds: [], roomIds: [], boardKeys: [] });
|
||||
expect(find('missing')).toEqual({ pieceIds: [], roomIds: [], boardKeys: [] });
|
||||
expect(find('B1')).toEqual(find('B1'));
|
||||
expect({ project, flooring, skirting }).toEqual(before);
|
||||
});
|
||||
|
||||
it('keeps all cuts from a matching source together and applies completion to the whole group', () => {
|
||||
const markup = list('F1', ['floor:B1']);
|
||||
expect(markup).toContain('data-board-key="floor:B1"');
|
||||
expect(markup).not.toContain('data-board-key="floor:B10"');
|
||||
expect(markup).not.toContain('data-board-key="skirting:S6"');
|
||||
expect(markup).toContain('Preview flooring cut F1');
|
||||
expect(markup).toContain('Preview flooring cut F12');
|
||||
expect(markup.match(/class="board-cut is-done/g)).toHaveLength(2);
|
||||
expect(markup).toContain('aria-label="Mark cuts F1 + F12 done" checked=""');
|
||||
expect(markup).toContain('Show cuts F1 + F12 on plan');
|
||||
expect(markup).toContain('Show cut F12 on plan');
|
||||
expect(markup).toContain('<span>Done</span>');
|
||||
});
|
||||
|
||||
it('names board groups and actions using their cuts while keeping bookkeeping IDs internal', () => {
|
||||
const markup = list();
|
||||
expect(markup).toContain('aria-label="Flooring cuts F1 + F12"');
|
||||
expect(markup).toContain('aria-label="Skirting cuts A2 + B1"');
|
||||
expect(markup).toContain('Mark cuts A2 + B1 done');
|
||||
expect(markup).toContain('data-board-key="skirting:S6"');
|
||||
expect(markup).toContain('Find a cut or room');
|
||||
expect(markup).not.toMatch(/(?:Floorboard B\d|Skirting board S\d|Export CSV)/i);
|
||||
expect(markup).not.toMatch(/aria-label="[^"]*(?:floor:B\d|skirting:S\d)/i);
|
||||
});
|
||||
|
||||
it('shows cut names when a located board is stored as a qualified source key', () => {
|
||||
expect(boardSearchLabel(project, flooring, skirting, 'floor:B1')).toBe('F1 + F12');
|
||||
expect(boardSearchLabel(project, flooring, skirting, 'skirting:S6')).toBe('A2 + B1');
|
||||
expect(boardSearchLabel(project, flooring, skirting, 'Office')).toBe('Office');
|
||||
expect(list('floor:B1')).toContain('value="F1 + F12"');
|
||||
expect(list('skirting:S6')).toContain('value="A2 + B1"');
|
||||
expect(find('F1 + F12')).toEqual(find('floor:B1'));
|
||||
// B1 here is a skirting cut, not the hidden flooring stock with the same internal code.
|
||||
expect(find('A2 + B1')).toEqual(find('skirting:S6'));
|
||||
});
|
||||
|
||||
it('exposes an interactive full-board result only when searched, without inventing a cut preview', () => {
|
||||
expect(list()).not.toContain('data-board-key="floor:B3"');
|
||||
const markup = list('floor:B3');
|
||||
expect(markup).toContain('data-board-key="floor:B3"');
|
||||
expect(markup).toContain('Mark uncut floorboard done');
|
||||
expect(markup).toContain('Show uncut floorboard on plan');
|
||||
expect(markup).toContain('Installed uncut');
|
||||
expect(markup).toContain('value="Uncut floorboard"');
|
||||
expect(markup).not.toContain('Preview flooring cut FU1');
|
||||
expect(list('skirting:S7')).toContain('value="Uncut skirting board"');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue