Files
mdg-filius/packages/editor/browser/viewers.spec.ts
T
julian.kiedaischandClaude Opus 5.5 6809a71300 feat: F4.6g5 the switch and NAT tables and "Pakete verwerfen", measured against Java's
ViewersRun takes the rows of Java's viewers from the firmware: a switch's
table, a home router's NAT table, each after its reset, and the frames
"Pakete verwerfen" drops while it is held. The core writes a frame as Java
does and resets the NAT's dynamic entries; the room sends both tables as
views, drops while any client holds the button, and sends the dropped
frames, begun anew with each start, with the clock their times of day are
written from; the editor opens a table with a tap on a switch or home
router and holds "Pakete verwerfen" in its toolbar, which now wraps on a
phone. This completes F4.6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:13:48 +00:00

115 lines
6.3 KiB
TypeScript

// The viewers of a running simulation and "Pakete verwerfen" in Chromium
// (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.
import { expect, test, type Locator, type Page } from '@playwright/test';
import { drawing, onDevice, openEditor, serveEditor } from './editorPage.js';
let server: { readonly base: string; readonly close: () => Promise<void> };
test.beforeAll(async () => {
server = await serveEditor();
});
test.afterAll(async () => {
await server.close();
});
async function tap(page: Page, id: string) {
const at = await onDevice(page, id);
await page.mouse.click(at.x, at.y);
}
async function install(desktop: Locator, program: string) {
await desktop.locator('[data-program="installation"]').click();
const installation = desktop.locator('[data-program-window="installation"]');
await installation.locator('[data-list="available"] [role="option"]').filter({ hasText: program }).click();
await installation.getByRole('button', { name: 'Installieren' }).click();
await installation.getByRole('button', { name: 'Änderungen annehmen' }).click();
}
test('shows the tables of a home router and a switch, and drops while “Pakete verwerfen” is held', async ({ page }) => {
test.setTimeout(120_000);
await openEditor(page, server.base, 'nat_with_mapped_port.fls');
const nodes = (await drawing(page)).nodes;
const idOf = (name: string) => nodes.find((node) => node.name === name)!.id;
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
// The home router with the port forwarding: its entry, with Java's columns.
const gateway = idOf('Heimrouter Vincent');
await tap(page, gateway);
const nat = page.locator(`section[data-viewer="${gateway}"]`);
await expect(nat.locator('h2')).toHaveText('NAT-Tabelle Heimrouter Vincent');
await expect(nat.locator('th')).toHaveText(['Protokoll', 'Portfreigabe', 'WAN-Adresse', 'LAN-Adresse', 'LAN-Port', 'Letzte Aktualisierung']);
await expect(nat.locator('tbody tr')).toHaveCount(1);
await expect(nat.locator('tbody tr').first().locator('td')).toHaveText(['TCP', '80', '*', '192.168.0.10', '80', '-']);
await nat.locator('.desktop-close').click();
// The web server behind it pings out.
const web = idOf('Webserver - 192.168.0.10');
await tap(page, web);
const desktop = page.locator(`section[data-desktop="${web}"]`);
await install(desktop, 'Befehlszeile');
await desktop.locator('[data-program="commandPrompt"]').click();
const input = desktop.locator('input[name="terminalInput"]');
await input.fill('ping 141.99.4.10');
await input.press('Enter');
await expect(desktop.locator('.terminal-text')).toContainText('4 Paket(e) empfangen', { timeout: 30_000 });
await desktop.locator('.desktop-close').click();
await tap(page, gateway);
await expect(nat.locator('tbody tr')).toHaveCount(2);
await expect(nat.locator('tbody tr').nth(1).locator('td').nth(0)).toHaveText('ICMP');
await expect(nat.locator('tbody tr').nth(1).locator('td').nth(5)).toHaveText(/^\d\d:\d\d:\d\d$/);
await nat.getByRole('button', { name: 'Tabelle zurücksetzen' }).click();
await expect(nat.locator('tbody tr')).toHaveCount(1);
await nat.locator('.desktop-close').click();
// The switch the web server hangs on: the addresses the ping taught it.
const link = (await drawing(page)).links.find((each) => each.sourceNode === web || each.targetNode === web)!;
const lanSwitch = link.sourceNode === web ? link.targetNode : link.sourceNode;
await tap(page, lanSwitch);
const sat = page.locator(`section[data-viewer="${lanSwitch}"]`);
await expect(sat.locator('th')).toHaveText(['MAC', 'Port', 'Letzte Aktualisierung']);
await expect(sat.locator('tbody tr').first().locator('td')).toHaveText([/^[0-9A-F:]{17}$/, /^Port \d+$/, /^\d\d:\d\d:\d\d$/]);
await sat.getByRole('button', { name: 'Tabelle löschen' }).click();
await expect(sat.locator('tbody tr')).toHaveCount(0);
await sat.locator('.desktop-close').click();
// "Pakete verwerfen" held while a ping runs: answers lost, the frames listed.
await tap(page, web);
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' });
const box = (await hold.boundingBox())!;
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await expect(hold).toHaveAttribute('aria-pressed', 'true');
await page.waitForTimeout(2_500);
await page.mouse.up();
await expect(hold).toHaveAttribute('aria-pressed', 'false');
// 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/);
});
test('keeps “Pakete verwerfen” in reach on a phone', 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);
});