Files
mdg-filius/packages/editor/browser/text.spec.ts
T
julian.kiedaischandClaude Opus 5.5 2c7733b575 feat: F4.6f2 the text editor on the document's Y.Text, files both ways while the simulation runs
Oracle TextEditorWindowRun uses Java's text editor window and its file
dialog as a person does. The open file's text is its Y.Text in the
document (plan 7.3): typed is in the file at once, for everyone and for
the running engine. Core openText/saveTextAs without Java's mistakes
(lost saves after save-as onto a file or a removed file, files named
'ordner/d.txt' or ''); doc files.ts (changeFiles, fileText, nodeFiles,
readFilesInto); the room takes a person's files into the engine at once
and writes the engine's files back at the tick they changed; the editor's
window, Java's dialog, the text area following others' changes with the
caret in place, its own undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:50:27 +00:00

169 lines
9.7 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 } 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;
const at = await onDevice(page, id);
await page.mouse.click(at.x, at.y);
return page.locator(`section[data-desktop="${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('.desktop-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('.desktop-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);
});