feat(fls): topology of a .fls file as Java loads it
readTopology maps the decoded JavaBeans graph to devices, cables and documentation items the way SzenarioVerwaltung.netzwerkLaden does: the kind from the Java class (the palette label is not file data), devices without bounds at 0,0, interface values through Java's setters. All 23 corpus files match Java field by field. Found on the way: - listItems mixed a constructor's elements with added ones; a router's second interface was lost. Lists now replay add/set in order after the elements the constructor made. - The first 0 of a file may be SwingConstants.CENTER; measured like the getter defaults. - IP.ipCheck (javaIp.ts) measured on 46 cases: empty tokens skipped, signs, leading zeros and BMP digits of any script accepted. - The Java oracle took lower-case MACs in a file for random ones (8 interfaces); it now compares case-insensitively and reports the host name flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
10.0.0.1
|
||||
0.0.0.0
|
||||
255.255.255.255
|
||||
010.000.000.001
|
||||
+10.0.0.1
|
||||
-0.0.0.0
|
||||
-1.0.0.0
|
||||
10..0.0.1
|
||||
.10.0.0.1
|
||||
10.0.0.1.
|
||||
...10.0.0.1...
|
||||
10.0.0
|
||||
10.0.0.1.5
|
||||
256.0.0.1
|
||||
10.0.0.1
|
||||
10.0.0.1
|
||||
|
||||
.
|
||||
....
|
||||
1e1.0.0.1
|
||||
0x10.0.0.1
|
||||
10.0.0.1\u0000
|
||||
2147483648.0.0.1
|
||||
-2147483648.0.0.1
|
||||
99999999999999999999.0.0.1
|
||||
+.0.0.1
|
||||
-.0.0.1
|
||||
++1.0.0.1
|
||||
1 .0.0.1
|
||||
\u0661\u0660.0.0.1
|
||||
\u0967.\u0968.\u0969.\u096a
|
||||
\uff11\uff10.0.0.1
|
||||
\u0e51\u0e59\u0e52.168.0.1
|
||||
\u1040.\u1049.0.1
|
||||
\u07c1.0.0.1
|
||||
\u194f.0.0.1
|
||||
\u19d9.0.0.1
|
||||
\u19da.0.0.1
|
||||
\uabf9.0.0.1
|
||||
\u0de7.0.0.1
|
||||
\ud835\udfcf.0.0.1
|
||||
\u00b2.0.0.1
|
||||
\u2460.0.0.1
|
||||
\u1369.0.0.1
|
||||
\uff0e10.0.0.1
|
||||
10\u30020.0.1
|
||||
@@ -0,0 +1,186 @@
|
||||
[
|
||||
[
|
||||
"10.0.0.1",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"0.0.0.0",
|
||||
"0.0.0.0"
|
||||
],
|
||||
[
|
||||
"255.255.255.255",
|
||||
"255.255.255.255"
|
||||
],
|
||||
[
|
||||
"010.000.000.001",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"+10.0.0.1",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"-0.0.0.0",
|
||||
"0.0.0.0"
|
||||
],
|
||||
[
|
||||
"-1.0.0.0",
|
||||
null
|
||||
],
|
||||
[
|
||||
"10..0.0.1",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
".10.0.0.1",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"10.0.0.1.",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"...10.0.0.1...",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"10.0.0",
|
||||
null
|
||||
],
|
||||
[
|
||||
"10.0.0.1.5",
|
||||
null
|
||||
],
|
||||
[
|
||||
"256.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
" 10.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"10.0.0.1 ",
|
||||
null
|
||||
],
|
||||
[
|
||||
"",
|
||||
null
|
||||
],
|
||||
[
|
||||
".",
|
||||
null
|
||||
],
|
||||
[
|
||||
"....",
|
||||
null
|
||||
],
|
||||
[
|
||||
"1e1.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"0x10.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"10.0.0.1\\u0000",
|
||||
null
|
||||
],
|
||||
[
|
||||
"2147483648.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"-2147483648.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"99999999999999999999.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"+.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"-.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"++1.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"1 .0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"\\u0661\\u0660.0.0.1",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"\\u0967.\\u0968.\\u0969.\\u096a",
|
||||
"1.2.3.4"
|
||||
],
|
||||
[
|
||||
"\\uff11\\uff10.0.0.1",
|
||||
"10.0.0.1"
|
||||
],
|
||||
[
|
||||
"\\u0e51\\u0e59\\u0e52.168.0.1",
|
||||
"192.168.0.1"
|
||||
],
|
||||
[
|
||||
"\\u1040.\\u1049.0.1",
|
||||
"0.9.0.1"
|
||||
],
|
||||
[
|
||||
"\\u07c1.0.0.1",
|
||||
"1.0.0.1"
|
||||
],
|
||||
[
|
||||
"\\u194f.0.0.1",
|
||||
"9.0.0.1"
|
||||
],
|
||||
[
|
||||
"\\u19d9.0.0.1",
|
||||
"9.0.0.1"
|
||||
],
|
||||
[
|
||||
"\\u19da.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"\\uabf9.0.0.1",
|
||||
"9.0.0.1"
|
||||
],
|
||||
[
|
||||
"\\u0de7.0.0.1",
|
||||
"1.0.0.1"
|
||||
],
|
||||
[
|
||||
"\\ud835\\udfcf.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"\\u00b2.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"\\u2460.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"\\u1369.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"\\uff0e10.0.0.1",
|
||||
null
|
||||
],
|
||||
[
|
||||
"10\\u30020.0.1",
|
||||
null
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
// IP.ipCheck as Java FILIUS runs it: every case in ip-check.cases.txt was run
|
||||
// through Java (ip-check.java.json, tools/java-oracle) and must come out the
|
||||
// same here.
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { javaIpCheck, javaParseInt } from '../src/javaIp.js';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const measured = JSON.parse(readFileSync(join(here, 'ip-check.java.json'), 'utf8')) as [string, string | null][];
|
||||
|
||||
/** The cases file's escapes: a backslash, `u` and four hex digits for one UTF-16 unit. */
|
||||
function unescape(line: string): string {
|
||||
return line.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)));
|
||||
}
|
||||
|
||||
describe('javaIpCheck against Java', () => {
|
||||
it('covers every line of the cases file', () => {
|
||||
const lines = readFileSync(join(here, 'ip-check.cases.txt'), 'ascii').split('\n').slice(0, -1);
|
||||
expect(measured.map(([line]) => line)).toEqual(lines);
|
||||
});
|
||||
|
||||
for (const [line, expected] of measured) {
|
||||
it(JSON.stringify(line), () => {
|
||||
expect(javaIpCheck(unescape(line))).toBe(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('javaParseInt', () => {
|
||||
it('keeps to the int range', () => {
|
||||
expect(javaParseInt('2147483647')).toBe(2147483647);
|
||||
expect(javaParseInt('-2147483648')).toBe(-2147483648);
|
||||
expect(javaParseInt('2147483648')).toBeUndefined();
|
||||
expect(javaParseInt('-2147483649')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -126,18 +126,93 @@ describe('XMLDecoder rules', () => {
|
||||
|
||||
it('reads static fields, static factories, maps and arrays', () => {
|
||||
const [constant, manager, map, array] = decodeJavaBeans(
|
||||
doc(`<object class="javax.swing.SwingConstants" field="CENTER"/>
|
||||
doc(`<object class="javax.swing.SwingConstants" field="LEFT"/>
|
||||
<object class="javax.swing.ToolTipManager" method="sharedInstance"/>
|
||||
<object class="java.util.HashMap"><void method="put"><string>k</string><int>1</int></void></object>
|
||||
<array class="filius.hardware.Port" length="2"><void index="1"><string>b</string></void></array>`),
|
||||
);
|
||||
|
||||
expect(object(constant)).toMatchObject({ className: 'javax.swing.SwingConstants', staticField: 'CENTER' });
|
||||
expect(object(constant)).toMatchObject({ className: 'javax.swing.SwingConstants', staticField: 'LEFT' });
|
||||
expect(object(manager).factoryMethod).toBe('sharedInstance');
|
||||
expect(mapEntries(map)).toEqual([['k', 1]]);
|
||||
expect(listItems(array)).toEqual([null, 'b']);
|
||||
});
|
||||
|
||||
it('answers a measured static constant with its value, which later references share', () => {
|
||||
const [color, holder] = decodeJavaBeans(
|
||||
doc(`<object class="java.awt.Color" id="Color0">
|
||||
<object class="javax.swing.SwingConstants" field="CENTER" id="Integer0"/>
|
||||
<int>128</int><int>255</int>
|
||||
</object>
|
||||
<object class="java.util.ArrayList"><void method="add"><object idref="Integer0"/></void></object>`),
|
||||
);
|
||||
|
||||
expect(object(color).args).toEqual([0, 128, 255]);
|
||||
expect(listItems(holder)).toEqual([0]);
|
||||
});
|
||||
|
||||
it('keeps the elements a constructor made ahead of the ones the file adds', () => {
|
||||
// A Java router starts with one interface; the file changes it by index
|
||||
// and adds two more — three interfaces, in this order.
|
||||
const [node] = decodeJavaBeans(
|
||||
doc(`<object class="filius.hardware.knoten.Vermittlungsrechner">
|
||||
<void id="LinkedList0" property="netzwerkInterfaces">
|
||||
<void id="NetzwerkInterface0" index="0"><void property="ip"><string>10.0.0.1</string></void></void>
|
||||
<void method="add"><object class="filius.hardware.NetzwerkInterface" id="NetzwerkInterface1"/></void>
|
||||
<void method="add"><object class="filius.hardware.NetzwerkInterface" id="NetzwerkInterface2"/></void>
|
||||
</void>
|
||||
</object>`),
|
||||
);
|
||||
|
||||
const interfaces = listItems(object(node).properties.get('netzwerkInterfaces'));
|
||||
expect(interfaces.map((nic) => object(nic).id)).toEqual(['NetzwerkInterface0', 'NetzwerkInterface1', 'NetzwerkInterface2']);
|
||||
expect(object(interfaces[0]).properties.get('ip')).toBe('10.0.0.1');
|
||||
});
|
||||
|
||||
it('fills the constructed elements the file never mentions, in index order', () => {
|
||||
// A switch has 24 ports; the file names the three that carry a cable,
|
||||
// in any order.
|
||||
const [ports] = decodeJavaBeans(
|
||||
doc(`<object class="filius.hardware.knoten.Switch"><void id="LinkedList0" property="anschluesse">
|
||||
<void id="Port9" index="1"/><void id="Port10" index="0"/><void id="Port11" index="2"/>
|
||||
</void></object>`),
|
||||
);
|
||||
|
||||
const list = object(ports).properties.get('anschluesse');
|
||||
const items = listItems(list, 24);
|
||||
expect(items).toHaveLength(24);
|
||||
expect(items.slice(0, 3).map((port) => object(port).id)).toEqual(['Port10', 'Port9', 'Port11']);
|
||||
expect(object(items[3])).toMatchObject({ className: 'unknown', fromGetter: true });
|
||||
expect(listItems(list)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('replays add at an index and set by index in order', () => {
|
||||
const [list] = decodeJavaBeans(
|
||||
doc(`<object class="java.util.ArrayList">
|
||||
<void method="add"><string>a</string></void>
|
||||
<void method="add"><string>c</string></void>
|
||||
<void method="add"><int>1</int><string>b</string></void>
|
||||
<void index="2"><string>C</string></void>
|
||||
</object>`),
|
||||
);
|
||||
|
||||
expect(listItems(list)).toEqual(['a', 'b', 'C']);
|
||||
});
|
||||
|
||||
it('does not guess what an index means once the list was changed', () => {
|
||||
const [, holder] = decodeJavaBeans(
|
||||
doc(`<object class="filius.hardware.knoten.Rechner">
|
||||
<void id="LinkedList0" property="netzwerkInterfaces">
|
||||
<void method="add"><object class="filius.hardware.NetzwerkInterface"/></void>
|
||||
<void id="NetzwerkInterface5" index="3"/>
|
||||
</void>
|
||||
</object>
|
||||
<object class="java.util.ArrayList"><void method="add"><object idref="NetzwerkInterface5"/></void></object>`),
|
||||
);
|
||||
|
||||
expect(isUnresolved(listItems(holder)[0])).toBe(true);
|
||||
});
|
||||
|
||||
it('reports an idref to an id that was never defined', () => {
|
||||
const [value] = decodeJavaBeans(doc('<object idref="Missing0"/>'));
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// The topology of every corpus file must be what Java FILIUS loads
|
||||
// (corpus.java.json, tools/java-oracle): each device with class, name,
|
||||
// position and interfaces, each cable with both ends, each documentation
|
||||
// item — field by field. Then the rules the corpus does not exercise.
|
||||
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { decodeJavaBeans, readFlsArchive, readTopology, type FlsNodeClass, type FlsTopology } from '../src/index.js';
|
||||
|
||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||
const javaView = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, unknown>;
|
||||
|
||||
function flsFiles(directory: string): string[] {
|
||||
return readdirSync(directory)
|
||||
.flatMap((name) => {
|
||||
const path = join(directory, name);
|
||||
return statSync(path).isDirectory() ? flsFiles(path) : name.endsWith('.fls') ? [path] : [];
|
||||
})
|
||||
.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* The palette label Java shows: not read from the file but set from the
|
||||
* class while loading (setTyp(holeHardwareTyp())), in German.
|
||||
*/
|
||||
const TYPE_LABELS: Record<FlsNodeClass, string> = {
|
||||
Rechner: 'Rechner',
|
||||
Notebook: 'Notebook',
|
||||
Switch: 'Switch / WLAN',
|
||||
Vermittlungsrechner: 'Vermittlungsrechner',
|
||||
Gateway: 'Heimrouter',
|
||||
Modem: 'Modem',
|
||||
};
|
||||
|
||||
/** The topology in the shape FlsOracle.java writes, less what Swing computes (label width and height). */
|
||||
function asJavaView(topology: FlsTopology, javaBounds: (number[] | null)[]) {
|
||||
return {
|
||||
success: true,
|
||||
nodes: topology.nodes.map((node, index) => ({
|
||||
class: node.javaClass,
|
||||
name: node.name,
|
||||
label: TYPE_LABELS[node.javaClass],
|
||||
bounds: [node.x, node.y, ...(javaBounds[index]?.slice(2) ?? [])],
|
||||
...(node.javaClass === 'Rechner' || node.javaClass === 'Notebook'
|
||||
? { useIpAsName: node.useIpAsName, useMacAsName: node.useMacAsName }
|
||||
: {}),
|
||||
ports:
|
||||
node.javaClass === 'Switch' || node.javaClass === 'Modem'
|
||||
? Array.from({ length: node.portCount }, () => ({}))
|
||||
: node.interfaces.map((nic) => ({ ...nic, mac: nic.mac ?? null })),
|
||||
})),
|
||||
cables: topology.links.map((link) => ({
|
||||
ends: link.ends.map((end) => [end.node, end.port]),
|
||||
wireless: link.wireless,
|
||||
})),
|
||||
documentation: topology.documentation.map((item) => ({
|
||||
type: item.type,
|
||||
text: item.text,
|
||||
bounds: [item.x, item.y, item.width, item.height],
|
||||
color: item.color,
|
||||
font: item.font === null ? null : [item.font.name, item.font.size, item.font.style],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
describe('corpus: the topology as Java loads it', () => {
|
||||
for (const file of flsFiles(corpusRoot)) {
|
||||
const name = relative(corpusRoot, file);
|
||||
it(name, () => {
|
||||
const values = decodeJavaBeans(readFlsArchive(new Uint8Array(readFileSync(file))).configurationXml);
|
||||
const topology = readTopology(values);
|
||||
const expected = javaView[name] as { nodes: { bounds: number[] | null }[] };
|
||||
|
||||
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(expected);
|
||||
expect(topology.warnings).toEqual([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── Rules the corpus does not exercise ─────────────────────────────────────
|
||||
|
||||
function doc(body: string): string {
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n<java version="17" class="java.beans.XMLDecoder">\n${body}\n</java>\n`;
|
||||
}
|
||||
|
||||
function topologyOf(body: string): FlsTopology {
|
||||
return readTopology(decodeJavaBeans(doc(body)));
|
||||
}
|
||||
|
||||
function nodeItem(device: string, extra = ''): string {
|
||||
return `<void method="add"><object class="filius.gui.netzwerksicht.GUIKnotenItem">
|
||||
<void property="knoten">${device}</void>${extra}</object></void>`;
|
||||
}
|
||||
|
||||
describe('readTopology rules', () => {
|
||||
it('passes addresses through Java setIp: canonical, null when invalid, unchanged when null', () => {
|
||||
const { nodes } = topologyOf(`<string>Filius version: 2.9.4 (1.1.2026)</string>
|
||||
<object class="java.util.LinkedList">${nodeItem(`<object class="filius.hardware.knoten.Vermittlungsrechner">
|
||||
<void property="netzwerkInterfaces">
|
||||
<void index="0"><void property="ip"><string>010.0..0.1</string></void>
|
||||
<void property="subnetzMaske"><string>255.255.255.256</string></void></void>
|
||||
<void method="add"><object class="filius.hardware.NetzwerkInterface">
|
||||
<void property="ip"><null/></void><void property="mac"><string>0a:0b:0c:0d:0e:0f</string></void>
|
||||
</object></void>
|
||||
</void></object>`)}</object>`);
|
||||
|
||||
expect(nodes[0]!.interfaces).toEqual([
|
||||
{ ip: '10.0.0.1', mask: null, mac: undefined, gateway: '', dns: '' },
|
||||
{ ip: '192.168.0.10', mask: '255.255.255.0', mac: '0A:0B:0C:0D:0E:0F', gateway: '', dns: '' },
|
||||
]);
|
||||
});
|
||||
|
||||
it("gives only a Gateway's own first interface the WAN address, not one the file creates", () => {
|
||||
const own = topologyOf(`<object class="java.util.LinkedList">${nodeItem(
|
||||
'<object class="filius.hardware.knoten.Gateway"/>',
|
||||
)}</object>`);
|
||||
const replaced = topologyOf(`<object class="java.util.LinkedList">${nodeItem(`<object class="filius.hardware.knoten.Gateway">
|
||||
<void property="netzwerkInterfaces"><object class="java.util.LinkedList">
|
||||
<void method="add"><object class="filius.hardware.NetzwerkInterface"/></void>
|
||||
</object></void></object>`)}</object>`);
|
||||
|
||||
expect(own.nodes[0]!.interfaces.map((nic) => [nic.ip, nic.mask])).toEqual([
|
||||
['42.0.0.10', '255.0.0.0'],
|
||||
['192.168.0.10', '255.255.255.0'],
|
||||
]);
|
||||
expect(own.nodes[0]!.name).toBe('Heimrouter');
|
||||
expect(replaced.nodes[0]!.interfaces.map((nic) => nic.ip)).toEqual(['192.168.0.10']);
|
||||
});
|
||||
|
||||
it('places a device without bounds at 0,0 instead of leaving it out', () => {
|
||||
const { nodes, warnings } = topologyOf(
|
||||
`<object class="java.util.LinkedList">${nodeItem('<object class="filius.hardware.knoten.Notebook"/>')}</object>`,
|
||||
);
|
||||
|
||||
expect(nodes.map((node) => [node.javaClass, node.x, node.y, node.portCount])).toEqual([['Notebook', 0, 0, 1]]);
|
||||
expect(warnings).toEqual([]);
|
||||
});
|
||||
|
||||
it('leaves out an unknown device and a cable to a port no device owns, and says so', () => {
|
||||
const { nodes, links, warnings } = topologyOf(`<object class="java.util.LinkedList">
|
||||
${nodeItem('<object class="filius.hardware.knoten.Drucker"/>')}
|
||||
${nodeItem(`<object class="filius.hardware.knoten.Switch"><void property="anschluesse">
|
||||
<void id="Port0" index="0"/><void id="Port1" index="1"/></void></object>`)}
|
||||
</object>
|
||||
<object class="java.util.LinkedList">
|
||||
<void method="add"><object class="filius.gui.netzwerksicht.GUIKabelItem"><void property="dasKabel">
|
||||
<object class="filius.hardware.Kabel"><object idref="Port0"/><object idref="Port1"/></object></void></object></void>
|
||||
<void method="add"><object class="filius.gui.netzwerksicht.GUIKabelItem"><void property="dasKabel">
|
||||
<object class="filius.hardware.Kabel"><object idref="Port0"/><object class="filius.hardware.Port"/></object></void></object></void>
|
||||
</object>`);
|
||||
|
||||
expect(nodes.map((node) => node.javaClass)).toEqual(['Switch']);
|
||||
expect(links).toEqual([{ ends: [{ node: 0, port: 0 }, { node: 0, port: 1 }], wireless: false }]);
|
||||
expect(warnings).toEqual([
|
||||
'node 1: unknown device class filius.hardware.knoten.Drucker, left out',
|
||||
'cable 2: an end is not a port of a device, left out',
|
||||
]);
|
||||
});
|
||||
|
||||
it('reads the lists by position and only when the first element fits, as netzwerkLaden does', () => {
|
||||
// No version line (files before 1.3.0); an empty cable list still takes its place.
|
||||
const topology = topologyOf(`<object class="java.util.LinkedList">${nodeItem(
|
||||
'<object class="filius.hardware.knoten.Rechner"/>',
|
||||
)}</object>
|
||||
<object class="java.util.LinkedList"/>
|
||||
<object class="java.util.LinkedList"><void method="add"><object class="filius.gui.netzwerksicht.GUIDocuItem">
|
||||
<void property="type"><int>1</int></void></object></void></object>`);
|
||||
const wrongFirst = topologyOf(`<object class="java.util.LinkedList">
|
||||
<void method="add"><string>not a node</string></void>
|
||||
${nodeItem('<object class="filius.hardware.knoten.Rechner"/>')}</object>`);
|
||||
|
||||
expect(topology.versionLine).toBeNull();
|
||||
expect(topology.nodes).toHaveLength(1);
|
||||
expect(topology.documentation.map((item) => item.type)).toEqual([1]);
|
||||
expect(wrongFirst.nodes).toEqual([]);
|
||||
});
|
||||
|
||||
it("reads colors as Java's constructors do and keeps an invalid one unset", () => {
|
||||
const colors = ['<int>16711935</int>', '<int>1</int><int>2</int><int>3</int>', '<int>1</int><int>2</int><int>300</int><int>4</int>'];
|
||||
const { documentation, warnings } = topologyOf(`<object class="java.util.LinkedList"/><object class="java.util.LinkedList"/>
|
||||
<object class="java.util.LinkedList">${colors
|
||||
.map(
|
||||
(args) => `<void method="add"><object class="filius.gui.netzwerksicht.GUIDocuItem">
|
||||
<void property="color"><object class="java.awt.Color">${args}</object></void>
|
||||
<void property="font"><object class="java.awt.Font"><string>Dialog</string><int>5</int><int>14</int></object></void>
|
||||
</object></void>`,
|
||||
)
|
||||
.join('')}</object>`);
|
||||
|
||||
expect(documentation.map((item) => item.color)).toEqual([[255, 0, 255, 255], [1, 2, 3, 255], null]);
|
||||
expect(documentation[0]!.font).toEqual({ name: 'Dialog', size: 14, style: 0 });
|
||||
expect(warnings).toEqual(['documentation item: color component outside 0…255, left unset (as in Java)']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user