feat(editor): the simulation's properties in a sheet of their own, with a restart for everyone

Who started the simulation, "Pakete verwerfen" and the dropped packets move
from the toolbar into a properties sheet beside the surface. New in it: restart
the simulation, after asking. The room learns a restart message: stopped as by
stop, every capture, dropped frame and program window emptied, started anew
and named after who restarted; refused for readers and frozen projects, logged,
and waiting for asked blobs like a start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 21:01:41 +00:00
co-authored by Claude Opus 5.5
parent 317c0d9602
commit 12b26f4abe
23 changed files with 531 additions and 165 deletions
+35 -9
View File
@@ -2,8 +2,10 @@
// (F4.6g5), on FILIUS' example nat_with_mapped_port: a tap on a home router
// shows its NAT table (the port forwarding's entry, then a ping's), a tap on
// a switch its table (the addresses a ping taught it), each reset; and
// "Pakete verwerfen" held while a ping runs: the ping loses answers, the
// list shows the dropped frames in Java's words.
// "Pakete verwerfen" held while a ping runs, in the simulation's
// „Eigenschaften“: the ping loses answers, the list shows the dropped frames
// in Java's words; „Simulation neu starten“ empties the command line and the
// list and keeps the server's file.
import { expect, test, type Locator, type Page } from '@playwright/test';
import { drawing, openEditor, serveEditor, showDesktop, showFromMenu } from './editorPage.js';
@@ -80,7 +82,10 @@ test('shows the tables of a home router and a switch, and drops while “Pakete
await desktop.locator('[data-program="commandPrompt"]').click();
await input.fill('ping 141.99.4.10');
await input.press('Enter');
const hold = page.getByRole('button', { name: 'Pakete verwerfen' });
await page.getByRole('button', { name: 'Eigenschaften' }).click();
const properties = page.getByRole('complementary', { name: 'Eigenschaften' });
await expect(properties.getByText('Noch keine verworfenen Pakete.')).toBeVisible();
const hold = properties.getByRole('button', { name: 'Pakete verwerfen' });
const box = (await hold.boundingBox())!;
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
@@ -91,19 +96,40 @@ test('shows the tables of a home router and a switch, and drops while “Pakete
// The second ping's statistics: an answer lost.
await expect(desktop.locator('.terminal-text')).toContainText(/Paketstatistik ---[^]*Paketstatistik ---\n4 Paket\(e\) gesendet, [0-3] Paket\(e\) empfangen/, { timeout: 30_000 });
await expect(desktop.locator('.terminal-text')).toContainText('-- Timeout!');
const toggle = page.getByRole('button', { name: /^Verworfen: [1-9]/ });
await toggle.click();
await expect(page.getByLabel('Verworfene Pakete')).toContainText(/\d\d:\d\d:\d\d\.\d\d\d : \[src=[0-9A-F:]+, dest=[0-9A-F:]+, type=0x8/);
await expect(properties.getByRole('heading', { name: /^Verworfene Pakete \([1-9]\d*\)$/ })).toBeVisible();
await expect(properties.getByLabel('Verworfene Pakete', { exact: true })).toContainText(/\d\d:\d\d:\d\d\.\d\d\d : \[src=[0-9A-F:]+, dest=[0-9A-F:]+, type=0x8/);
// „Simulation neu starten“: asked first — cancelled, nothing happens; confirmed, the command line and the
// dropped frames begin anew, the simulation runs, and what the server saved is still there.
await input.fill('touch bleibt.txt');
await input.press('Enter');
await properties.getByRole('button', { name: 'Simulation neu starten' }).click();
await expect(properties.getByText('Die Simulation für alle neu starten? Alle Protokolle und Datenpakete gehen verloren.')).toBeVisible();
await properties.getByRole('button', { name: 'Abbrechen' }).click();
await expect(properties.getByLabel('Verworfene Pakete', { exact: true })).toBeVisible();
await expect(desktop.locator('.terminal-text')).toContainText('ping 141.99.4.10');
await properties.getByRole('button', { name: 'Simulation neu starten' }).click();
await properties.getByRole('button', { name: 'Neu starten' }).click();
await expect(desktop.locator('.terminal-text')).not.toContainText('ping 141.99.4.10');
await expect(properties.getByText('Noch keine verworfenen Pakete.')).toBeVisible();
await expect(properties.getByRole('heading', { name: 'Verworfene Pakete (0)' })).toBeVisible();
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
await input.fill('ls');
await input.press('Enter');
await expect(desktop.locator('.terminal-text')).toContainText('bleibt.txt');
});
test('keeps “Pakete verwerfen” in reach on a phone', async ({ page }) => {
test('keeps “Pakete verwerfen” in reach on a phone, in „Eigenschaften“ over the whole editor', async ({ page }) => {
await page.setViewportSize({ width: 360, height: 740 });
await openEditor(page, server.base, 'nat_with_mapped_port.fls');
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
await expect(page.getByRole('button', { name: 'Pakete verwerfen' })).toBeInViewport({ ratio: 1 });
await expect(page.getByRole('button', { name: /^Verworfen: / })).toBeInViewport({ ratio: 1 });
// Every part of the toolbar within the screen (the dev page's own fields above the editor are wider; they are not the editor's).
const rights = await page.locator('.editor-toolbar > *').evaluateAll((parts) => parts.map((part) => part.getBoundingClientRect().right));
expect(Math.max(...rights)).toBeLessThanOrEqual(360);
await page.getByRole('button', { name: 'Eigenschaften' }).click();
const properties = page.getByRole('complementary', { name: 'Eigenschaften' });
expect(await properties.boundingBox()).toEqual(await page.locator('.editor-root').boundingBox());
await expect(properties.getByRole('button', { name: 'Pakete verwerfen' })).toBeInViewport({ ratio: 1 });
await expect(properties.getByRole('button', { name: 'Simulation neu starten' })).toBeInViewport({ ratio: 1 });
});