This commit is contained in:
parent
7959fe85cc
commit
b9485e4097
32 changed files with 4692 additions and 65 deletions
|
|
@ -3,6 +3,7 @@ import type { Locator, Page } from '@playwright/test';
|
|||
import LZString from 'lz-string';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { decodeState, encodeState } from '../src/lib/url-state';
|
||||
import { PROJECT_STORAGE_KEY } from '../src/lib/use-project';
|
||||
import { defaultState, fireplace, rectangle } from '../src/lib/defaults';
|
||||
import { planFlooring } from '../src/lib/flooring';
|
||||
import { planSkirting } from '../src/lib/skirting';
|
||||
|
|
@ -23,6 +24,8 @@ const state = (page: Page) => {
|
|||
const url = new URL(page.url());
|
||||
return decodeState(url.search || url.hash).state;
|
||||
};
|
||||
const savedQuery = (page: Page) =>
|
||||
page.evaluate((key) => localStorage.getItem(key), PROJECT_STORAGE_KEY);
|
||||
const plans = (page: Page) => {
|
||||
const { project } = state(page);
|
||||
return {
|
||||
|
|
@ -37,7 +40,8 @@ function sharedRoomFixture() {
|
|||
fixture.project.flooring.width = 200;
|
||||
fixture.project.rooms = [
|
||||
rectangle('room-1', 'Living room', 1820, 1220),
|
||||
rectangle('room-2', 'Study', 1220, 1820),
|
||||
// Its end pieces complement the first room's offcuts after saw kerf.
|
||||
rectangle('room-2', 'Study', 1220, 1814),
|
||||
];
|
||||
fixture.project.rooms.forEach((room) =>
|
||||
room.walls.forEach((wall) => {
|
||||
|
|
@ -259,7 +263,7 @@ async function noWallLabelOverlap(page: Page) {
|
|||
test.beforeEach(async ({ page }) => {
|
||||
errors.set(page, []);
|
||||
page.on('pageerror', (error) => errors.get(page)!.push(error.message));
|
||||
await page.goto('/');
|
||||
await page.goto('/?planner');
|
||||
await expect(page.getByRole('heading', { name: 'Room measurements' })).toBeVisible();
|
||||
await expect(page).toHaveURL(/\?version=1&/);
|
||||
});
|
||||
|
|
@ -679,9 +683,115 @@ test('pattern, direction and spacers persist without steps, layers or stored res
|
|||
).toHaveAttribute('aria-pressed', 'true');
|
||||
await expect(page.locator('.flooring-cuts')).toHaveText(table, { useInnerText: true });
|
||||
await noHorizontalOverflow(page);
|
||||
expect(await page.evaluate(() => Object.keys(localStorage))).toEqual([]);
|
||||
expect(await savedQuery(page)).toBe(new URL(page.url()).search);
|
||||
});
|
||||
|
||||
test('localStorage restores all project and view parameters when reopening without a link', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
const fixture = sharedRoomFixture();
|
||||
fixture.project.name = 'Café 北 flooring';
|
||||
fixture.project.tolerance = 7;
|
||||
fixture.project.flooring = {
|
||||
...fixture.project.flooring,
|
||||
length: 1300,
|
||||
width: 180,
|
||||
gap: 12,
|
||||
pattern: 'random',
|
||||
minOffset: 250,
|
||||
maxOffset: 600,
|
||||
minLength: 180,
|
||||
minWidth: 50,
|
||||
kerf: 2.5,
|
||||
};
|
||||
fixture.project.skirting = { stockLength: 3600, thickness: 20, minLength: 250, kerf: 2 };
|
||||
fixture.project.rooms[1].walls[0].doors = [{ id: 'study-door', offset: 300, width: 850 }];
|
||||
fixture.project.completedBoards = ['floor:B1', 'skirting:S1'];
|
||||
fixture.view = {
|
||||
roomId: 'room-2',
|
||||
selectedPiece: 'F1',
|
||||
search: 'Study',
|
||||
collapsedSections: ['cuts', 'materials'],
|
||||
};
|
||||
const query = encodeState(fixture);
|
||||
await page.goto(`/${query}`);
|
||||
await expect(page.getByRole('dialog', { name: 'Flooring cut F1', exact: true })).toBeVisible();
|
||||
expect(await savedQuery(page)).toBe(query);
|
||||
|
||||
const reopened = await context.newPage();
|
||||
await reopened.goto('/?planner');
|
||||
await expect(reopened).toHaveURL(page.url());
|
||||
expect(state(reopened)).toEqual(fixture);
|
||||
await expect(
|
||||
reopened.getByRole('dialog', { name: 'Flooring cut F1', exact: true }),
|
||||
).toBeVisible();
|
||||
await reopened.keyboard.press('Escape');
|
||||
await expect(reopened.getByLabel('Room name', { exact: true })).toHaveValue('Study');
|
||||
await expect(reopened.locator('#materials-content')).toBeHidden();
|
||||
await expect(reopened.locator('#cuts-content')).toBeHidden();
|
||||
await reopened.getByLabel('Room name', { exact: true }).fill('Saved study');
|
||||
fixture.project.rooms[1].name = 'Saved study';
|
||||
fixture.view.selectedPiece = null;
|
||||
expect(await savedQuery(reopened)).toBe(encodeState(fixture));
|
||||
await reopened.goto('/?planner');
|
||||
await expect(reopened.getByLabel('Room name', { exact: true })).toHaveValue('Saved study');
|
||||
expect(state(reopened)).toEqual(fixture);
|
||||
await reopened.close();
|
||||
});
|
||||
|
||||
test('explicit query parameters override the saved plan and default omitted fields', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Saved room');
|
||||
await page.getByLabel('Wall spacer', { exact: true }).fill('12');
|
||||
await page.goto('/?flooring.length=1400');
|
||||
await expect(page.getByLabel('Board length', { exact: true })).toHaveValue('1400');
|
||||
const expected = defaultState();
|
||||
expected.project.flooring.length = 1400;
|
||||
expect(state(page)).toEqual(expected);
|
||||
expect(await savedQuery(page)).toBe(encodeState(expected));
|
||||
});
|
||||
|
||||
for (const saved of ['damaged data', '?flooring.length=broken']) {
|
||||
test(`invalid localStorage falls back to defaults: ${saved}`, async ({ page }) => {
|
||||
await page.evaluate(
|
||||
([key, value]) => localStorage.setItem(key, value),
|
||||
[PROJECT_STORAGE_KEY, saved],
|
||||
);
|
||||
await page.goto('/?planner');
|
||||
await expect(page).toHaveURL(/\?version=1&/);
|
||||
await expect(page.locator('.url-error')).toHaveCount(0);
|
||||
expect(state(page)).toEqual(defaultState());
|
||||
expect(await savedQuery(page)).toBe(encodeState(defaultState()));
|
||||
});
|
||||
}
|
||||
|
||||
for (const failure of ['blocked', 'full']) {
|
||||
test(`URL persistence still works when localStorage is ${failure}`, async ({ page }) => {
|
||||
await page.addInitScript((failure) => {
|
||||
if (failure === 'blocked') {
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
get() {
|
||||
throw new DOMException('Storage blocked', 'SecurityError');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
Storage.prototype.setItem = () => {
|
||||
throw new DOMException('Storage full', 'QuotaExceededError');
|
||||
};
|
||||
}
|
||||
}, failure);
|
||||
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');
|
||||
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');
|
||||
await expect(page.locator('.url-error')).toHaveCount(0);
|
||||
});
|
||||
}
|
||||
|
||||
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');
|
||||
|
|
@ -780,12 +890,15 @@ test('saw kerf help works with keyboard and touch or mouse', async ({ page, isMo
|
|||
});
|
||||
|
||||
test('invalid URLs recover when measurements are edited', async ({ page }) => {
|
||||
const saved = await savedQuery(page);
|
||||
await page.goto('/?flooring.length=broken');
|
||||
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 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('');
|
||||
expect(await savedQuery(page)).toBe(new URL(page.url()).search);
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Recovered plan');
|
||||
});
|
||||
|
|
@ -818,6 +931,7 @@ test('readable query fields, legacy links and browser history preserve the plan'
|
|||
await expect(page).toHaveURL(/\?version=1&/);
|
||||
expect(new URL(page.url()).hash).toBe('');
|
||||
expect(state(page)).toEqual(legacy);
|
||||
expect(await savedQuery(page)).toBe(encodeState(legacy));
|
||||
const original = page.url();
|
||||
await page.getByRole('button', { name: 'Add room', exact: true }).click();
|
||||
await page.getByLabel('Room name', { exact: true }).fill('Study');
|
||||
|
|
@ -833,10 +947,12 @@ test('readable query fields, legacy links and browser history preserve the plan'
|
|||
await expect(page).toHaveURL(original);
|
||||
await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Legacy lounge');
|
||||
expect(state(page).project.rooms).toHaveLength(1);
|
||||
expect(await savedQuery(page)).toBe(new URL(original).search);
|
||||
await page.goForward();
|
||||
await expect(page).toHaveURL(twoRooms);
|
||||
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}`);
|
||||
await expect(page.getByLabel('Board width', { exact: true })).toHaveValue('205');
|
||||
|
|
|
|||
Loading…
Reference in a new issue