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>
111 lines
5.6 KiB
TypeScript
111 lines
5.6 KiB
TypeScript
// Gnutella on two notebooks' desktops in Chromium (F4.6g4), on FILIUS'
|
||
// example email_komplett with the program installed during the run: Java's
|
||
// four tabs, an address refused with Java's words, a peer joined, a file
|
||
// found on the other notebook and downloaded (the files tab shows it), the
|
||
// number of peers saved (also one Java refuses, and what is no number), the
|
||
// network left.
|
||
|
||
import { expect, test, type Locator, type Page } from '@playwright/test';
|
||
import { drawing, onDevice, openEditor, 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();
|
||
});
|
||
|
||
async function idOf(page: Page, name: string) {
|
||
return (await drawing(page)).nodes.find((node) => node.name === name)!.id;
|
||
}
|
||
|
||
async function desktopOf(page: Page, name: string) {
|
||
const id = await idOf(page, name);
|
||
return showDesktop(page, id);
|
||
}
|
||
|
||
async function install(desktop: Locator, program: string) {
|
||
await desktop.locator('[data-program="installation"]').click();
|
||
const installation = desktop.locator('[data-program-window="installation"]');
|
||
await installation.locator('[data-list="available"] [role="option"]').filter({ hasText: program }).click();
|
||
await installation.getByRole('button', { name: 'Installieren' }).click();
|
||
await installation.getByRole('button', { name: 'Änderungen annehmen' }).click();
|
||
}
|
||
|
||
test('joins, searches and downloads as Java’s Gnutella window does', async ({ page }) => {
|
||
test.setTimeout(120_000);
|
||
await openEditor(page, server.base, 'email_komplett.fls');
|
||
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
|
||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||
// The other notebook shares a file.
|
||
const other = await desktopOf(page, 'Rechner 2');
|
||
await install(other, 'Gnutella');
|
||
await expect(other.locator('[data-program="gnutella"]')).toBeVisible();
|
||
const otherId = await idOf(page, 'Rechner 2');
|
||
await page.evaluate(
|
||
(id) => (window as unknown as { filiusDev: { writeFile: (id: string, path: string, text: string) => void } }).filiusDev.writeFile(id, '/peer2peer/lied.txt', 'La la la'),
|
||
otherId,
|
||
);
|
||
await other.locator('.simulation-sheet__close').click();
|
||
|
||
const desktop = await desktopOf(page, 'Rechner 1');
|
||
await install(desktop, 'Gnutella');
|
||
await desktop.locator('[data-program="gnutella"]').click();
|
||
const window = desktop.locator('[data-program-window="gnutella"]');
|
||
await expect(window.locator('.program-titlebar h3')).toHaveText('Gnutella');
|
||
await expect(window.getByRole('tab')).toHaveText(['Netzwerk', 'Suche', 'Dateien', 'Einstellungen']);
|
||
|
||
// What is no address: red, and Java's words.
|
||
const address = window.getByRole('textbox', { name: 'Teilnehmer IP-Adresse:' });
|
||
await expect(address).toHaveValue('192.168.0.1');
|
||
await address.fill('141.99.50.300');
|
||
await expect(address).toHaveAttribute('aria-invalid', 'true');
|
||
await window.getByRole('button', { name: 'Netz beitreten' }).click();
|
||
await expect(window.getByRole('alert')).toHaveText('Dies ist keine gültige IP-Adresse');
|
||
await address.fill('141.99.50.182');
|
||
await window.getByRole('button', { name: 'Netz beitreten' }).click();
|
||
const peers = window.getByRole('table', { name: 'Liste der verbundenen Nachbarn:' }).locator('tbody tr');
|
||
await expect(peers).toHaveCount(1, { timeout: 30_000 });
|
||
await expect(peers.first().locator('td')).toHaveText(['1', '141.99.50.182']);
|
||
|
||
// The other's file found and downloaded.
|
||
await window.getByRole('tab', { name: 'Suche' }).click();
|
||
await window.getByRole('textbox', { name: 'Suchbegriff:' }).fill('lied');
|
||
await window.getByRole('button', { name: 'Suchen' }).click();
|
||
const results = window.getByRole('table', { name: 'Suche' }).locator('tbody tr');
|
||
await expect(results).toHaveCount(1, { timeout: 30_000 });
|
||
await expect(results.first().locator('td')).toHaveText(['141.99.50.182', 'lied.txt', '8 B']);
|
||
const download = window.getByRole('button', { name: 'Herunterladen' });
|
||
await expect(download).toBeDisabled();
|
||
await results.first().dispatchEvent('pointerdown');
|
||
await download.click();
|
||
// The list emptied: nothing is chosen any more.
|
||
await window.getByRole('button', { name: 'Liste leeren' }).click();
|
||
await expect(results).toHaveCount(0);
|
||
await expect(download).toBeDisabled();
|
||
await window.getByRole('tab', { name: 'Dateien' }).click();
|
||
await expect(window.getByRole('table', { name: 'Dateien' }).locator('tbody tr td')).toHaveText(['lied.txt', 'txt'], { timeout: 30_000 });
|
||
|
||
// The number of peers: 12 saved (Java takes one digit only), what is no number refused with Java's words.
|
||
await window.getByRole('tab', { name: 'Einstellungen' }).click();
|
||
const maximum = window.getByRole('textbox', { name: 'Maximale Anzahl direkter Nachbarn:' });
|
||
await expect(maximum).toHaveValue('3');
|
||
await maximum.fill('012');
|
||
await window.getByRole('button', { name: 'Speichern' }).click();
|
||
// The field shows the program's number once it is saved.
|
||
await expect(maximum).toHaveValue('12');
|
||
await expect(window.getByRole('alert')).toHaveCount(0);
|
||
await maximum.fill('x');
|
||
await expect(maximum).toHaveAttribute('aria-invalid', 'true');
|
||
await window.getByRole('button', { name: 'Speichern' }).click();
|
||
await expect(window.getByRole('alert')).toHaveText('Es sind nur Zahlen erlaubt!');
|
||
|
||
// The network left.
|
||
await window.getByRole('tab', { name: 'Netzwerk' }).click();
|
||
await window.getByRole('button', { name: 'Netz verlassen' }).click();
|
||
await expect(peers).toHaveCount(0);
|
||
});
|