feat(editor): a tap opens a device's menu in the simulation; undo and redo drawn

In the simulation a tap on a device chooses it and opens its menu where
the finger was; the three-dot button is gone there, the long press stays.
The context menu moves in from the surface's edge instead of being cut off.
Undo and redo show drawn arrows: the characters took whatever font the
device had and looked squashed on an iPad (the buttons measured 44 x 44 at
every width; the size was not the cause).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 08:04:57 +00:00
co-authored by Claude Opus 5.5
parent ae10ee6c31
commit 15498a2bed
8 changed files with 108 additions and 42 deletions
+35 -16
View File
@@ -208,51 +208,70 @@ test("shows a card's frames as Java's data exchange window does, with their deta
await expect(window).toHaveCount(0);
});
test('chooses a device with a tap in the simulation, its three-dot button as in the design, and opens nothing by itself', async ({ page }) => {
test('opens a device\'s menu with a tap in the simulation, chooses it, and opens nothing else by itself', async ({ page }) => {
await openPages(page);
await start(page);
const button = page.locator('.device-menu-button');
const menu = page.getByRole('menu');
const sheet = page.locator('aside[aria-label="Einstellungen"]');
// A router: the button opens the same menu as the long press.
// A router: the tap chooses it and opens the same menu as the long press, where the finger was; no three-dot button.
const router = await onDevice(page, await idOf(page, 'Router'));
await page.mouse.click(router.x, router.y);
await expect(page.locator(`g.device[data-node="${await idOf(page, 'Router')}"]`)).toHaveAttribute('data-selected', 'true');
await button.click();
await expect(page.getByRole('menuitem')).toHaveText(['Datenaustausch anzeigen (192.168.0.1)', 'Datenaustausch anzeigen (10.0.0.1)']);
const opened = (await menu.boundingBox())!;
expect([opened.x, opened.y]).toEqual([router.x, router.y]);
await expect(page.locator('.device-menu-button')).toHaveCount(0);
await page.getByRole('menuitem', { name: 'Datenaustausch anzeigen (10.0.0.1)' }).click();
await expect(page.locator('section[data-capture]').getByRole('tab', { selected: true })).toHaveText('Router - 10.0.0.1');
await page.locator('section[data-capture] .simulation-sheet__close').click();
// A computer: the tap opens no desktop (Java's click does), it only chooses; its settings stay closed, the design is locked.
// A computer: the tap opens its menu, no desktop by itself (Java's click does); its settings stay closed, the design is locked.
const browser = await idOf(page, 'Browser');
const at = await onDevice(page, browser);
await page.mouse.click(at.x, at.y);
await expect(button).toBeVisible();
await expect(page.getByRole('menuitem')).toHaveText(['Desktop anzeigen', 'Datenaustausch anzeigen (192.168.0.10)']);
await expect(page.locator('section[data-desktop]')).toHaveCount(0);
await expect(sheet).toHaveCount(0);
await button.click();
await expect(page.getByRole('menuitem')).toHaveText(['Desktop anzeigen', 'Datenaustausch anzeigen (192.168.0.10)']);
// A tap on another device beside the open menu: that one's menu (the menu lies over what is below it on the right).
await page.mouse.click(router.x, router.y);
await expect(page.getByRole('menuitem')).toHaveText(['Datenaustausch anzeigen (192.168.0.1)', 'Datenaustausch anzeigen (10.0.0.1)']);
await page.keyboard.press('Escape');
// A switch: the tap opens no table either; its menu does.
const lanSwitch = (await drawing(page)).nodes.find((node) => node.kind === 'networkSwitch')!.id;
const hub = await onDevice(page, lanSwitch);
await page.mouse.click(hub.x, hub.y);
await expect(page.locator(`g.device[data-node="${lanSwitch}"]`)).toHaveAttribute('data-selected', 'true');
await expect(page.locator('section[data-viewer]')).toHaveCount(0);
await button.click();
await expect(page.getByRole('menuitem')).toHaveText(['SAT Tabelle anzeigen']);
await expect(page.locator('section[data-viewer]')).toHaveCount(0);
await page.getByRole('menuitem', { name: 'SAT Tabelle anzeigen' }).click();
await expect(page.locator(`section[data-viewer="${lanSwitch}"]`)).toBeVisible();
await page.locator('.simulation-sheet__close').click();
// A tap beside lets go.
// A tap beside closes the menu and lets go.
await page.mouse.click(at.x, at.y);
await expect(button).toBeVisible();
await expect(menu).toBeVisible();
const surface = (await page.locator('svg.filius-canvas').boundingBox())!;
await page.mouse.click(surface.x + surface.width - 30, surface.y + surface.height - 80);
await expect(button).toHaveCount(0);
// Back in the design nothing stays chosen.
await expect(menu).toHaveCount(0);
await expect(page.locator(`g.device[data-node="${browser}"]`)).not.toHaveAttribute('data-selected', 'true');
// A device at the surface's edge: its menu moves in, whole on the surface.
await page.mouse.click(at.x, at.y);
await page.keyboard.press('Escape');
const area = (await page.locator('.editor-area').boundingBox())!;
// The wheel moves the surface (a drag on a device in the simulation moves nothing).
await page.mouse.move(at.x, at.y);
await page.mouse.wheel(-(area.x + area.width - 12 - at.x), -(area.y + area.height - 12 - at.y));
await expect.poll(async () => (await onDevice(page, browser)).x).toBeGreaterThan(area.x + area.width - 40);
const edge = await onDevice(page, browser);
await page.mouse.click(edge.x, edge.y);
const moved = (await menu.boundingBox())!;
expect(moved.x + moved.width).toBeLessThanOrEqual(area.x + area.width + 0.5);
expect(moved.y + moved.height).toBeLessThanOrEqual(area.y + area.height + 0.5);
expect(moved.x).toBeLessThan(edge.x);
expect(moved.y).toBeLessThan(edge.y);
await page.keyboard.press('Escape');
// Back in the design nothing stays chosen.
await page.mouse.click(edge.x, edge.y);
await page.keyboard.press('Escape');
await page.getByRole('button', { name: 'Wechselt in den Entwurfsmodus' }).click();
await expect(button).toHaveCount(0);
await expect(page.locator('.device-menu-button')).toHaveCount(0);
await expect(sheet).toHaveCount(0);
});