Files
mdg-filius/packages/doc/test/view.test.ts
T
julian.kiedaischandClaude Opus 5.5 56cdb4926e feat: F4.8a the documentation items in model and file, measured against Java's
New oracle DocuRun on Java's JDocuElement. Italic is kept (the model knew
only bold), an item keeps its file's size and font size (Swift's 32 x 24
and 8-72 changed Java's files), an item without a colour is Java's light
grey. Java's new items, colour lists, font sizes and styles, the drag's
smallest sizes and Java's stacking; document commands for the editor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:42:21 +00:00

157 lines
9.1 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 { inJavaDrawingOrder, stableGeneratedMacAddress } from '@mdg-filius/core';
import { exportProject, openProjectArchive, type ImportedProject } from '@mdg-filius/fls';
import { addDevice, captureCards, desktopView, readProject, renameDevice, setHostAddress, setHostLabels, setInterfaceAddress, 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));
// In Java's drawing order, the last on top.
expect(
view.docs.map(({ id, kind, x, y, width, height, text, color, font, size, bold, italic, order }) => ({
id,
kind,
frame: { x, y, width, height },
text,
color,
fontName: font,
fontSize: size,
isBold: bold,
isItalic: italic,
order,
})),
).toEqual(inJavaDrawingOrder(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 });
});
it("offers each card's data exchange with the title of Java's tab: the address where the label is it, else the label and the address", () => {
// Java's titles (packages/core/test/exchangeWindow.java.json): "Anna - 192.168.0.20", a computer named so; "192.168.0.30", one labelled by its address.
const java = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../core/test/exchangeWindow.java.json'), 'utf8')) as { steps: { step: string; titles: string[] }[] };
const doc = new Y.Doc();
let counter = 0;
const newId = () => `port-${++counter}`;
addDevice(doc, { kind: 'pc', id: 'anna', name: 'Anna', x: 0, y: 0, newId }, 'me');
setHostAddress(doc, 'anna', 'ipAddress', '192.168.0.20', 'me');
addDevice(doc, { kind: 'notebook', id: 'server', name: 'Server', x: 0, y: 0, newId }, 'me');
setHostAddress(doc, 'server', 'ipAddress', '192.168.0.30', 'me');
setHostLabels(doc, 'server', { ipAddress: true, macAddress: false }, 'me');
expect([...captureCards(doc, 'anna'), ...captureCards(doc, 'server')].map((card) => card.title)).toEqual(java.steps.find((step) => step.step === 'the server\'s card beside it')!.titles);
expect(captureCards(doc, 'anna')).toEqual([{ portId: 'port-1', ipAddress: '192.168.0.20', title: 'Anna - 192.168.0.20' }]);
// Address and MAC address: the address; the MAC address alone: the label, which is it.
setHostLabels(doc, 'server', { ipAddress: true, macAddress: true }, 'me');
expect(captureCards(doc, 'server')[0]!.title).toBe('192.168.0.30');
setHostLabels(doc, 'server', { ipAddress: false, macAddress: true }, 'me');
expect(captureCards(doc, 'server')[0]!.title).toBe(`${stableGeneratedMacAddress('port-2')} - 192.168.0.30`);
// A router's cards in the order of its ports, each with its own address; a home router's WAN card first.
addDevice(doc, { kind: 'router', id: 'router', name: 'Vermittlungsrechner', x: 0, y: 0, interfaces: 2, newId }, 'me');
const [first, second] = topologyView(doc).nodes.find((node) => node.id === 'router')!.ports;
setInterfaceAddress(doc, 'router', second!.id, 'ipAddress', '10.0.0.1', 'me');
renameDevice(doc, 'router', 'R1', 'me');
expect(captureCards(doc, 'router').map((card) => [card.portId, card.title])).toEqual([
[first!.id, `R1 - ${captureCards(doc, 'router')[0]!.ipAddress}`],
[second!.id, 'R1 - 10.0.0.1'],
]);
addDevice(doc, { kind: 'gateway', id: 'gateway', name: 'Heimrouter', x: 0, y: 0, newId }, 'me');
expect(captureCards(doc, 'gateway').map((card) => card.portId)).toEqual(topologyView(doc).nodes.find((node) => node.id === 'gateway')!.ports.map((port) => port.id));
// No cards: a switch, a modem; no device.
addDevice(doc, { kind: 'networkSwitch', id: 'switch', name: 'Switch', x: 0, y: 0, newId }, 'me');
addDevice(doc, { kind: 'remoteLink', id: 'modem', name: 'Modem', x: 0, y: 0, newId }, 'me');
expect([captureCards(doc, 'switch'), captureCards(doc, 'modem'), captureCards(doc, 'nobody')]).toEqual([[], [], []]);
});
});