Java's mode buttons start and stop the room, now in a worker of the editor's page (LocalSimHost), with Java's speed slider. While the simulation runs the design is locked as in Java: no palette, no sheet, nothing moves, no undo. Cables that carry a frame light up in Java's green, and a tap on a computer opens its desktop: Java's title, the software installation first, then the installed programs in the order of Java's list for the language, a taskbar with the network icon and the network as the simulation runs it. The room sends that over a new topic, node:<id>. Desktops stay over a return to the design and show again at the next start. The programs' windows follow from F4.6b. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
5.8 KiB
TypeScript
108 lines
5.8 KiB
TypeScript
// The drawing of a document against the project it holds, for every file of
|
|
// the corpus: the same devices in the same order at the same places, Java's
|
|
// labels, the cables as the export draws them (wireless or not), the
|
|
// documentation items in the model's render order.
|
|
|
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { describe, expect, it } from 'vitest';
|
|
import * as Y from 'yjs';
|
|
import { stableGeneratedMacAddress } from '@mdg-filius/core';
|
|
import { exportProject, openProjectArchive, type ImportedProject } from '@mdg-filius/fls';
|
|
import { addDevice, desktopView, readProject, setHostLabels, topologyView, writeProject } from '../src/index.js';
|
|
|
|
const corpus = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
|
const files = ['java-examples', 'java-tests', 'ipad-fixtures'].flatMap((folder) =>
|
|
readdirSync(join(corpus, folder))
|
|
.filter((name) => name.endsWith('.fls'))
|
|
.map((name) => join(folder, name)),
|
|
);
|
|
|
|
function docOf(file: string, change: (project: ImportedProject) => void = () => {}): Y.Doc {
|
|
let counter = 0;
|
|
const project = openProjectArchive(new Uint8Array(readFileSync(join(corpus, file))), () => `00000000-0000-0000-0000-${(0xffff_ffff - ++counter).toString(16).padStart(12, '0')}`);
|
|
change(project);
|
|
const doc = new Y.Doc();
|
|
writeProject(doc, project);
|
|
return doc;
|
|
}
|
|
|
|
describe('the drawing of a document', () => {
|
|
for (const file of files) {
|
|
it(file, () => {
|
|
const doc = docOf(file);
|
|
const project = readProject(doc);
|
|
const view = topologyView(doc);
|
|
expect(view.nodes.map((node) => [node.id, node.kind, node.name, node.x, node.y])).toEqual(
|
|
project.graph.nodes.map((node) => [node.id, node.kind, node.displayName, node.position.x, node.position.y]),
|
|
);
|
|
expect(view.nodes.map((node) => node.ports)).toEqual(project.graph.nodes.map((node) => node.ports.map((port) => ({ id: port.id, label: port.label }))));
|
|
expect(view.links.map((link) => [link.id, link.sourceNode, link.sourcePort, link.targetNode, link.targetPort])).toEqual(
|
|
project.graph.links.map((link) => [link.id, link.sourceNodeId, link.sourcePortId, link.targetNodeId, link.targetPortId]),
|
|
);
|
|
expect(view.links.map((link) => link.wireless)).toEqual(exportProject(project).topology.links.map((link) => link.wireless));
|
|
expect(view.docs.map(({ id, kind, x, y, width, height, text, color, font, size, bold, order }) => ({ id, kind, frame: { x, y, width, height }, text, color, fontName: font, fontSize: size, isBold: bold, order }))).toEqual(project.documentationItems);
|
|
});
|
|
}
|
|
|
|
it('draws the Internet of the NAT example as a cloud', () => {
|
|
const view = topologyView(docOf('java-examples/nat.fls'));
|
|
expect(view.nodes.filter((node) => node.cloud).map((node) => node.name)).toEqual(['Internet']);
|
|
});
|
|
|
|
it("writes a host's label as Java does: its name, address, MAC address or both", () => {
|
|
const labels = (policy: 'manual' | 'ipAddress' | 'macAddress' | 'ipAndMAC') => {
|
|
const doc = docOf('java-examples/zwei_rechnernetze_komplett.fls', (project) => {
|
|
for (const node of project.graph.nodes) node.hostLabelPolicy = policy;
|
|
});
|
|
const project = readProject(doc);
|
|
const notebook = project.graph.nodes.find((node) => node.displayName === 'Notebook 1.10')!;
|
|
const view = topologyView(doc);
|
|
return { label: view.nodes.find((node) => node.id === notebook.id)!.label, mac: notebook.ports[0]!.importedMacAddress ?? stableGeneratedMacAddress(notebook.ports[0]!.id), switchLabel: view.nodes.find((node) => node.kind === 'networkSwitch')!.label };
|
|
};
|
|
expect(labels('manual').label).toBe('Notebook 1.10');
|
|
expect(labels('ipAddress').label).toBe('141.99.1.10');
|
|
const { label, mac, switchLabel } = labels('ipAndMAC');
|
|
expect(label).toBe(`141.99.1.10 (${mac})`);
|
|
expect(labels('macAddress').label).toBe(mac);
|
|
// Only hosts show an address (Host.holeAnzeigeName).
|
|
expect(switchLabel).toBe('Switch 1');
|
|
});
|
|
});
|
|
|
|
describe("a computer's desktop", () => {
|
|
it('names its programs and its label, and whether the label is its address, for computers only', () => {
|
|
for (const file of files) {
|
|
const doc = docOf(file);
|
|
const project = readProject(doc);
|
|
for (const node of project.graph.nodes) {
|
|
const desktop = desktopView(doc, node.id);
|
|
if (node.kind !== 'pc' && node.kind !== 'notebook') {
|
|
expect(desktop, `${file} ${node.displayName}`).toBeUndefined();
|
|
continue;
|
|
}
|
|
expect(new Set(desktop!.programs), `${file} ${node.displayName}`).toEqual(project.installedPrograms.get(node.id) ?? new Set());
|
|
expect(desktop!.kind).toBe(node.kind);
|
|
expect(desktop!.label).toBe(topologyView(doc).nodes.find((each) => each.id === node.id)!.label);
|
|
expect(desktop!.addressAsName).toBe(node.hostLabelPolicy === 'ipAddress' || node.hostLabelPolicy === 'ipAndMAC');
|
|
}
|
|
}
|
|
expect(desktopView(docOf(files[0]!), 'nobody')).toBeUndefined();
|
|
});
|
|
|
|
it("takes a label of address and MAC address for the address, as Java's title does (isUseIPAsName), the MAC address alone not", () => {
|
|
const doc = new Y.Doc();
|
|
let counter = 0;
|
|
addDevice(doc, { kind: 'pc', id: 'pc', name: 'PC', x: 0, y: 0, newId: () => `port-${++counter}` }, 'me');
|
|
expect(desktopView(doc, 'pc')).toMatchObject({ kind: 'pc', label: 'PC', addressAsName: false, programs: [] });
|
|
setHostLabels(doc, 'pc', { ipAddress: true, macAddress: true }, 'me');
|
|
expect(desktopView(doc, 'pc')!.addressAsName).toBe(true);
|
|
setHostLabels(doc, 'pc', { ipAddress: false, macAddress: true }, 'me');
|
|
expect(desktopView(doc, 'pc')!.addressAsName).toBe(false);
|
|
setHostLabels(doc, 'pc', { ipAddress: true, macAddress: false }, 'me');
|
|
expect(desktopView(doc, 'pc')).toMatchObject({ label: '192.168.0.10', addressAsName: true });
|
|
});
|
|
});
|
|
|