feat(editor): the simulation opens sheets, not windows in the window
A tap on a device in the simulation now only chooses it; its three-dot button (or a long press) opens what Java's click opened: the desktop, and newly a switch's SAT table and a home router's NAT table, beside each card's data exchange. The desktop, the tables and the data exchange are no windows in the window any more but the simulation's sheet (SimulationSheet), after the dashboard's AppSheet: below the toolbar to the bottom, a head with icon, title, who else has it open, see-through and close. One at a time — it covers the surface. It stays over a return to the design with the program open on a desktop; a sheet whose device was removed closes. See-through fades the body (0.55) and keeps the head; the choice is kept in the browser. Java opens these as movable windows, several at once, with a click; named in CHANGES.md. Tests open through the menu (showFromMenu, showDesktop in editorPage.ts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
// The simulation in Chromium (F4.6a): Java's mode buttons, the design locked
|
||||
// while it runs, the speed, the cables that carry frames, a computer's
|
||||
// desktop opened with a tap — Java's title, its programs in Java's order, its
|
||||
// network as it runs — and desktops kept over a return to the design.
|
||||
// desktop opened from its menu — Java's title, its programs in Java's order,
|
||||
// its network as it runs — as the simulation's sheet, one at a time, below
|
||||
// the toolbar, see-through on request, kept over a return to the design.
|
||||
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
import { desktopOf, drawing, onDevice, openEditor, serveEditor } from './editorPage.js';
|
||||
import { expect, test, type Locator, type Page } from '@playwright/test';
|
||||
import { desktopOf, drawing, onDevice, openEditor, serveEditor, showDesktop, showFromMenu } from './editorPage.js';
|
||||
|
||||
let server: { readonly base: string; readonly close: () => Promise<void> };
|
||||
|
||||
@@ -29,6 +30,9 @@ const simulationButton = (page: Page) => page.getByRole('button', { name: 'Wechs
|
||||
const designButton = (page: Page) => page.getByRole('button', { name: 'Wechselt in den Entwurfsmodus' });
|
||||
const desktop = (page: Page, id: string) => page.locator(`section[data-desktop="${id}"]`);
|
||||
|
||||
/** Once the sheet has come out of the toolbar (its 140 ms), where it rests. */
|
||||
const settled = (sheet: Locator) => sheet.evaluate((element) => Promise.all(element.getAnimations().map((animation) => animation.finished)));
|
||||
|
||||
/** Starts the simulation and waits until the room says it runs (it answers from its worker). */
|
||||
async function start(page: Page): Promise<void> {
|
||||
await simulationButton(page).click();
|
||||
@@ -86,13 +90,16 @@ test("lights a cable while it carries a frame, in Java's green", async ({ page }
|
||||
await expect(page.locator('path[data-link][data-active]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("opens a computer's desktop with a tap: Java's title, its programs in Java's order, its network as it runs", async ({ page }) => {
|
||||
test("opens a computer's desktop from its menu: Java's title, its programs in Java's order, its network as it runs", async ({ page }) => {
|
||||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||||
const id = await deviceNamed(page, 'DNS-Server 2.10');
|
||||
expect((await desktopOf(page, id))!.programs).toHaveLength(3);
|
||||
await start(page);
|
||||
// A tap only chooses the computer (Java's click opens its desktop): its three-dot button is there, no desktop.
|
||||
await tap(page, id);
|
||||
const window = desktop(page, id);
|
||||
await expect(page.locator('.device-menu-button')).toBeVisible();
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(0);
|
||||
const window = await showDesktop(page, id);
|
||||
await expect(window.getByRole('heading')).toHaveText('DNS-Server 2.10 - 141.99.2.10');
|
||||
await expect(window.locator('.desktop-icon span')).toHaveText(['Software-Installation', 'Datei-Explorer', 'DNS-Server', 'Webserver']);
|
||||
await expect(window.getByRole('button', { name: 'Startbildschirm' })).toBeVisible();
|
||||
@@ -106,11 +113,13 @@ test("opens a computer's desktop with a tap: Java's title, its programs in Java'
|
||||
await window.locator('.desktop-info-layer').click({ position: { x: 20, y: 20 } });
|
||||
await expect(window.locator('.desktop-network')).toHaveCount(0);
|
||||
|
||||
// A switch has no desktop; its long-press menu has nothing either.
|
||||
await tap(page, await deviceNamed(page, 'Switch 1'));
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(1);
|
||||
// A switch has no desktop: its menu offers its table, nothing more (it has no address for a data exchange).
|
||||
await window.getByRole('button', { name: 'Schließen' }).click();
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(0);
|
||||
await tap(page, await deviceNamed(page, 'Switch 1'));
|
||||
await page.locator('.device-menu-button').click();
|
||||
await expect(page.getByRole('menuitem')).toHaveText(['SAT Tabelle anzeigen']);
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Java's menu: "show desktop".
|
||||
const at = await onDevice(page, await deviceNamed(page, 'Notebook 1.10'));
|
||||
@@ -123,36 +132,50 @@ test("opens a computer's desktop with a tap: Java's title, its programs in Java'
|
||||
await expect(page.locator('section[data-desktop] .desktop-icon span')).toHaveText(['Software-Installation', 'Webbrowser']);
|
||||
});
|
||||
|
||||
test('moves a desktop by its title bar, brings the one touched to the front, and keeps them over a return to the design', async ({ page }) => {
|
||||
test('opens one sheet at a time below the toolbar, see-through on request, kept with its program over a return to the design', async ({ page }) => {
|
||||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||||
const [first, second] = [await deviceNamed(page, 'Notebook 1.10'), await deviceNamed(page, 'Notebook 1.11')];
|
||||
await start(page);
|
||||
await tap(page, first);
|
||||
// Moved by its title bar into the half of the page the second notebook is not in.
|
||||
const at = await onDevice(page, second);
|
||||
const width = page.viewportSize()!.width;
|
||||
const bar = (await desktop(page, first).locator('.desktop-titlebar h2').boundingBox())!;
|
||||
const before = (await desktop(page, first).boundingBox())!;
|
||||
const step = { x: (at.x < width / 2 ? width - 650 : 0) - before.x, y: 30 };
|
||||
await page.mouse.move(bar.x + 10, bar.y + 10);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(bar.x + 10 + step.x, bar.y + 10 + step.y, { steps: 8 });
|
||||
await page.mouse.up();
|
||||
const after = (await desktop(page, first).boundingBox())!;
|
||||
expect([Math.round(after.x - before.x), Math.round(after.y - before.y)]).toEqual([Math.round(step.x), step.y]);
|
||||
|
||||
await tap(page, second);
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(2);
|
||||
const z = async (id: string) => Number(await desktop(page, id).evaluate((element) => getComputedStyle(element).zIndex));
|
||||
expect(await z(second)).toBeGreaterThan(await z(first));
|
||||
await desktop(page, first).locator('.desktop-titlebar h2').click();
|
||||
expect(await z(first)).toBeGreaterThan(await z(second));
|
||||
|
||||
const sheet = await showDesktop(page, first);
|
||||
await settled(sheet);
|
||||
// The dashboard's sheet: from below the toolbar to the bottom, the area's width — no window in the window.
|
||||
const area = (await page.locator('.editor-area').boundingBox())!;
|
||||
const toolbar = (await page.locator('.editor-toolbar').boundingBox())!;
|
||||
const box = (await sheet.boundingBox())!;
|
||||
expect(box.y).toBeGreaterThanOrEqual(toolbar.y + toolbar.height);
|
||||
expect(box.x).toBeCloseTo(area.x, 0);
|
||||
expect(box.width).toBeCloseTo(area.width, 0);
|
||||
expect(box.y + box.height).toBeCloseTo(area.y + area.height, 0);
|
||||
// Its program stays open over a return to the design.
|
||||
await sheet.locator('[data-program="installation"]').click();
|
||||
await designButton(page).click();
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(0);
|
||||
await simulationButton(page).click();
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(2);
|
||||
expect(Math.round((await desktop(page, first).boundingBox())!.x)).toBe(Math.round(after.x));
|
||||
await expect(sheet.locator('[data-program-window="installation"]')).toBeVisible();
|
||||
|
||||
// Closed (it covers the surface), another opens: one at a time.
|
||||
await showDesktop(page, second);
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(1);
|
||||
await expect(desktop(page, second)).toBeVisible();
|
||||
|
||||
// „Durchsichtig“: the network shows through; the choice holds for the next sheet and is kept in this browser.
|
||||
const seeThrough = desktop(page, second).getByRole('button', { name: 'Durchsichtig' });
|
||||
await expect(seeThrough).toHaveAttribute('aria-pressed', 'false');
|
||||
const body = (id: string) => desktop(page, id).locator('.simulation-sheet__body');
|
||||
await expect(body(second)).toHaveCSS('opacity', '1');
|
||||
await seeThrough.click();
|
||||
await expect(seeThrough).toHaveAttribute('aria-pressed', 'true');
|
||||
await expect(body(second)).toHaveCSS('opacity', '0.55');
|
||||
// Only the body fades: the sheet itself — and so its head, with title and buttons — stays as it is (opacity is not inherited, so the sheet is asked).
|
||||
await expect(desktop(page, second)).toHaveCSS('opacity', '1');
|
||||
expect(await page.evaluate(() => window.localStorage.getItem('filius.sheetSeeThrough'))).toBe('true');
|
||||
|
||||
// The next sheet is see-through too; the first desktop, closed in between, begins at its home screen.
|
||||
await showDesktop(page, first);
|
||||
await expect(desktop(page, first)).toHaveAttribute('data-see-through', 'true');
|
||||
await expect(desktop(page, first).locator('[data-program-window]')).toHaveCount(0);
|
||||
await desktop(page, first).getByRole('button', { name: 'Durchsichtig' }).click();
|
||||
await expect(body(first)).toHaveCSS('opacity', '1');
|
||||
});
|
||||
|
||||
test('takes nothing back and keeps no design tool while the simulation runs', async ({ page }) => {
|
||||
@@ -185,8 +208,7 @@ test("titles a desktop with the address alone where the address is the computer'
|
||||
await page.getByRole('complementary', { name: 'Einstellungen' }).getByRole('button', { name: 'Schließen' }).click();
|
||||
// The notebooks ask for their addresses by DHCP as the simulation starts; the switch passes their broadcasts on to Rechner 1.
|
||||
await start(page);
|
||||
await tap(page, id);
|
||||
const window = desktop(page, id);
|
||||
const window = await showDesktop(page, id);
|
||||
await expect(window.getByRole('heading')).toHaveText('192.168.0.11');
|
||||
await expect(window.locator('.desktop-network-icon[data-active="true"]')).toBeAttached({ timeout: 10_000 });
|
||||
await window.getByRole('button', { name: 'Netzwerk' }).click();
|
||||
@@ -199,7 +221,7 @@ test("closes a desktop whose computer is removed while the design is shown", asy
|
||||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||||
await start(page);
|
||||
await tap(page, id);
|
||||
await showDesktop(page, id);
|
||||
await designButton(page).click();
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'design');
|
||||
await tap(page, id);
|
||||
@@ -207,27 +229,36 @@ test("closes a desktop whose computer is removed while the design is shown", asy
|
||||
await simulationButton(page).click();
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(0);
|
||||
// The next desktop takes the first place: the gone one is not counted.
|
||||
// The next desktop opens as ever.
|
||||
const next = await deviceNamed(page, 'Notebook 1.11');
|
||||
await tap(page, next);
|
||||
const area = (await page.locator('.editor-area').boundingBox())!;
|
||||
const box = (await desktop(page, next).boundingBox())!;
|
||||
expect([Math.round(box.x - area.x), Math.round(box.y - area.y)]).toEqual([24, 76]);
|
||||
await showDesktop(page, next);
|
||||
await expect(page.locator('section[data-desktop]')).toHaveCount(1);
|
||||
|
||||
// So does a switch's table whose switch is removed (a table would otherwise stand there empty, under no name).
|
||||
const hub = await deviceNamed(page, 'Switch 1');
|
||||
await showFromMenu(page, hub, 'SAT Tabelle anzeigen');
|
||||
await expect(page.locator(`section[data-viewer="${hub}"]`)).toBeVisible();
|
||||
await designButton(page).click();
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'design');
|
||||
await tap(page, hub);
|
||||
await page.keyboard.press('Delete');
|
||||
await expect.poll(async () => (await drawing(page)).nodes.some((node) => node.id === hub)).toBe(false);
|
||||
await simulationButton(page).click();
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||
await expect(page.locator('section[data-viewer]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test.describe('on a phone', () => {
|
||||
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true });
|
||||
|
||||
test('fills the surface with a desktop opened by a tap, below the mode buttons', async ({ page }) => {
|
||||
test('fills the surface with a desktop opened from its menu, below the mode buttons', async ({ page }) => {
|
||||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||||
await simulationButton(page).tap();
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||
await page.getByRole('button', { name: 'Alles zeigen' }).tap();
|
||||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||||
const at = await onDevice(page, id);
|
||||
await page.touchscreen.tap(at.x, at.y);
|
||||
const window = desktop(page, id);
|
||||
await expect(window).toBeVisible();
|
||||
const window = await showDesktop(page, id, true);
|
||||
await settled(window);
|
||||
const box = (await window.boundingBox())!;
|
||||
const area = (await page.locator('.editor-area').boundingBox())!;
|
||||
const modes = (await simulationButton(page).boundingBox())!;
|
||||
@@ -244,8 +275,7 @@ test.describe('on a phone', () => {
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||
await page.getByRole('button', { name: 'Alles zeigen' }).tap();
|
||||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||||
const at = await onDevice(page, id);
|
||||
await page.touchscreen.tap(at.x, at.y);
|
||||
await showDesktop(page, id, true);
|
||||
await desktop(page, id).locator('[data-program="commandPrompt"]').tap();
|
||||
const window = desktop(page, id).locator('[data-program-window="commandPrompt"]');
|
||||
for (const name of ['ipconfig', 'Vorheriger Befehl', 'Befehl abbrechen (Strg+C)']) {
|
||||
@@ -264,8 +294,7 @@ test.describe('on a phone', () => {
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||
await page.getByRole('button', { name: 'Alles zeigen' }).tap();
|
||||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||||
const at = await onDevice(page, id);
|
||||
await page.touchscreen.tap(at.x, at.y);
|
||||
await showDesktop(page, id, true);
|
||||
await desktop(page, id).locator('[data-program="installation"]').tap();
|
||||
const panel = desktop(page, id).locator('[data-program-window="installation"]');
|
||||
const first = (await panel.locator('[data-list="available"] [role="option"]').first().boundingBox())!;
|
||||
@@ -281,7 +310,7 @@ test.describe('on a phone', () => {
|
||||
/** Opens a computer's command line on its desktop. */
|
||||
async function commandLine(page: Page, name: string) {
|
||||
const id = await deviceNamed(page, name);
|
||||
await tap(page, id);
|
||||
await showDesktop(page, id);
|
||||
await desktop(page, id).locator('[data-program="commandPrompt"]').click();
|
||||
const window = desktop(page, id).locator('[data-program-window="commandPrompt"]');
|
||||
await expect(window.getByRole('heading')).toHaveText('Befehlszeile');
|
||||
@@ -398,8 +427,7 @@ test("installs and removes programs as Java's installation does, and a program i
|
||||
const id = await deviceNamed(page, 'Rechner 3.10');
|
||||
expect((await desktopOf(page, id))!.programs).toEqual([]);
|
||||
await start(page);
|
||||
await tap(page, id);
|
||||
const window = desktop(page, id);
|
||||
const window = await showDesktop(page, id);
|
||||
await window.locator('[data-program="installation"]').click();
|
||||
const panel = window.locator('[data-program-window="installation"]');
|
||||
await expect(panel.getByRole('heading')).toHaveText('Software-Installation');
|
||||
@@ -464,7 +492,7 @@ test('speaks English when asked', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Change to Simulation Mode' }).click();
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||
const id = await deviceNamed(page, 'DNS-Server 2.10');
|
||||
await tap(page, id);
|
||||
await showDesktop(page, id);
|
||||
await expect(desktop(page, id).locator('.desktop-icon span')).toHaveText(['Software Installation', 'DNS server', 'File explorer', 'Webserver']);
|
||||
await expect(desktop(page, id).getByRole('button', { name: 'Home Screen' })).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user