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>
106 lines
5.2 KiB
TypeScript
106 lines
5.2 KiB
TypeScript
// What the editor's browser tests share: the built page served once per file,
|
|
// and reading the surface (its transform, a device's place on the screen, the
|
|
// document's drawing through the development page's filiusDev).
|
|
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import type { Page } from '@playwright/test';
|
|
import { build, preview, type PreviewServer } from 'vite';
|
|
import type { DesktopView, DeviceSheet, TopologyView } from '@mdg-filius/doc';
|
|
|
|
const config = join(dirname(fileURLToPath(import.meta.url)), '../vite.config.ts');
|
|
|
|
/** The editor built into `outDir`, as the image's build builds it. */
|
|
export async function buildEditor(outDir: string): Promise<void> {
|
|
await build({ configFile: config, logLevel: 'silent', build: { outDir, emptyOutDir: true } });
|
|
}
|
|
|
|
export async function serveEditor(): Promise<{ readonly base: string; readonly close: () => Promise<void> }> {
|
|
await build({ configFile: config, logLevel: 'silent' });
|
|
const server: PreviewServer = await preview({ configFile: config, preview: { port: 0 }, logLevel: 'silent' });
|
|
return {
|
|
base: server.resolvedUrls!.local[0]!,
|
|
close: () => new Promise<void>((resolve) => server.httpServer.close(() => resolve())),
|
|
};
|
|
}
|
|
|
|
export async function openEditor(page: Page, base: string, example: string, query = ''): Promise<void> {
|
|
await page.goto(`${base}?example=${example}${query}`);
|
|
await page.waitForSelector('svg.filius-canvas');
|
|
await page.waitForFunction(() => (window as unknown as { filiusDev?: unknown }).filiusDev !== undefined);
|
|
}
|
|
|
|
/** The document's drawing. */
|
|
export function drawing(page: Page): Promise<TopologyView> {
|
|
return page.evaluate(() => (window as unknown as { filiusDev: { view: () => TopologyView } }).filiusDev.view());
|
|
}
|
|
|
|
/** How many changes the document has had since the page opened it. */
|
|
export function documentUpdates(page: Page): Promise<number> {
|
|
return page.evaluate(() => (window as unknown as { filiusDev: { updates: () => number } }).filiusDev.updates());
|
|
}
|
|
|
|
/** What the device sheet reads of a device. */
|
|
export function sheetOf(page: Page, id: string): Promise<DeviceSheet | undefined> {
|
|
return page.evaluate((each) => (window as unknown as { filiusDev: { sheet: (id: string) => DeviceSheet | undefined } }).filiusDev.sheet(each), id);
|
|
}
|
|
|
|
/** What a computer's desktop reads of the document. */
|
|
export function desktopOf(page: Page, id: string): Promise<DesktopView | undefined> {
|
|
return page.evaluate((each) => (window as unknown as { filiusDev: { desktop: (id: string) => DesktopView | undefined } }).filiusDev.desktop(each), id);
|
|
}
|
|
|
|
/** A device's button as data-box says it (surface pixels). */
|
|
export async function boxOf(page: Page, id: string): Promise<{ x: number; y: number; width: number; height: number }> {
|
|
const [x, y, width, height] = (await page.locator(`g.device[data-node="${id}"]`).getAttribute('data-box'))!.split(' ').map(Number) as [number, number, number, number];
|
|
return { x, y, width, height };
|
|
}
|
|
|
|
/** Where a device's icon's middle is on the screen. */
|
|
export async function iconCentre(page: Page, id: string): Promise<{ x: number; y: number }> {
|
|
const box = (await page.locator(`g.device[data-node="${id}"] image`).boundingBox())!;
|
|
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
}
|
|
|
|
/** A point of a device's button on the screen, by its share of the button's width and height. */
|
|
export async function onDevice(page: Page, id: string, across = 0.5, down = 0.4): Promise<{ x: number; y: number }> {
|
|
const box = (await page.locator(`g.device[data-node="${id}"] > rect`).first().boundingBox())!;
|
|
return { x: box.x + box.width * across, y: box.y + box.height * down };
|
|
}
|
|
|
|
/**
|
|
* In the simulation: a tap chooses the device, its three-dot button opens its
|
|
* menu, the item opens its sheet — a desktop, a table, the data exchange. A
|
|
* sheet already open covers the surface; it is closed first. With `touch`,
|
|
* by finger.
|
|
*/
|
|
export async function showFromMenu(page: Page, id: string, item: string | RegExp, touch = false): Promise<void> {
|
|
const open = page.locator('.simulation-sheet__close');
|
|
if ((await open.count()) > 0) await (touch ? open.tap() : open.click());
|
|
const at = await onDevice(page, id);
|
|
await (touch ? page.touchscreen.tap(at.x, at.y) : page.mouse.click(at.x, at.y));
|
|
const button = page.locator('.device-menu-button');
|
|
await (touch ? button.tap() : button.click());
|
|
const entry = page.getByRole('menuitem', { name: item, exact: true });
|
|
await (touch ? entry.tap() : entry.click());
|
|
}
|
|
|
|
/** A computer's desktop as the simulation's sheet (showFromMenu), in either language; its section. */
|
|
export async function showDesktop(page: Page, id: string, touch = false) {
|
|
await showFromMenu(page, id, /^(Desktop anzeigen|Show desktop)$/, touch);
|
|
return page.locator(`section[data-desktop="${id}"]`);
|
|
}
|
|
|
|
/** The surface's scale. */
|
|
export async function scaleOf(page: Page): Promise<number> {
|
|
return Number(await page.locator('[data-zoom]').getAttribute('data-zoom'));
|
|
}
|
|
|
|
/** Drags with the mouse from one point to another in steps. */
|
|
export async function drag(page: Page, from: { x: number; y: number }, to: { x: number; y: number }): Promise<void> {
|
|
await page.mouse.move(from.x, from.y);
|
|
await page.mouse.down();
|
|
await page.mouse.move(to.x, to.y, { steps: 8 });
|
|
await page.mouse.up();
|
|
}
|