feat: F4.6g4 the Gnutella window, measured against Java's

GnutellaWindowRun uses Java's Gnutella window on terminal-net beside the
line of GnutellaRun's story "search". The core cancels a search as Java
does (every connection of the program's own requests closed, passed-on
queries kept), lets the number of peers change and lists /peer2peer's
files; the room takes the window's actions and sends its view; the editor
shows Java's four tabs with Java's symbols. Not adopted from Java: a number
of peers of one digit only, a results table that keeps the last search's
rows, a files table that lags behind the folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 21:39:29 +00:00
co-authored by Claude Opus 5.5
parent 2b85e7432c
commit 5224e1a28b
24 changed files with 2211 additions and 17 deletions
+112
View File
@@ -0,0 +1,112 @@
// 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 } 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);
const at = await onDevice(page, id);
await page.mouse.click(at.x, at.y);
return page.locator(`section[data-desktop="${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('.desktop-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);
});