This commit is contained in:
parent
171fbf1927
commit
8bf3e3b705
40 changed files with 1443 additions and 1497 deletions
|
|
@ -88,7 +88,7 @@ async function wallDimensionsInsideSvg(page: Page, wallNumber: number) {
|
|||
.filter({ hasText: new RegExp(`W${wallNumber} ·`) });
|
||||
await expect(wall.locator('.wall-label text')).toBeVisible();
|
||||
const result = await wall.evaluate((element) => {
|
||||
const svg = element.ownerSVGElement!;
|
||||
const svg = (element as SVGElement).ownerSVGElement!;
|
||||
const box = svg.viewBox.baseVal;
|
||||
const inverse = svg.getScreenCTM()!.inverse();
|
||||
const rootPoint = (element: SVGGraphicsElement, x: number, y: number) =>
|
||||
|
|
@ -109,7 +109,6 @@ async function wallDimensionsInsideSvg(page: Page, wallNumber: number) {
|
|||
rootPoint(label, bounds.x, bounds.y + bounds.height),
|
||||
];
|
||||
return {
|
||||
lineCount: lines.length,
|
||||
outside: [...endpoints, ...corners]
|
||||
.filter(
|
||||
(point) =>
|
||||
|
|
@ -121,7 +120,6 @@ async function wallDimensionsInsideSvg(page: Page, wallNumber: number) {
|
|||
.map((point) => ({ x: point.x, y: point.y })),
|
||||
};
|
||||
});
|
||||
expect(result.lineCount).toBe(4);
|
||||
expect(
|
||||
result.outside,
|
||||
`W${wallNumber} lines, ticks and label must fit inside the SVG viewBox`,
|
||||
|
|
@ -266,7 +264,7 @@ test.beforeEach(async ({ page }) => {
|
|||
page.on('pageerror', (error) => errors.get(page)!.push(error.message));
|
||||
await page.goto('/?planner');
|
||||
await expect(page.getByRole('heading', { name: 'Room measurements' })).toBeVisible();
|
||||
await expect(page).toHaveURL(/\?version=2&/);
|
||||
await expect(page).toHaveURL(/\?plan=/);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
|
|
@ -405,10 +403,12 @@ test('inconsistent final measurements block cuts and small residuals show the ex
|
|||
}) => {
|
||||
await openWalls(page);
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).fill('3700');
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).blur();
|
||||
await expect(page.getByText(/The outline is open by 100\.0 mm/).first()).toBeVisible();
|
||||
await expect(floorEntries(page)).toHaveCount(0);
|
||||
await expect(skirtEntries(page)).toHaveCount(0);
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).fill('3605');
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).blur();
|
||||
await expect(page.getByText(/non-straight wall/)).toBeVisible();
|
||||
await expect(page.getByText('Final wall: 3,600 mm expected · 5 mm difference')).toBeVisible();
|
||||
expect(await floorEntries(page).count()).toBeGreaterThan(0);
|
||||
|
|
@ -417,9 +417,11 @@ test('inconsistent final measurements block cuts and small residuals show the ex
|
|||
test('room warnings stay with their room even when two room names match', async ({ page }) => {
|
||||
await openWalls(page);
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).fill('3605');
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).blur();
|
||||
await expect(page.getByText(/non-straight wall/).first()).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Add room', exact: true }).click();
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Living room');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
expect(state(page).project.rooms.map((room) => room.name)).toEqual([
|
||||
'Living room',
|
||||
'Living room',
|
||||
|
|
@ -429,6 +431,7 @@ test('room warnings stay with their room even when two room names match', async
|
|||
await expect(page.getByText(/non-straight wall/).first()).toBeVisible();
|
||||
await openWalls(page);
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).fill('3700');
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).blur();
|
||||
await expect(page.getByText(/outline is open by 100\.0 mm/).first()).toBeVisible();
|
||||
await page.locator('.room-tab').nth(1).click();
|
||||
await expect(page.getByText(/outline is open by 100\.0 mm/)).toHaveCount(0);
|
||||
|
|
@ -440,14 +443,17 @@ test('custom walls close using a calculated wall that can be measured again', as
|
|||
await page.getByRole('button', { name: 'Custom', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Add a wall', exact: true }).click();
|
||||
await page.getByLabel('Wall 2 length', { exact: true }).fill('3000');
|
||||
await page.getByLabel('Wall 2 length', { exact: true }).blur();
|
||||
await page.getByRole('button', { name: 'Add a wall', exact: true }).click();
|
||||
await page.getByLabel('Wall 3 length', { exact: true }).fill('4000');
|
||||
await page.getByLabel('Wall 3 length', { exact: true }).blur();
|
||||
await expect(page.getByText('3,000 mm to close')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Add closing wall' }).click();
|
||||
expect(await preview(page).locator('.floor-piece').count()).toBeGreaterThan(0);
|
||||
await openWalls(page);
|
||||
await expect(page.getByLabel('Wall 4 length', { exact: true })).toHaveValue('3000');
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).fill('3050');
|
||||
await page.getByLabel('Wall 4 length', { exact: true }).blur();
|
||||
await expect(page.getByText(/3,000 mm expected; length differs by 50 mm/)).toBeVisible();
|
||||
});
|
||||
|
||||
|
|
@ -554,7 +560,6 @@ test('internal skirting corners retain coping blanks and expose the installed mi
|
|||
await expect(page.locator('.skirting-cuts .cut-summary')).toContainText(
|
||||
'Buy 4 lengths · 1 installed uncut',
|
||||
);
|
||||
const piece = skirt.pieces.find((piece) => piece.id === 'A1')!;
|
||||
const entry = skirtEntries(page).filter({
|
||||
has: page.getByRole('button', { name: 'Preview skirting cut A1', exact: true }),
|
||||
});
|
||||
|
|
@ -582,11 +587,11 @@ test('internal skirting corners retain coping blanks and expose the installed mi
|
|||
expect(Math.abs(geometry.preparationX)).toBeCloseTo(18, 3);
|
||||
expect(Math.abs(geometry.preparationY)).toBeCloseTo(18, 3);
|
||||
expect(geometry.installedX).toBeCloseTo(0, 3);
|
||||
expect(piece.leftOverlap).toBeCloseTo(18, 3);
|
||||
await page.keyboard.press('Escape');
|
||||
const floorCount = await floorEntries(page).count();
|
||||
await page.getByText('Advanced skirting settings', { exact: true }).click();
|
||||
await page.getByLabel('Minimum piece', { exact: true }).fill('2365');
|
||||
await page.getByLabel('Minimum piece', { exact: true }).blur();
|
||||
await expect(skirtEntries(page)).toHaveCount(0);
|
||||
await expect(floorEntries(page)).toHaveCount(floorCount);
|
||||
await expect(page.locator('.issue.error').first()).toBeVisible();
|
||||
|
|
@ -602,7 +607,9 @@ test('door positions and material lengths update skirting immediately', async ({
|
|||
'W1: measure from W4 corner toward W2 corner.',
|
||||
);
|
||||
await movedDoor.getByLabel(/^From W\d+ (corner|start)$/).fill('1200');
|
||||
await movedDoor.getByLabel(/^From W\d+ (corner|start)$/).blur();
|
||||
await movedDoor.getByLabel('Door width', { exact: true }).fill('900');
|
||||
await movedDoor.getByLabel('Door width', { exact: true }).blur();
|
||||
expect(state(page).project.rooms[0].walls[0].doors[0]).toMatchObject({
|
||||
offset: 1200,
|
||||
width: 900,
|
||||
|
|
@ -613,13 +620,16 @@ test('door positions and material lengths update skirting immediately', async ({
|
|||
);
|
||||
expect(await page.locator('.skirting-cuts').innerText()).not.toBe(before);
|
||||
await page.getByLabel('Skirting board length', { exact: true }).fill('3600');
|
||||
await page.getByLabel('Skirting board length', { exact: true }).blur();
|
||||
await page.getByLabel('Skirting thickness', { exact: true }).fill('22');
|
||||
await page.getByLabel('Skirting thickness', { exact: true }).blur();
|
||||
const { skirt } = plans(page);
|
||||
await expect(skirtCodes(page)).toHaveCount(skirtingCuts(skirt).length);
|
||||
await expect(page.locator('.skirting-cuts .cut-summary')).toContainText(
|
||||
`Buy ${skirt.stockCount} lengths`,
|
||||
);
|
||||
await movedDoor.getByLabel('Door width', { exact: true }).fill('6000');
|
||||
await movedDoor.getByLabel('Door width', { exact: true }).blur();
|
||||
await expect(page.getByText(/must fit within the wall/).first()).toBeVisible();
|
||||
await expect(skirtEntries(page)).toHaveCount(0);
|
||||
});
|
||||
|
|
@ -627,10 +637,12 @@ test('door positions and material lengths update skirting immediately', async ({
|
|||
test('invalid skirting settings keep flooring results usable', async ({ page }) => {
|
||||
const floorCount = await floorCodes(page).count();
|
||||
await page.getByLabel('Skirting board length', { exact: true }).fill('100');
|
||||
await page.getByLabel('Skirting board length', { exact: true }).blur();
|
||||
await expect(skirtEntries(page)).toHaveCount(0);
|
||||
await expect(floorCodes(page)).toHaveCount(floorCount);
|
||||
await expect(page.locator('.issue.error').first()).toBeVisible();
|
||||
await page.getByLabel('Skirting board length', { exact: true }).fill('2400');
|
||||
await page.getByLabel('Skirting board length', { exact: true }).blur();
|
||||
expect(await skirtEntries(page).count()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
|
|
@ -665,19 +677,13 @@ test('pattern, direction and spacers persist without steps, layers or stored res
|
|||
page,
|
||||
}) => {
|
||||
await page.getByLabel('Room length', { exact: true }).fill('3650');
|
||||
await page.getByLabel('Room length', { exact: true }).blur();
|
||||
await page.getByRole('button', { name: 'Random', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Across first wall', exact: true }).click();
|
||||
await page.getByLabel('Wall spacer', { exact: true }).fill('12');
|
||||
await page.getByLabel('Wall spacer', { exact: true }).blur();
|
||||
expect(state(page).project.flooring).toMatchObject({ pattern: 'random', gap: 12 });
|
||||
expect(state(page).project.rooms[0].direction).toBe(90);
|
||||
expect(Object.keys(state(page).view).sort()).toEqual([
|
||||
'collapsedSections',
|
||||
'material',
|
||||
'roomId',
|
||||
'search',
|
||||
'selectedPiece',
|
||||
]);
|
||||
expect(Object.keys(state(page)).sort()).toEqual(['project', 'view']);
|
||||
const table = await page.locator('.flooring-cuts').innerText();
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('Wall spacer', { exact: true })).toHaveValue('12');
|
||||
|
|
@ -698,7 +704,7 @@ test('localStorage restores all project and view parameters when reopening witho
|
|||
context,
|
||||
}) => {
|
||||
const fixture = sharedRoomFixture();
|
||||
fixture.project.name = 'Café 北 flooring';
|
||||
fixture.project.rooms[0].name = 'Café 北 flooring';
|
||||
fixture.project.tolerance = 7;
|
||||
fixture.project.flooring = {
|
||||
...fixture.project.flooring,
|
||||
|
|
@ -717,7 +723,8 @@ test('localStorage restores all project and view parameters when reopening witho
|
|||
fixture.project.completedBoards = ['floor:B1', 'skirting:S1'];
|
||||
fixture.view = {
|
||||
roomId: 'room-2',
|
||||
selectedPiece: 'F1',
|
||||
selectedPiece: 'flooring:F1',
|
||||
focus: null,
|
||||
search: 'Study',
|
||||
collapsedSections: ['cuts', 'materials'],
|
||||
material: 'flooring',
|
||||
|
|
@ -739,6 +746,7 @@ test('localStorage restores all project and view parameters when reopening witho
|
|||
await expect(reopened.locator('#materials-content')).toBeHidden();
|
||||
await expect(reopened.locator('#cuts-content')).toBeHidden();
|
||||
await reopened.getByLabel('Room name', { exact: true }).fill('Saved study');
|
||||
await reopened.getByLabel('Room name', { exact: true }).blur();
|
||||
fixture.project.rooms[1].name = 'Saved study';
|
||||
fixture.view.selectedPiece = null;
|
||||
expect(await savedQuery(reopened)).toBe(encodeState(fixture));
|
||||
|
|
@ -752,7 +760,9 @@ test('explicit query parameters override the saved plan and default omitted fiel
|
|||
page,
|
||||
}) => {
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Saved room');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
await page.getByLabel('Wall spacer', { exact: true }).fill('12');
|
||||
await page.getByLabel('Wall spacer', { exact: true }).blur();
|
||||
await page.goto('/?flooring.length=1400');
|
||||
await expect(page.getByLabel('Board length', { exact: true })).toHaveValue('1400');
|
||||
const expected = defaultState();
|
||||
|
|
@ -768,7 +778,7 @@ for (const saved of ['damaged data', '?flooring.length=broken']) {
|
|||
[PROJECT_STORAGE_KEY, saved],
|
||||
);
|
||||
await page.goto('/?planner');
|
||||
await expect(page).toHaveURL(/\?version=2&/);
|
||||
await expect(page).toHaveURL(/\?plan=/);
|
||||
await expect(page.locator('.url-error')).toHaveCount(0);
|
||||
expect(state(page)).toEqual(defaultState());
|
||||
expect(await savedQuery(page)).toBe(encodeState(defaultState()));
|
||||
|
|
@ -793,6 +803,7 @@ for (const failure of ['blocked', 'full']) {
|
|||
await page.goto('/?planner');
|
||||
await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Living room');
|
||||
await page.getByLabel('Room name', { exact: true }).fill('URL still works');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
expect(state(page).project.rooms[0].name).toBe('URL still works');
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('URL still works');
|
||||
|
|
@ -803,6 +814,7 @@ for (const failure of ['blocked', 'full']) {
|
|||
test('a narrow remainder is shared between the first and last flooring rows', async ({ page }) => {
|
||||
// 3650 minus two 10 mm spacers leaves 19 full rows and a 20 mm sliver.
|
||||
await page.getByLabel('Room length', { exact: true }).fill('3650');
|
||||
await page.getByLabel('Room length', { exact: true }).blur();
|
||||
const { floor } = plans(page);
|
||||
for (const row of [1, 20]) {
|
||||
const piece = flooringCuts(floor).find((piece) => piece.row === row);
|
||||
|
|
@ -818,7 +830,9 @@ test('multiple rooms retain independent measurements with combined cut lists', a
|
|||
await page.getByRole('button', { name: 'Add room', exact: true }).click();
|
||||
await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Room 2');
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Study');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
await page.getByLabel('Room width', { exact: true }).fill('3200');
|
||||
await page.getByLabel('Room width', { exact: true }).blur();
|
||||
expect(state(page).project.rooms).toHaveLength(2);
|
||||
expect(state(page).project.rooms[0].walls[0].length).toBe(4800);
|
||||
expect(state(page).project.rooms[1].walls[0].length).toBe(3200);
|
||||
|
|
@ -839,6 +853,7 @@ test('shared URLs reopen the selected preview and copying the link preserves the
|
|||
context,
|
||||
}) => {
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Café 北');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
const { floor } = plans(page);
|
||||
const pieceCode = flooringCuts(floor)[0].id;
|
||||
await page
|
||||
|
|
@ -847,7 +862,7 @@ test('shared URLs reopen the selected preview and copying the link preserves the
|
|||
await expect(
|
||||
page.getByRole('dialog', { name: `Flooring cut ${pieceCode}`, exact: true }),
|
||||
).toBeVisible();
|
||||
expect(state(page).view.selectedPiece).toBe(pieceCode);
|
||||
expect(state(page).view.selectedPiece).toBe(`flooring:${pieceCode}`);
|
||||
const shareUrl = page.url();
|
||||
const originalTable = await page.locator('.flooring-cuts').innerText();
|
||||
const shared = await context.newPage();
|
||||
|
|
@ -905,6 +920,7 @@ test('invalid URLs recover when measurements are edited', async ({ page }) => {
|
|||
await expect(page.getByText(/Could not load this plan/)).toBeVisible();
|
||||
expect(await savedQuery(page)).toBe(saved);
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Recovered plan');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
await expect(page.getByText(/Could not load this plan/)).toHaveCount(0);
|
||||
expect(decodeState(new URL(page.url()).search).error).toBeNull();
|
||||
expect(new URL(page.url()).hash).toBe('');
|
||||
|
|
@ -928,31 +944,32 @@ test('clicking the skirting code itself opens its cutting dialog', async ({ page
|
|||
await expect(
|
||||
page.getByRole('dialog', { name: `Skirting cut ${id}`, exact: true }),
|
||||
).toBeVisible();
|
||||
expect(state(page).view.selectedPiece).toBe(id);
|
||||
expect(state(page).view.selectedPiece).toBe(`skirting:${id}`);
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('readable query fields and browser history preserve the plan', async ({ page }) => {
|
||||
test('the versioned payload and browser history preserve the plan', async ({ page }) => {
|
||||
const initial = defaultState();
|
||||
initial.project.rooms[0].name = 'Current lounge';
|
||||
initial.project.flooring.pattern = 'random';
|
||||
await page.goto(`/${encodeState(initial)}`);
|
||||
await expect(page).toHaveURL(/\?version=2&/);
|
||||
await expect(page).toHaveURL(/\?plan=/);
|
||||
expect(state(page)).toEqual(initial);
|
||||
await expect.poll(() => savedQuery(page)).toBe(encodeState(initial));
|
||||
const original = page.url();
|
||||
await page.getByRole('button', { name: 'Add room', exact: true }).click();
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Study');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
await page.getByLabel('Room width', { exact: true }).fill('3333');
|
||||
await page.getByLabel('Room width', { exact: true }).blur();
|
||||
const twoRooms = page.url();
|
||||
const params = new URL(twoRooms).searchParams;
|
||||
expect(params.get('room.2.name')).toBe('Study');
|
||||
expect(params.get('room.2.wall.1.length')).toBe('3333');
|
||||
expect(params.get('flooring.pattern')).toBe('random');
|
||||
expect(params.has('plan')).toBe(false);
|
||||
expect([...params.keys()].some((key) => /boardCount|polygon|pieceIds/.test(key))).toBe(false);
|
||||
expect(state(page).project.rooms[1]).toMatchObject({
|
||||
name: 'Study',
|
||||
walls: [{ length: 3333 }, {}, {}, {}],
|
||||
});
|
||||
expect(state(page).project.flooring.pattern).toBe('random');
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL(original);
|
||||
await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Current lounge');
|
||||
|
|
@ -963,8 +980,9 @@ test('readable query fields and browser history preserve the plan', async ({ pag
|
|||
await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Study');
|
||||
await expect(page.getByLabel('Room width', { exact: true })).toHaveValue('3333');
|
||||
expect(await savedQuery(page)).toBe(new URL(twoRooms).search);
|
||||
params.set('flooring.width', '205');
|
||||
await page.goto(`/?${params}`);
|
||||
const edited = state(page);
|
||||
edited.project.flooring.width = 205;
|
||||
await page.goto(`/${encodeState(edited)}`);
|
||||
await expect(page.getByLabel('Board width', { exact: true })).toHaveValue('205');
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('Board width', { exact: true })).toHaveValue('205');
|
||||
|
|
@ -992,6 +1010,7 @@ test('printing includes every room and every cut once, grouped by its source boa
|
|||
).toBe(true);
|
||||
await expect(page.locator('.print-plan')).toBeHidden();
|
||||
await page.emulateMedia({ media: 'print' });
|
||||
await expect(page.locator('.print-plan')).toBeVisible();
|
||||
const printed = page.locator('.print-plan');
|
||||
await expect(printed).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Room measurements' })).toBeHidden();
|
||||
|
|
@ -1043,8 +1062,7 @@ test('panels collapse independently and the room stays pinned while reading cuts
|
|||
await panel(page, section).locator('.panel-toggle').click();
|
||||
await expect(page.locator(`#${section}-content`)).toBeHidden();
|
||||
}
|
||||
expect(state(page).view.collapsedSections).toEqual(['cuts', 'materials', 'measurements']);
|
||||
expect(new URL(page.url()).searchParams.getAll('collapsed')).toEqual([
|
||||
expect(state(page).view.collapsedSections.slice().sort()).toEqual([
|
||||
'cuts',
|
||||
'materials',
|
||||
'measurements',
|
||||
|
|
@ -1083,7 +1101,7 @@ test('drawing cuts still open a usable preview when the cut plan is collapsed',
|
|||
const dialog = page.getByRole('dialog', { name: 'Flooring cut F1', exact: true });
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.locator('.cut-dimensions')).toBeVisible();
|
||||
expect(state(page).view.selectedPiece).toBe('F1');
|
||||
expect(state(page).view.selectedPiece).toBe('flooring:F1');
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(page.locator('#cuts-content')).toBeHidden();
|
||||
|
|
@ -1100,7 +1118,7 @@ test('negative angles can be backspaced and retyped without saving unfinished dr
|
|||
await turn.press('End');
|
||||
await turn.press('Backspace');
|
||||
await expect(turn).toHaveValue('-9');
|
||||
expect(state(page).project.rooms[0].walls[3].turn).toBe(-9);
|
||||
expect(state(page).project.rooms[0].walls[3].turn).toBe(-90);
|
||||
const committed = page.url();
|
||||
await turn.press('Backspace');
|
||||
await expect(turn).toHaveValue('-');
|
||||
|
|
@ -1108,7 +1126,15 @@ test('negative angles can be backspaced and retyped without saving unfinished dr
|
|||
await turn.press('Backspace');
|
||||
await expect(turn).toHaveValue('');
|
||||
expect(page.url()).toBe(committed);
|
||||
await turn.pressSequentially('-90');
|
||||
await turn.pressSequentially('-45');
|
||||
expect(page.url()).toBe(committed);
|
||||
await turn.press('Escape');
|
||||
await expect(turn).toHaveValue('-90');
|
||||
await turn.fill('-80');
|
||||
await turn.press('Enter');
|
||||
expect(state(page).project.rooms[0].walls[3].turn).toBe(-80);
|
||||
await turn.fill('-90');
|
||||
await turn.blur();
|
||||
await expect(turn).toHaveValue('-90');
|
||||
expect(state(page).project.rooms[0].walls[3].turn).toBe(-90);
|
||||
expect(await preview(page).locator('.floor-piece').count()).toBeGreaterThan(0);
|
||||
|
|
@ -1390,14 +1416,17 @@ test('completed source boards and locate actions follow shared offcuts across ro
|
|||
await done.check();
|
||||
await expect(group).toHaveClass(/is-done/);
|
||||
expect(state(page).project.completedBoards).toEqual([key]);
|
||||
expect(new URL(page.url()).searchParams.getAll('done')).toEqual([key]);
|
||||
await group.getByRole('button', { name: `Show cuts ${groupCodes} on plan`, exact: true }).click();
|
||||
expect(state(page).view.search).toBe(key);
|
||||
expect(state(page).view.focus).toEqual({ kind: 'board', material: 'flooring', id: shared.id });
|
||||
expect(state(page).view.search).toBe('');
|
||||
const search = page.getByLabel('Find a cut or room', { exact: true });
|
||||
await expect(search).toHaveValue(groupCodes);
|
||||
await expect(search).toHaveValue('');
|
||||
await expect(page.locator('.plan-search-status')).toContainText(groupCodes);
|
||||
await noVisibleStockIds(page);
|
||||
await search.fill('');
|
||||
await search.blur();
|
||||
await search.fill(groupCodes);
|
||||
await search.blur();
|
||||
expect(state(page).view.search).toBe(groupCodes);
|
||||
await noVisibleStockIds(page);
|
||||
expect(await highlightedIds(page, 'flooring')).toEqual(
|
||||
|
|
@ -1425,19 +1454,21 @@ test('completed source boards and locate actions follow shared offcuts across ro
|
|||
const piece = sourcePieces.find((piece) => piece.roomId === 'room-2')!;
|
||||
await page.getByRole('button', { name: `Show cut ${piece.id} on plan`, exact: true }).click();
|
||||
expect(state(page).view.roomId).toBe('room-2');
|
||||
expect(state(page).view.search).toBe(piece.id);
|
||||
expect(state(page).view.focus).toEqual({ kind: 'piece', material: 'flooring', id: piece.id });
|
||||
expect(await highlightedIds(page)).toEqual([piece.id]);
|
||||
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||
await expect(group.locator('.board-cut')).toHaveCount(sourcePieces.length);
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('Find a cut or room', { exact: true })).toHaveValue(piece.id);
|
||||
await expect(page.getByLabel('Find a cut or room', { exact: true })).toHaveValue('');
|
||||
await expect(page.locator('.plan-search-status')).toContainText(piece.id);
|
||||
await expect(done).toBeChecked();
|
||||
expect(await highlightedIds(page)).toEqual([piece.id]);
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Office');
|
||||
await page.getByLabel('Room name', { exact: true }).blur();
|
||||
expect(state(page).project.completedBoards).toEqual([key]);
|
||||
await page.locator('.room-tab').first().click();
|
||||
expect(state(page).project.completedBoards).toEqual([key]);
|
||||
await page.getByLabel('Find a cut or room', { exact: true }).fill('');
|
||||
await page.getByRole('button', { name: 'Show all pieces', exact: true }).click();
|
||||
const skirtPiece = skirtingCuts(skirt).find((piece) => piece.roomId === 'room-1')!;
|
||||
const skirtKey = `skirting:${skirtPiece.stockId}`;
|
||||
await page
|
||||
|
|
@ -1451,9 +1482,9 @@ test('completed source boards and locate actions follow shared offcuts across ro
|
|||
.click();
|
||||
expect(await preview(page).locator('.skirt-piece[data-done="true"]').count()).toBeGreaterThan(0);
|
||||
await page.getByLabel('Board width', { exact: true }).fill('201');
|
||||
await page.getByLabel('Board width', { exact: true }).blur();
|
||||
expect(state(page).project.completedBoards).toEqual([]);
|
||||
await expect(preview(page).locator('[data-done="true"]')).toHaveCount(0);
|
||||
expect(new URL(page.url()).searchParams.getAll('done')).toEqual([]);
|
||||
});
|
||||
|
||||
test('the pinned plan makes active search obvious and can restore every piece', async ({
|
||||
|
|
@ -1461,6 +1492,7 @@ test('the pinned plan makes active search obvious and can restore every piece',
|
|||
}) => {
|
||||
const allCuts = await floorEntries(page).count();
|
||||
await page.getByLabel('Find a cut or room', { exact: true }).fill('F1');
|
||||
await page.getByLabel('Find a cut or room', { exact: true }).blur();
|
||||
const banner = page.locator('.drawing-column .plan-search-status');
|
||||
await expect(banner).toContainText('Search active');
|
||||
await expect(banner).toContainText('F1');
|
||||
|
|
@ -1508,18 +1540,17 @@ test('cuts from one board share the available width and wrap on a narrow phone',
|
|||
await noVisibleStockIds(page);
|
||||
});
|
||||
|
||||
test('search can locate and complete a whole board without inventing a cut code', async ({
|
||||
page,
|
||||
}) => {
|
||||
test('locate and complete a whole board without inventing a cut code', async ({ page }) => {
|
||||
const { floor } = plans(page);
|
||||
const whole = floor.pieces.find((piece) => !piece.cut && !piece.reused)!;
|
||||
expect(whole).toBeDefined();
|
||||
const search = page.getByLabel('Find a cut or room', { exact: true });
|
||||
await search.fill(`floor:${whole.stockId}`);
|
||||
await search.fill('Living room');
|
||||
const group = page.locator(`.stock-cut-group[data-board-key="floor:${whole.stockId}"]`);
|
||||
await expect(group).toContainText('Installed uncut');
|
||||
await group.getByRole('button', { name: 'Show uncut floorboard on plan', exact: true }).click();
|
||||
await expect(search).toHaveValue('Uncut floorboard');
|
||||
await expect(search).toHaveValue('');
|
||||
await expect(page.locator('.plan-search-status')).toContainText('Uncut floorboard');
|
||||
await noVisibleStockIds(page);
|
||||
await expect(group.locator('.board-cut')).toHaveCount(0);
|
||||
await expect(group.getByRole('button', { name: /^Preview/ })).toHaveCount(0);
|
||||
|
|
@ -1533,6 +1564,7 @@ test('search can locate and complete a whole board without inventing a cut code'
|
|||
'true',
|
||||
);
|
||||
await search.fill('nothing matches this room');
|
||||
await search.blur();
|
||||
await expect(page.locator('.search-results')).toHaveText('No matching boards or cuts.');
|
||||
expect(await highlightedIds(page)).toEqual([]);
|
||||
expect(state(page).project.completedBoards).toEqual([`floor:${whole.stockId}`]);
|
||||
|
|
@ -1609,3 +1641,45 @@ for (const width of [320, 390]) {
|
|||
await noHorizontalOverflow(page);
|
||||
});
|
||||
}
|
||||
|
||||
test('material-qualified selections disambiguate flooring and sixth-room skirting F1', async ({
|
||||
page,
|
||||
}) => {
|
||||
const fixture = defaultState();
|
||||
fixture.project.rooms = Array.from({ length: 6 }, (_, i) =>
|
||||
rectangle(`room-${i + 1}`, `Room ${i + 1}`, 2400, 2400),
|
||||
);
|
||||
fixture.view.roomId = 'room-6';
|
||||
fixture.view.material = 'skirting';
|
||||
await page.goto(`/${encodeState(fixture)}`);
|
||||
await preview(page).getByRole('button', { name: 'Select skirting cut F1', exact: true }).click();
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toHaveAccessibleName('Skirting cut F1');
|
||||
await expect(dialog).toContainText('Room 6');
|
||||
await expect(dialog.locator('.flooring-preview')).toHaveCount(0);
|
||||
expect(state(page).view.selectedPiece).toBe('skirting:F1');
|
||||
await page.reload();
|
||||
await expect(dialog).toHaveAccessibleName('Skirting cut F1');
|
||||
await page.keyboard.press('Escape');
|
||||
await page.getByRole('button', { name: 'Preview flooring cut F1', exact: true }).click();
|
||||
await expect(dialog).toHaveAccessibleName('Flooring cut F1');
|
||||
await expect(dialog).toContainText('Room 1');
|
||||
await expect(dialog.locator('.skirting-full-preview')).toHaveCount(0);
|
||||
expect(state(page).view.selectedPiece).toBe('flooring:F1');
|
||||
});
|
||||
|
||||
test('native print events build current sheets synchronously and release them afterward', async ({
|
||||
page,
|
||||
}) => {
|
||||
await expect(page.locator('.print-plan')).toHaveCount(0);
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Print this room');
|
||||
const printed = await page.evaluate(() => {
|
||||
window.dispatchEvent(new Event('beforeprint'));
|
||||
return document.querySelector('.print-plan')?.textContent;
|
||||
});
|
||||
expect(printed).toContain('Print this room');
|
||||
expect(printed).toContain('Flooring cuts');
|
||||
expect(printed).toContain('Skirting cuts');
|
||||
await page.evaluate(() => window.dispatchEvent(new Event('afterprint')));
|
||||
await expect(page.locator('.print-plan')).toHaveCount(0);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue