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>
500 lines
28 KiB
TypeScript
500 lines
28 KiB
TypeScript
// 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 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 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> };
|
||
|
||
test.beforeAll(async () => {
|
||
server = await serveEditor();
|
||
});
|
||
|
||
test.afterAll(async () => {
|
||
await server.close();
|
||
});
|
||
|
||
async function deviceNamed(page: Page, name: string): Promise<string> {
|
||
return (await drawing(page)).nodes.find((node) => node.name === name)!.id;
|
||
}
|
||
|
||
async function tap(page: Page, id: string): Promise<void> {
|
||
const at = await onDevice(page, id);
|
||
await page.mouse.click(at.x, at.y);
|
||
}
|
||
|
||
const simulationButton = (page: Page) => page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' });
|
||
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();
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||
}
|
||
|
||
test('locks the design while the simulation runs, as Java does, and gives it back', async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||
const router = (await drawing(page)).nodes.find((node) => node.kind === 'router')!;
|
||
await tap(page, router.id);
|
||
await expect(page.getByRole('complementary', { name: 'Einstellungen' })).toBeVisible();
|
||
await expect(designButton(page)).toHaveAttribute('aria-pressed', 'true');
|
||
|
||
await simulationButton(page).click();
|
||
await expect(simulationButton(page)).toHaveAttribute('aria-pressed', 'true');
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||
await expect(page.getByRole('complementary', { name: 'Einstellungen' })).toHaveCount(0);
|
||
await expect(page.locator('[data-palette]')).toHaveCount(0);
|
||
await expect(page.getByRole('button', { name: 'Rückgängig' })).toHaveCount(0);
|
||
// Java's slider: 90 per cent at first (Verbindung's delay factor 10).
|
||
const speed = page.locator('input[name="speed"]');
|
||
await expect(speed).toHaveValue('90');
|
||
await expect(page.getByTitle('Aktuelle Simulationsgeschwindigkeit')).toHaveText('90%');
|
||
await speed.fill('40');
|
||
await expect(page.getByTitle('Aktuelle Simulationsgeschwindigkeit')).toHaveText('40%');
|
||
|
||
// Nothing moves, nothing is chosen, a key removes nothing.
|
||
const at = await onDevice(page, router.id);
|
||
await page.mouse.move(at.x, at.y);
|
||
await page.mouse.down();
|
||
await page.mouse.move(at.x + 80, at.y + 60, { steps: 5 });
|
||
await page.mouse.up();
|
||
await page.keyboard.press('Delete');
|
||
const still = (await drawing(page)).nodes.find((node) => node.id === router.id)!;
|
||
expect([still.x, still.y]).toEqual([router.x, router.y]);
|
||
await expect(page.locator('g.device[data-selected]')).toHaveCount(0);
|
||
|
||
await designButton(page).click();
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'design');
|
||
await expect(page.locator('[data-palette]').first()).toBeVisible();
|
||
await expect(page.getByRole('button', { name: 'Rückgängig' })).toBeVisible();
|
||
await expect(page.locator('input[name="speed"]')).toHaveCount(0);
|
||
// The speed stays the room's for the next start.
|
||
await simulationButton(page).click();
|
||
await expect(page.locator('input[name="speed"]')).toHaveValue('40');
|
||
});
|
||
|
||
test("lights a cable while it carries a frame, in Java's green", async ({ page }) => {
|
||
// The notebooks ask for their addresses by DHCP as the simulation starts.
|
||
await openEditor(page, server.base, 'einfaches_rechnernetz_komplett.fls');
|
||
await simulationButton(page).click();
|
||
await expect(page.locator('path[data-link][data-active="true"]').first()).toBeAttached({ timeout: 10_000 });
|
||
await expect(page.locator('path[data-link][data-active="true"]').first()).toHaveCSS('stroke', 'rgb(0, 255, 64)');
|
||
await designButton(page).click();
|
||
await expect(page.locator('path[data-link][data-active]')).toHaveCount(0);
|
||
});
|
||
|
||
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 opens the computer's menu (Java's click opens its desktop, its menu holds it), no desktop by itself, no three-dot button.
|
||
await tap(page, id);
|
||
await expect(page.getByRole('menuitem')).toHaveText(['Desktop anzeigen', 'Datenaustausch anzeigen (141.99.2.10)']);
|
||
await expect(page.locator('section[data-desktop]')).toHaveCount(0);
|
||
await expect(page.locator('.device-menu-button')).toHaveCount(0);
|
||
await page.keyboard.press('Escape');
|
||
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();
|
||
|
||
await window.getByRole('button', { name: 'Netzwerk' }).click();
|
||
const values = window.locator('.desktop-network dd');
|
||
await expect(window.locator('.desktop-network dt')).toHaveText(['IP-Adresse:', 'Netzmaske:', 'DNS-Server:', 'Gateway:', 'MAC-Adresse:']);
|
||
await expect(values.nth(0)).toHaveText('141.99.2.10');
|
||
await expect(values.nth(4)).toHaveText(/^([0-9A-F]{2}:){5}[0-9A-F]{2}$/);
|
||
// A press beside the panel closes it.
|
||
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 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 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'));
|
||
await page.mouse.move(at.x, at.y);
|
||
await page.mouse.down();
|
||
await page.waitForTimeout(700);
|
||
await page.mouse.up();
|
||
await page.getByRole('menuitem', { name: 'Desktop anzeigen' }).click();
|
||
await expect(page.locator('section[data-desktop] h2')).toHaveText('Notebook 1.10 - 141.99.1.10');
|
||
await expect(page.locator('section[data-desktop] .desktop-icon span')).toHaveText(['Software-Installation', 'Webbrowser']);
|
||
});
|
||
|
||
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);
|
||
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(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 }) => {
|
||
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.mouse.move(at.x, at.y);
|
||
await page.mouse.down();
|
||
await page.mouse.move(at.x + 60, at.y + 40, { steps: 5 });
|
||
await page.mouse.up();
|
||
const moved = (await drawing(page)).nodes.find((node) => node.id === id)!;
|
||
await page.locator('[data-palette="cable"]').click();
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-tool', 'cable');
|
||
await start(page);
|
||
await page.keyboard.press('Control+z');
|
||
const still = (await drawing(page)).nodes.find((node) => node.id === id)!;
|
||
expect([still.x, still.y]).toEqual([moved.x, moved.y]);
|
||
await designButton(page).click();
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-tool', 'select');
|
||
// Back in the design, the step can be taken back.
|
||
await page.getByRole('button', { name: 'Rückgängig' }).click();
|
||
await expect.poll(async () => (await drawing(page)).nodes.find((node) => node.id === id)!.x).not.toBe(moved.x);
|
||
});
|
||
|
||
test("titles a desktop with the address alone where the address is the computer's name, and lights its network icon while its cable carries a frame", async ({ page }) => {
|
||
await openEditor(page, server.base, 'einfaches_rechnernetz_komplett.fls');
|
||
const id = await deviceNamed(page, 'Rechner 1');
|
||
await tap(page, id);
|
||
await page.getByRole('complementary', { name: 'Einstellungen' }).getByLabel('IP-Adresse als Name verwenden').check();
|
||
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);
|
||
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();
|
||
await expect(window.locator('.desktop-network')).toBeVisible();
|
||
await window.getByRole('button', { name: 'Startbildschirm' }).click();
|
||
await expect(window.locator('.desktop-network')).toHaveCount(0);
|
||
});
|
||
|
||
test("closes a desktop whose computer is removed while the design is shown", async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||
await start(page);
|
||
await showDesktop(page, id);
|
||
await designButton(page).click();
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'design');
|
||
await tap(page, id);
|
||
await page.keyboard.press('Delete');
|
||
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 opens as ever.
|
||
const next = await deviceNamed(page, 'Notebook 1.11');
|
||
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 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 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())!;
|
||
expect(box.width).toBeCloseTo(area.width, 0);
|
||
expect(box.y).toBeGreaterThanOrEqual(modes.y + modes.height);
|
||
expect(box.y + box.height).toBeCloseTo(area.y + area.height, 0);
|
||
const home = (await window.getByRole('button', { name: 'Startbildschirm' }).boundingBox())!;
|
||
expect(home.height).toBeGreaterThanOrEqual(44);
|
||
});
|
||
|
||
test("offers the command line's keys as buttons for a finger, and types without zooming", async ({ page }) => {
|
||
await openEditor(page, server.base, 'internetwork_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');
|
||
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)']) {
|
||
const box = (await window.getByRole('button', { name, exact: true }).boundingBox())!;
|
||
expect(box.height, name).toBeGreaterThanOrEqual(44);
|
||
}
|
||
await expect(window.locator('input[name="terminalInput"]')).toHaveCSS('font-size', '16px');
|
||
await window.getByRole('button', { name: 'ipconfig', exact: true }).tap();
|
||
await window.locator('input[name="terminalInput"]').press('Enter');
|
||
await expect(window.locator('.terminal-text')).toContainText('IP Adresse . . . : 141.99.1.10');
|
||
});
|
||
|
||
test('offers the installation’s programs as targets for a finger', async ({ page }) => {
|
||
await openEditor(page, server.base, 'internetwork_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');
|
||
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())!;
|
||
expect(first.height).toBeGreaterThanOrEqual(44);
|
||
const arrow = (await panel.getByRole('button', { name: 'Installieren' }).boundingBox())!;
|
||
expect(arrow.height).toBeGreaterThanOrEqual(44);
|
||
await panel.locator('[data-list="available"] [role="option"]').first().tap();
|
||
await panel.getByRole('button', { name: 'Installieren' }).tap();
|
||
await expect(panel.locator('[data-list="installed"] [role="option"]')).toHaveText(['Befehlszeile', 'Bildbetrachter', 'Webbrowser']);
|
||
});
|
||
});
|
||
|
||
/** Opens a computer's command line on its desktop. */
|
||
async function commandLine(page: Page, name: string) {
|
||
const id = await deviceNamed(page, name);
|
||
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');
|
||
return { id, window, input: window.locator('input[name="terminalInput"]'), text: window.locator('.terminal-text'), prompt: window.locator('.terminal-prompt') };
|
||
}
|
||
|
||
test("types into a computer's command line as into Java's: the start screen, a line after its prompt, the history, a ping across the routers", async ({ page }) => {
|
||
test.setTimeout(60_000);
|
||
await openEditor(page, server.base, 'internetwork_komplett.fls');
|
||
await start(page);
|
||
const { window, input, text, prompt } = await commandLine(page, 'Notebook 1.10');
|
||
await expect(text).toContainText("Verwende den Befehl 'help'");
|
||
await expect(prompt).toHaveText('/>');
|
||
await expect(input).toBeFocused();
|
||
await input.fill('ipconfig');
|
||
await input.press('Enter');
|
||
await expect(text).toContainText('\n/> ipconfig\nIP Adresse . . . : 141.99.1.10');
|
||
await expect(input).toHaveValue('');
|
||
await input.press('ArrowUp');
|
||
await expect(input).toHaveValue('ipconfig');
|
||
await input.press('ArrowDown');
|
||
await expect(input).toHaveValue('');
|
||
// A chip puts its command into the line.
|
||
await window.getByRole('button', { name: 'ping', exact: true }).click();
|
||
await expect(input).toHaveValue('ping ');
|
||
await input.pressSequentially('141.99.5.10');
|
||
await input.press('Enter');
|
||
// While the ping runs, Java hides the prompt.
|
||
await expect(prompt).toHaveCSS('visibility', 'hidden');
|
||
await expect(text).toContainText('4 Paket(e) gesendet, 4 Paket(e) empfangen', { timeout: 20_000 });
|
||
await expect(prompt).toHaveCSS('visibility', 'visible');
|
||
await window.getByRole('button', { name: 'Vorheriger Befehl' }).click();
|
||
await expect(input).toHaveValue('ping 141.99.5.10');
|
||
});
|
||
|
||
test('ends a command with Ctrl+C, from the keyboard and from its button', async ({ page }) => {
|
||
test.setTimeout(60_000);
|
||
await openEditor(page, server.base, 'internetwork_komplett.fls');
|
||
await start(page);
|
||
const { window, input, text, prompt } = await commandLine(page, 'Notebook 1.10');
|
||
await input.fill('ping 141.99.9.9');
|
||
await input.press('Enter');
|
||
await expect(prompt).toHaveCSS('visibility', 'hidden');
|
||
await input.press('Control+c');
|
||
await expect(prompt).toHaveCSS('visibility', 'visible', { timeout: 20_000 });
|
||
await expect(text).toContainText('Paketstatistik');
|
||
await input.fill('ping 141.99.9.9');
|
||
await input.press('Enter');
|
||
await expect(prompt).toHaveCSS('visibility', 'hidden');
|
||
await window.getByRole('button', { name: 'Befehl abbrechen (Strg+C)' }).click();
|
||
await expect(prompt).toHaveCSS('visibility', 'visible', { timeout: 20_000 });
|
||
});
|
||
|
||
test('keeps the command line over leaving it, over a return to the design, and exit closes it', async ({ page }) => {
|
||
await openEditor(page, server.base, 'internetwork_komplett.fls');
|
||
await start(page);
|
||
const { id, window, input, text } = await commandLine(page, 'Notebook 1.10');
|
||
await input.fill('pwd');
|
||
await input.press('Enter');
|
||
await expect(text).toContainText('\n/> pwd\n/');
|
||
await desktop(page, id).getByRole('button', { name: 'Startbildschirm' }).click();
|
||
await expect(window).toHaveCount(0);
|
||
await desktop(page, id).locator('[data-program="commandPrompt"]').click();
|
||
await expect(text).toContainText('\n/> pwd\n/');
|
||
await designButton(page).click();
|
||
await start(page);
|
||
await expect(text).toContainText('\n/> pwd\n/');
|
||
await input.fill('exit');
|
||
await input.press('Enter');
|
||
await expect(window).toHaveCount(0);
|
||
await expect(desktop(page, id).locator('.desktop-icon').first()).toBeVisible();
|
||
await desktop(page, id).locator('[data-program="commandPrompt"]').click();
|
||
await expect(text).toContainText('\n/> exit\n');
|
||
// An exit that was there when the window opened does not close it again: it takes the next line.
|
||
await input.fill('ls');
|
||
await input.press('Enter');
|
||
await expect(text).toContainText('\n/> exit\n\n/> ls\n');
|
||
await expect(window).toBeVisible();
|
||
});
|
||
|
||
test('keeps what a run made in the files over a return to the design, as Java\'s project keeps it', async ({ page }) => {
|
||
await openEditor(page, server.base, 'internetwork_komplett.fls');
|
||
await start(page);
|
||
const { input, text } = await commandLine(page, 'Notebook 1.10');
|
||
await input.fill('mkdir ordner');
|
||
await input.press('Enter');
|
||
await input.fill('ls');
|
||
await input.press('Enter');
|
||
await expect(text).toContainText('ordner');
|
||
await designButton(page).click();
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'design');
|
||
await start(page);
|
||
await input.fill('cls');
|
||
await input.press('Enter');
|
||
await input.fill('ls');
|
||
await input.press('Enter');
|
||
await expect(text).toContainText('\n/> ls\n');
|
||
await expect(text).toContainText('ordner');
|
||
// Removed in the run, it is gone from the project too.
|
||
await input.fill('rm ordner');
|
||
await input.press('Enter');
|
||
await designButton(page).click();
|
||
await start(page);
|
||
await input.fill('cls');
|
||
await input.press('Enter');
|
||
await input.fill('ls');
|
||
await input.press('Enter');
|
||
await expect(text).toContainText('\n/> ls\n');
|
||
await expect(text).not.toContainText('ordner');
|
||
});
|
||
|
||
test("installs and removes programs as Java's installation does, and a program installed runs at once", async ({ page }) => {
|
||
await openEditor(page, server.base, 'internetwork_komplett.fls');
|
||
const id = await deviceNamed(page, 'Rechner 3.10');
|
||
expect((await desktopOf(page, id))!.programs).toEqual([]);
|
||
await start(page);
|
||
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');
|
||
const installed = panel.locator('[data-list="installed"] [role="option"]');
|
||
const available = panel.locator('[data-list="available"] [role="option"]');
|
||
await expect(installed).toHaveCount(0);
|
||
await expect(available).toHaveText(['Befehlszeile', 'Bildbetrachter', 'Datei-Explorer', 'DNS-Server', 'E-Mail-Programm', 'E-Mail-Server', 'Echo-Server', 'Einfacher Client', 'Firewall', 'Gnutella', 'Text-Editor', 'Webserver', 'Webbrowser']);
|
||
// Chosen with a tap each, moved over with the arrow; the list keeps Java's order.
|
||
await available.filter({ hasText: 'Webserver' }).click();
|
||
await available.filter({ hasText: 'Befehlszeile' }).click();
|
||
await expect(available.filter({ hasText: 'Webserver' })).toHaveAttribute('aria-selected', 'true');
|
||
// A second tap lets a program go again.
|
||
await available.filter({ hasText: 'Gnutella' }).click();
|
||
await available.filter({ hasText: 'Gnutella' }).click();
|
||
await expect(available.filter({ hasText: 'Gnutella' })).toHaveAttribute('aria-selected', 'false');
|
||
await panel.getByRole('button', { name: 'Installieren' }).click();
|
||
await expect(installed).toHaveText(['Befehlszeile', 'Webserver']);
|
||
// Nothing changes before "apply changes".
|
||
expect((await desktopOf(page, id))!.programs).toEqual([]);
|
||
await panel.getByRole('button', { name: 'Änderungen annehmen' }).click();
|
||
await expect(panel).toHaveCount(0);
|
||
await expect(window.locator('.desktop-icon span')).toHaveText(['Software-Installation', 'Befehlszeile', 'Webserver']);
|
||
expect([...(await desktopOf(page, id))!.programs].sort()).toEqual(['commandPrompt', 'webServer']);
|
||
|
||
// The command line runs at once, and sees the web server's files.
|
||
await window.locator('[data-program="commandPrompt"]').click();
|
||
const input = window.locator('input[name="terminalInput"]');
|
||
await input.fill('ls /webserver');
|
||
await input.press('Enter');
|
||
await expect(window.locator('.terminal-text')).toContainText('index.html');
|
||
await window.getByRole('button', { name: 'Startbildschirm' }).click();
|
||
|
||
// A double click moves a program back; applied, it is gone.
|
||
await window.locator('[data-program="installation"]').click();
|
||
await installed.filter({ hasText: 'Webserver' }).dblclick();
|
||
await expect(installed).toHaveText(['Befehlszeile']);
|
||
await panel.getByRole('button', { name: 'Änderungen annehmen' }).click();
|
||
await expect(window.locator('.desktop-icon span')).toHaveText(['Software-Installation', 'Befehlszeile']);
|
||
expect((await desktopOf(page, id))!.programs).toEqual(['commandPrompt']);
|
||
// Taken on and given back before applying: nothing happens.
|
||
await window.locator('[data-program="installation"]').click();
|
||
await available.filter({ hasText: 'Gnutella' }).dblclick();
|
||
await installed.filter({ hasText: 'Gnutella' }).click();
|
||
await panel.getByRole('button', { name: 'Entfernen' }).click();
|
||
await panel.getByRole('button', { name: 'Änderungen annehmen' }).click();
|
||
expect((await desktopOf(page, id))!.programs).toEqual(['commandPrompt']);
|
||
});
|
||
|
||
test('says so when the simulation fails, and goes back to the design', async ({ page }) => {
|
||
// FILIUS' modem example: the core has no modems yet (remote links); a ping to the other side fails it.
|
||
await openEditor(page, server.base, 'modem_beispiel.fls');
|
||
await start(page);
|
||
const { input } = await commandLine(page, 'Notebook 0.10');
|
||
await input.fill('ping 192.168.0.11');
|
||
await input.press('Enter');
|
||
await expect(page.getByRole('alert')).toHaveText('Die Simulation ist auf einen Fehler gestoßen und wurde beendet.', { timeout: 20_000 });
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'design');
|
||
});
|
||
|
||
test('speaks English when asked', async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls', '&lang=en');
|
||
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 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();
|
||
});
|