This commit is contained in:
parent
171fbf1927
commit
8bf3e3b705
40 changed files with 1443 additions and 1497 deletions
|
|
@ -1,9 +1,7 @@
|
|||
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 { 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();
|
||||
|
|
@ -20,7 +18,6 @@ function floorPiece(id: string, stockId: string, roomId = 'room-1', cut = true):
|
|||
blankLength: length,
|
||||
blankWidth: 190,
|
||||
row: 1,
|
||||
column: 1,
|
||||
polygon: [],
|
||||
template: [
|
||||
{ x: 0, y: 0 },
|
||||
|
|
@ -69,6 +66,7 @@ function skirtPiece(id: string, stockId: string, roomId = 'room-1', length = 900
|
|||
roomId,
|
||||
length,
|
||||
blankLength: length,
|
||||
cut: length !== 2400,
|
||||
backLength: length,
|
||||
wallId: 'w1',
|
||||
wallIndex: 0,
|
||||
|
|
@ -81,10 +79,6 @@ function skirtPiece(id: string, stockId: string, roomId = 'room-1', length = 900
|
|||
template,
|
||||
installedTemplate: template,
|
||||
visibleLength: length,
|
||||
leftOverlap: 0,
|
||||
rightOverlap: 0,
|
||||
leftBevel: 0,
|
||||
rightBevel: 0,
|
||||
};
|
||||
}
|
||||
const skirting: SkirtingPlan = {
|
||||
|
|
@ -104,122 +98,57 @@ const skirting: SkirtingPlan = {
|
|||
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,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const cuts = cutData(flooring, skirting, project.flooring);
|
||||
|
||||
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']);
|
||||
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('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('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('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('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('returns empty matches for empty or unknown queries and never mutates the plans', () => {
|
||||
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(' ')).toEqual({ pieceIds: [], roomIds: [], boardKeys: [] });
|
||||
expect(find('missing')).toEqual({ pieceIds: [], roomIds: [], boardKeys: [] });
|
||||
expect(find('B1')).toEqual(find('B1'));
|
||||
expect(find(' ').pieceKeys.size).toBe(0);
|
||||
expect(find('missing').pieceKeys.size).toBe(0);
|
||||
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