Files
mdg-filius/packages/editor/browser/text.spec.ts
T
julian.kiedaischandClaude Opus 5.5 16d9cc2095 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>
2026-10-10 17:18:25 +00:00

167 lines
9.6 KiB
TypeScript

// The text editor on a computer's desktop in Chromium (F4.6f2), on
// browser/fixtures/web-pages.fls (test/webPagesFixture.test.ts): installed
// while the simulation runs, a page written and saved through Java's file
// dialog into the web server's folder, fetched by the other computer's
// browser, changed while it runs and fetched again; the caret where one
// types, the window's own undo, what the dialog refuses, a file removed by
// the command line while it is open, „Neu“.
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { expect, test, type Page } from '@playwright/test';
import { drawing, onDevice, serveEditor, showDesktop } from './editorPage.js';
let server: { readonly base: string; readonly close: () => Promise<void> };
test.beforeAll(async () => {
server = await serveEditor();
});
test.afterAll(async () => {
await server.close();
});
/** What the development page lets a test do: write into a file as another would. */
interface DevPage {
readonly insertText: (id: string, path: string, index: number, text: string) => void;
}
const FIXTURE = join(dirname(fileURLToPath(import.meta.url)), 'fixtures/web-pages.fls');
async function openPages(page: Page): Promise<void> {
await page.goto(`${server.base}?example=new`);
await page.waitForSelector('svg.filius-canvas');
await page.locator('input[type="file"]').setInputFiles(FIXTURE);
await expect.poll(async () => (await drawing(page).catch(() => ({ nodes: [] }))).nodes.length).toBe(4);
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
}
/** A computer's desktop, opened with a tap. */
async function desktopOf(page: Page, name: string) {
const id = (await drawing(page)).nodes.find((node) => node.name === name)!.id;
return showDesktop(page, id);
}
test('writes a page into the web server, which serves it while the simulation runs, and changes it there', async ({ page }) => {
test.setTimeout(120_000);
await openPages(page);
const desktop = await desktopOf(page, 'Server');
// Installed while it runs, with the command line beside it.
await desktop.locator('[data-program="installation"]').click();
const installation = desktop.locator('[data-program-window="installation"]');
const available = installation.locator('[data-list="available"] [role="option"]');
await available.filter({ hasText: 'Text-Editor' }).click();
await available.filter({ hasText: 'Befehlszeile' }).click();
await installation.getByRole('button', { name: 'Installieren' }).click();
await installation.getByRole('button', { name: 'Änderungen annehmen' }).click();
await desktop.locator('[data-program="textEditor"]').click();
const window = desktop.locator('[data-program-window="textEditor"]');
const title = window.locator('.program-titlebar h3');
const text = window.getByRole('textbox', { name: 'Text' });
await expect(title).toHaveText('Text-Editor');
await expect(window.locator('.text-editor-bar button')).toHaveText(['Neu', 'Öffnen', 'Speichern', 'Speichern unter']);
await expect(text).toHaveCSS('font-size', '16px');
// A new text: „Speichern“ asks for a name in Java's dialog; a tap opens a folder.
await text.fill('<html><head><title>Neu</title></head><body>Erster Stand</body></html>');
await window.getByRole('button', { name: 'Speichern', exact: true }).click();
const dialog = window.getByRole('dialog', { name: 'Datei Speichern' });
await expect(dialog.locator('.file-dialog-folder span')).toHaveText('Ordner: /');
await expect(dialog.getByRole('button', { name: 'Ordner aufwärts' })).toBeDisabled();
await expect(dialog.locator('.file-dialog-entries button')).toHaveText(['webserver']);
await dialog.locator('.file-dialog-entries button', { hasText: 'webserver' }).click();
await expect(dialog.locator('.file-dialog-folder span')).toHaveText('Ordner: /webserver');
await expect(dialog.locator('.file-dialog-entries button')).toHaveText(['ordner', 'index.html', 'splashscreen-mini.png', 'ziel.html', 'zwei.html']);
// No name: said in the dialog, which stays.
await dialog.getByRole('button', { name: 'Speichern' }).click();
await expect(dialog.getByRole('alert')).toHaveText('Geben Sie einen Dateinamen ein.');
await dialog.getByRole('textbox', { name: 'Dateiname:' }).fill('neu.html');
await dialog.getByRole('button', { name: 'Speichern' }).click();
await expect(dialog).toHaveCount(0);
await expect(title).toHaveText('Text-Editor - neu.html');
// Saved as it is typed: „Speichern“ has nothing left to do.
await expect(window.getByRole('button', { name: 'Speichern', exact: true })).toBeDisabled();
// The other computer's browser fetches it from the running web server (the desktops closed in between, which covered the computers).
const fetchPage = async (expected: string) => {
await desktop.locator('.simulation-sheet__close').click();
const browserDesktop = await desktopOf(page, 'Browser');
await browserDesktop.locator('[data-program="webBrowser"]').click();
const browser = browserDesktop.locator('[data-program-window="webBrowser"]');
const address = browser.getByRole('textbox', { name: 'Adresse' });
await address.fill('192.168.0.20/neu.html');
await address.press('Enter');
await expect(browser.frameLocator('iframe[title="Seite"]').locator('body')).toHaveText(expected, { timeout: 30_000 });
await browserDesktop.locator('.simulation-sheet__close').click();
await desktopOf(page, 'Server');
await desktop.locator('[data-program="textEditor"]').click();
};
await fetchPage('Erster Stand');
// The window is kept over the desktop closed, as Java's desktop keeps it.
await expect(title).toHaveText('Text-Editor - neu.html');
// Changed in the middle, the caret where one types: the server serves it at once.
const at = (await text.inputValue()).indexOf('Erster');
await text.evaluate((area: HTMLTextAreaElement, index) => {
area.focus();
area.setSelectionRange(index, index + 'Erster'.length);
}, at);
await page.keyboard.type('Zweiter');
await expect(text).toHaveValue('<html><head><title>Neu</title></head><body>Zweiter Stand</body></html>');
await fetchPage('Zweiter Stand');
// Another writes before the caret: the caret moves with its text, and one types on where one was.
const id = (await drawing(page)).nodes.find((node) => node.name === 'Server')!.id;
await text.evaluate((area: HTMLTextAreaElement, index) => {
area.focus();
area.setSelectionRange(index, index);
}, at + 'Zweiter'.length);
await page.evaluate(([node]) => (window as unknown as { filiusDev: DevPage }).filiusDev.insertText(node!, '/webserver/neu.html', 0, '<!-- x -->'), [id]);
await expect(text).toHaveValue('<!-- x --><html><head><title>Neu</title></head><body>Zweiter Stand</body></html>');
await page.keyboard.type('!');
await expect(text).toHaveValue('<!-- x --><html><head><title>Neu</title></head><body>Zweiter! Stand</body></html>');
await page.keyboard.press('Backspace');
await text.evaluate((area: HTMLTextAreaElement) => area.blur());
await expect(text).toHaveValue('<!-- x --><html><head><title>Neu</title></head><body>Zweiter Stand</body></html>');
// The window's own undo takes back its typing.
await text.press('Control+z');
await expect(text).toHaveValue('<!-- x --><html><head><title>Neu</title></head><body>Erster Stand</body></html>');
await text.press('Control+y');
await expect(text).toHaveValue('<!-- x --><html><head><title>Neu</title></head><body>Zweiter Stand</body></html>');
// „Öffnen“: a name that is not there and an image are refused, the open file stays.
await window.getByRole('button', { name: 'Öffnen' }).click();
const opening = window.getByRole('dialog', { name: 'Datei Öffnen' });
await opening.getByRole('textbox', { name: 'Dateiname:' }).fill('fehlt.txt');
await opening.getByRole('button', { name: 'Öffnen' }).click();
await expect(opening.getByRole('alert')).toHaveText('Die Datei „fehlt.txt“ gibt es nicht.');
await opening.locator('.file-dialog-entries button', { hasText: 'webserver' }).click();
await opening.locator('.file-dialog-entries button', { hasText: 'splashscreen-mini.png' }).click();
await expect(opening.getByRole('textbox', { name: 'Dateiname:' })).toHaveValue('splashscreen-mini.png');
await opening.getByRole('button', { name: 'Öffnen' }).click();
await expect(opening.getByRole('alert')).toHaveText('„splashscreen-mini.png“ ist keine Textdatei.');
await opening.getByRole('button', { name: 'Abbrechen' }).click();
await expect(title).toHaveText('Text-Editor - neu.html');
// Removed by the command line while open: the text stays as a new text, „Speichern“ asks for a name.
await desktop.locator('.desktop-home').click();
await desktop.locator('[data-program="commandPrompt"]').click();
const input = desktop.locator('input[name="terminalInput"]');
await input.fill('rm /webserver/neu.html');
await input.press('Enter');
await desktop.locator('.desktop-home').click();
await desktop.locator('[data-program="textEditor"]').click();
await expect(title).toHaveText('Text-Editor');
await expect(window.getByRole('status')).toHaveText('Die Datei „neu.html“ wurde gelöscht. Der Text ist noch da; „Speichern“ legt ihn als neue Datei ab.');
await expect(text).toHaveValue('<!-- x --><html><head><title>Neu</title></head><body>Zweiter Stand</body></html>');
await expect(window.getByRole('button', { name: 'Speichern', exact: true })).toBeEnabled();
// „Neu“: empty, no file.
await window.getByRole('button', { name: 'Neu' }).click();
await expect(text).toHaveValue('');
await expect(window.getByRole('status')).toHaveCount(0);
});