feat(editor): the left bar folds away, and the device sheet takes the whole editor on a phone
A button at the top of the palette and the documentation's bar folds it to a slim column, as the dashboard's own bar does; one state for both, kept in the browser. Up to 639 px the device sheet covers the whole editor, over the bar and the mode buttons, instead of sharing half the height below the surface. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -245,3 +245,43 @@ test('by touch: a long press opens the menu, a finger drags a device', async ({
|
||||
await expect(page.getByRole('menu')).toBeHidden();
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test('folds the palette to a slim column with its button in the same place, kept for the next visit and for the documentation', async ({ page }) => {
|
||||
await openEditor(page, server.base, 'new');
|
||||
const palette = page.getByRole('navigation', { name: 'Komponenten' });
|
||||
const fold = palette.getByRole('button', { name: 'Leiste einklappen' });
|
||||
const before = { fold: (await fold.boundingBox())!, bar: (await palette.boundingBox())!, surface: (await page.locator('svg.filius-canvas').boundingBox())! };
|
||||
// The button stands at the top, over the cable.
|
||||
expect(before.fold.y + before.fold.height).toBeLessThanOrEqual((await paletteCentre(page, 'cable')).y);
|
||||
await expect(fold).toHaveAttribute('aria-expanded', 'true');
|
||||
await fold.click();
|
||||
|
||||
const unfold = palette.getByRole('button', { name: 'Leiste ausklappen' });
|
||||
await expect(unfold).toHaveAttribute('aria-expanded', 'false');
|
||||
await expect(palette.locator('[data-palette]')).toHaveCount(0);
|
||||
const folded = { fold: (await unfold.boundingBox())!, bar: (await palette.boundingBox())!, surface: (await page.locator('svg.filius-canvas').boundingBox())! };
|
||||
expect([folded.fold.x, folded.fold.y, folded.fold.width, folded.fold.height]).toEqual([before.fold.x, before.fold.y, 44, 44]);
|
||||
expect(folded.bar.width).toBe(57);
|
||||
expect(folded.surface.x).toBe(before.surface.x - (before.bar.width - 57));
|
||||
expect(folded.surface.width).toBe(before.surface.width + (before.bar.width - 57));
|
||||
|
||||
// Folded on the next visit, and the documentation's bar with it; unfolded there, the palette is back.
|
||||
await openEditor(page, server.base, 'new');
|
||||
await expect(palette.getByRole('button', { name: 'Leiste ausklappen' })).toBeVisible();
|
||||
await expect(palette.locator('[data-palette]')).toHaveCount(0);
|
||||
await page.getByRole('button', { name: 'Wechselt in den Dokumentationsmodus' }).click();
|
||||
const sidebar = page.getByRole('navigation', { name: 'Dokumentation' });
|
||||
await expect(sidebar.locator('[data-docu]')).toHaveCount(0);
|
||||
await sidebar.getByRole('button', { name: 'Leiste ausklappen' }).click();
|
||||
await expect(sidebar.locator('[data-docu]')).toHaveCount(2);
|
||||
await page.getByRole('button', { name: 'Wechselt in den Entwurfsmodus' }).click();
|
||||
await expect(palette.locator('[data-palette]')).toHaveCount(7);
|
||||
expect((await palette.boundingBox())!.width).toBe(before.bar.width);
|
||||
});
|
||||
|
||||
test('names the fold button in English when asked', async ({ page }) => {
|
||||
await openEditor(page, server.base, 'new', '&lang=en');
|
||||
const palette = page.getByRole('navigation', { name: 'Components' });
|
||||
await palette.getByRole('button', { name: 'Collapse the bar' }).click();
|
||||
await expect(palette.getByRole('button', { name: 'Expand the bar' })).toHaveAttribute('title', 'Expand the bar');
|
||||
});
|
||||
|
||||
@@ -54,7 +54,8 @@ test('makes, moves, resizes, colours and types documentation as Java does, and p
|
||||
await expect(canvas).toHaveAttribute('data-mode', 'documentation');
|
||||
await expect(page.getByRole('navigation', { name: 'Komponenten' })).toHaveCount(0);
|
||||
const sidebar = page.getByRole('navigation', { name: 'Dokumentation' });
|
||||
await expect(sidebar.getByRole('button')).toHaveText(['Text', 'Gliederungsfläche', 'Exportieren', 'Report']);
|
||||
await expect(sidebar.getByRole('button', { name: 'Leiste einklappen' })).toBeVisible();
|
||||
await expect(sidebar.locator('.side-bar__items').getByRole('button')).toHaveText(['Text', 'Gliederungsfläche', 'Exportieren', 'Report']);
|
||||
|
||||
// A rectangle dragged onto the surface: 100 × 50, its middle where it was dropped, grey, with the focus.
|
||||
const surface = (await canvas.boundingBox())!;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// The device sheet in Chromium (Playwright): it opens with the device that
|
||||
// is chosen, shows Java's fields, takes a field when it is left, marks and
|
||||
// keeps a text its rule refuses, moves a notebook into a wireless network —
|
||||
// beside the surface on a wide screen, below it on a phone.
|
||||
// beside the surface on a wide screen, over the whole editor on a phone.
|
||||
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
import type { HomeRouterSheet, HostSheet, RouterSheet } from '@mdg-filius/doc';
|
||||
@@ -561,10 +561,14 @@ test("stacks the DHCP server's segments exactly where a segment's longest word,
|
||||
test.describe('on a phone', () => {
|
||||
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true });
|
||||
|
||||
test("stacks the DHCP server's segments where a word no longer fits its share — still one control", async ({ page }) => {
|
||||
test("sets the DHCP server's segments side by side in the sheet that covers the phone, and stacks them on a small one — still one control", async ({ page }) => {
|
||||
await openEditor(page, server.base, 'new');
|
||||
const { control, boxes } = await dhcpSegments(page);
|
||||
const { control } = await dhcpSegments(page);
|
||||
await expect(control).not.toHaveAttribute('data-stacked', 'true');
|
||||
// 320 px: the share of „Grundeinstellungen" falls below the word.
|
||||
await page.setViewportSize({ width: 320, height: 700 });
|
||||
await expect(control).toHaveAttribute('data-stacked', 'true');
|
||||
const boxes = await Promise.all([0, 1].map(async (index) => (await control.getByRole('tab').nth(index).boundingBox())!));
|
||||
await expect(control).toHaveAttribute('aria-orientation', 'vertical');
|
||||
const [basic, assignments] = boxes as [{ x: number; y: number; width: number; height: number }, { x: number; y: number; width: number; height: number }];
|
||||
expect(Math.round(basic.x)).toBe(Math.round(assignments.x));
|
||||
@@ -574,22 +578,38 @@ test.describe('on a phone', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('opens below the surface with a tap, the device still in sight', async ({ page }) => {
|
||||
test('covers the whole editor with a tap, the bar and the mode buttons too, and gives the surface back when closed', async ({ page }) => {
|
||||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||||
const at = await onDevice(page, id);
|
||||
await page.touchscreen.tap(at.x, at.y);
|
||||
await expect(sheet(page)).toBeVisible();
|
||||
const surface = (await page.locator('svg.filius-canvas').boundingBox())!;
|
||||
const aside = (await sheet(page).boundingBox())!;
|
||||
expect(aside.y).toBeGreaterThanOrEqual(surface.y + surface.height - 1);
|
||||
expect(aside.height).toBeLessThanOrEqual(844 / 2);
|
||||
expect(surface.height).toBeGreaterThan(200);
|
||||
expect(await sheet(page).boundingBox()).toEqual(await page.locator('.editor-root').boundingBox());
|
||||
// What lies under it does not take the finger: the palette, the mode buttons.
|
||||
for (const under of [page.locator('[data-palette="pc"]'), page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' })]) {
|
||||
const box = (await under.boundingBox())!;
|
||||
const top = await page.evaluate(([x, y]) => Boolean(document.elementFromPoint(x!, y!)?.closest('.device-sheet')), [box.x + box.width / 2, box.y + box.height / 2]);
|
||||
expect(top).toBe(true);
|
||||
}
|
||||
const fieldBox = (await field(page, 'ipAddress').boundingBox())!;
|
||||
expect(fieldBox.height).toBeGreaterThanOrEqual(44);
|
||||
await sheet(page).getByRole('button', { name: 'Schließen' }).tap();
|
||||
await expect(sheet(page)).toHaveCount(0);
|
||||
await expect(page.locator('svg.filius-canvas')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('covers the editor from the phone width down, 639 px, and stands beside the surface from 640', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 640, height: 800 });
|
||||
await openEditor(page, server.base, 'new');
|
||||
await page.locator('[data-palette="pc"]').click();
|
||||
await expect(sheet(page)).toBeVisible();
|
||||
const surface = (await page.locator('svg.filius-canvas').boundingBox())!;
|
||||
expect((await sheet(page).boundingBox())!.x).toBeGreaterThanOrEqual(surface.x + surface.width - 1);
|
||||
await page.setViewportSize({ width: 639, height: 800 });
|
||||
await expect.poll(async () => (await sheet(page).boundingBox())!.width).toBe(639);
|
||||
});
|
||||
|
||||
test('speaks English when asked', async ({ page }) => {
|
||||
await openEditor(page, server.base, 'new', '&lang=en');
|
||||
await page.locator('[data-palette="networkSwitch"]').click();
|
||||
|
||||
Reference in New Issue
Block a user