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>
169 lines
9.7 KiB
TypeScript
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);
|
|
});
|