// 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([[], [], []]); }); });