Skip to content

Commit 193d4bf

Browse files
authored
test: introduce tests for model encode/decode (maxGraph#183)
The code managing the "model encode/decode" is not yet fully operational. This set of changes adds tests to replicate current behavior and provide the infrastructure for problem resolution. In particular, the tests show that some errors occur on import because some Codecs are not registered. There is no error if an export has already been performed. **Other info** The tests are done using both the model alone or a model linked to a Graph instance. Using only the model shows that some properties of objects in the model are not set correctly: they are not decoded and are stored as `Element` instances. The test involving the Graph instance shows the behavior seen in "real" application which are the consequences of the issue during the import.
1 parent 0b4df83 commit 193d4bf

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
Copyright 2023-present The maxGraph project Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { describe, expect, test } from '@jest/globals';
18+
import { Cell, Codec, Geometry, Graph, GraphDataModel, Point } from '../../src';
19+
import { getPrettyXml, parseXml } from '../../src/util/xmlUtils';
20+
21+
type ModelExportOptions = {
22+
/**
23+
* @default true
24+
*/
25+
pretty?: boolean;
26+
};
27+
28+
/**
29+
* Convenient utility class using {@link Codec} to manage maxGraph model import and export.
30+
*
31+
* @internal
32+
* @alpha subject to change (class and method names)
33+
*/
34+
class ModelXmlSerializer {
35+
// Include 'XML' in the class name as there were past discussions about supporting other format (JSON for example {@link https://github.com/maxGraph/maxGraph/discussions/60}).
36+
constructor(private dataModel: GraphDataModel) {}
37+
38+
import(xml: string): void {
39+
const doc = parseXml(xml);
40+
new Codec(doc).decode(doc.documentElement, this.dataModel);
41+
}
42+
43+
export(options?: ModelExportOptions): string {
44+
const encodedNode = new Codec().encode(this.dataModel);
45+
return options?.pretty ?? true
46+
? getPrettyXml(encodedNode)
47+
: getPrettyXml(encodedNode, '', '', '');
48+
}
49+
}
50+
51+
// inspired by VertexMixin.createVertex
52+
const newVertex = (id: string, value: string) => {
53+
const vertex = new Cell(value);
54+
vertex.setId(id);
55+
vertex.setVertex(true);
56+
return vertex;
57+
};
58+
59+
// inspired by EdgeMixin.createEdge
60+
const newEdge = (id: string, value: string) => {
61+
const edge = new Cell(value, new Geometry());
62+
edge.setId(id);
63+
edge.setEdge(true);
64+
return edge;
65+
};
66+
67+
const getParent = (model: GraphDataModel) => {
68+
// As done in the Graph object
69+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- here we know that model is not null
70+
return model.getRoot()!.getChildAt(0);
71+
};
72+
73+
// Adapted from https://github.com/maxGraph/maxGraph/issues/178
74+
const xmlFromIssue178 = `<GraphDataModel>
75+
<root>
76+
<Cell id="0">
77+
<Object as="style"/>
78+
</Cell>
79+
<Cell id="1" parent="0">
80+
<Object as="style"/>
81+
</Cell>
82+
<Cell id="B_#0" value="rootNode" vertex="1" parent="1">
83+
<Geometry _x="100" _y="100" _width="100" _height="80" as="geometry"/>
84+
<!-- not in the xml of issue 178, same issue as with Geometry -->
85+
<Object fillColor="green" strokeWidth="4" shape="triangle" as="style" />
86+
</Cell>
87+
</root>
88+
</GraphDataModel>`;
89+
90+
describe('import before the export (reproduce https://github.com/maxGraph/maxGraph/issues/178)', () => {
91+
test('only use GraphDataModel', () => {
92+
const model = new GraphDataModel();
93+
new ModelXmlSerializer(model).import(xmlFromIssue178);
94+
95+
const cell = model.getCell('B_#0');
96+
expect(cell).not.toBeNull();
97+
expect(cell?.value).toEqual('rootNode');
98+
expect(cell?.vertex).toEqual(1); // FIX should be set to true
99+
expect(cell?.isVertex()).toBeTruthy();
100+
expect(cell?.getParent()?.id).toEqual('1');
101+
const geometry = <Element>(<unknown>cell?.geometry); // FIX should be new Geometry(100, 100, 100, 80)
102+
expect(geometry.getAttribute('_x')).toEqual('100');
103+
expect(geometry.getAttribute('_y')).toEqual('100');
104+
expect(geometry.getAttribute('_height')).toEqual('80');
105+
expect(geometry.getAttribute('_width')).toEqual('100');
106+
107+
const style = <Element>(<unknown>cell?.style); // FIX should be { fillColor: 'green', shape: 'triangle', strokeWidth: 4, }
108+
expect(style.getAttribute('fillColor')).toEqual('green');
109+
expect(style.getAttribute('shape')).toEqual('triangle');
110+
expect(style.getAttribute('strokeWidth')).toEqual('4');
111+
});
112+
113+
test('use Graph - reproduced what is described in issue 178', () => {
114+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
115+
const graph = new Graph(null!);
116+
expect(() =>
117+
new ModelXmlSerializer(graph.getDataModel()).import(xmlFromIssue178)
118+
).toThrow(new Error('Invalid x supplied.'));
119+
});
120+
});
121+
122+
describe('export', () => {
123+
test('empty model exported as pretty XML', () => {
124+
expect(new ModelXmlSerializer(new GraphDataModel()).export()).toEqual(
125+
`<GraphDataModel>
126+
<root>
127+
<Cell id="0">
128+
<Object as="style" />
129+
</Cell>
130+
<Cell id="1" parent="0">
131+
<Object as="style" />
132+
</Cell>
133+
</root>
134+
</GraphDataModel>
135+
`
136+
);
137+
});
138+
139+
test('empty model exported as non pretty XML', () => {
140+
expect(
141+
new ModelXmlSerializer(new GraphDataModel()).export({ pretty: false })
142+
).toEqual(
143+
`<GraphDataModel><root><Cell id="0"><Object as="style" /></Cell><Cell id="1" parent="0"><Object as="style" /></Cell></root></GraphDataModel>`
144+
);
145+
});
146+
147+
test('model with 2 vertices linked with an edge', () => {
148+
const model = new GraphDataModel();
149+
const parent = getParent(model);
150+
151+
const v1 = newVertex('v1', 'vertex 1');
152+
model.add(parent, v1);
153+
v1.setStyle({ fillColor: 'green', strokeWidth: 4 });
154+
v1.geometry = new Geometry(100, 100, 100, 80);
155+
const v2 = newVertex('v2', 'vertex 2');
156+
v2.style = { bendable: false, rounded: true, fontColor: 'yellow' };
157+
model.add(parent, v2);
158+
159+
const edge = newEdge('e1', 'edge');
160+
model.add(parent, edge);
161+
model.setTerminal(edge, v1, true);
162+
model.setTerminal(edge, v2, false);
163+
(<Geometry>edge.geometry).points = [
164+
new Point(0, 10),
165+
new Point(0, 40),
166+
new Point(40, 40),
167+
];
168+
169+
// FIX boolean values should be set to true/false instead of 1/0
170+
expect(new ModelXmlSerializer(model).export()).toEqual(
171+
`<GraphDataModel>
172+
<root>
173+
<Cell id="0">
174+
<Object as="style" />
175+
</Cell>
176+
<Cell id="1" parent="0">
177+
<Object as="style" />
178+
</Cell>
179+
<Cell id="v1" value="vertex 1" vertex="1" parent="1">
180+
<Geometry _x="100" _y="100" _width="100" _height="80" as="geometry" />
181+
<Object fillColor="green" strokeWidth="4" as="style" />
182+
</Cell>
183+
<Cell id="v2" value="vertex 2" vertex="1" parent="1">
184+
<Object bendable="0" rounded="1" fontColor="yellow" as="style" />
185+
</Cell>
186+
<Cell id="e1" value="edge" edge="1" parent="1" source="v1" target="v2">
187+
<Geometry as="geometry">
188+
<Array as="points">
189+
<Point _y="10" />
190+
<Point _y="40" />
191+
<Point _x="40" _y="40" />
192+
</Array>
193+
</Geometry>
194+
<Object as="style" />
195+
</Cell>
196+
</root>
197+
</GraphDataModel>
198+
`
199+
);
200+
});
201+
});
202+
203+
describe('import', () => {
204+
test('XML from issue 178', () => {
205+
const model = new GraphDataModel();
206+
new ModelXmlSerializer(model).import(xmlFromIssue178);
207+
208+
const cell = model.getCell('B_#0');
209+
expect(cell).toBeDefined();
210+
expect(cell?.value).toEqual('rootNode');
211+
expect(cell?.vertex).toEqual(1); // FIX should be set to true
212+
expect(cell?.isVertex()).toBeTruthy();
213+
expect(cell?.getParent()?.id).toEqual('1');
214+
expect(cell?.geometry).toEqual(new Geometry(100, 100, 100, 80));
215+
expect(cell?.style).toEqual({
216+
fillColor: 'green',
217+
shape: 'triangle',
218+
strokeWidth: 4,
219+
});
220+
});
221+
});

0 commit comments

Comments
 (0)