UNPKG

8.79 kBJavaScriptView Raw
1// @flow
2
3import assert from 'assert';
4import sinon from 'sinon';
5
6import Graph from '../src/Graph';
7
8describe('Graph', () => {
9 it('constructor should initialize an empty graph', () => {
10 let graph = new Graph();
11 assert.deepEqual(graph.nodes, new Map());
12 assert.deepEqual(graph.getAllEdges(), []);
13 });
14
15 it('addNode should add a node to the graph', () => {
16 let graph = new Graph();
17 let node = {id: 'a', value: 'a'};
18 graph.addNode(node);
19 assert.equal(graph.nodes.get(node.id), node);
20 });
21
22 it("errors when removeNode is called with a node that doesn't belong", () => {
23 let graph = new Graph();
24 assert.throws(() => {
25 graph.removeNode({id: 'dne', value: null});
26 }, /Does not have node/);
27 });
28
29 it('errors when traversing a graph with no root', () => {
30 let graph = new Graph();
31
32 assert.throws(() => {
33 graph.traverse(() => {});
34 }, /A start node is required to traverse/);
35 });
36
37 it("errors when traversing a graph with a startNode that doesn't belong", () => {
38 let graph = new Graph();
39
40 assert.throws(() => {
41 graph.traverse(() => {}, {id: 'dne', value: null});
42 }, /Does not have node/);
43 });
44
45 it("errors if replaceNodesConnectedTo is called with a node that doesn't belong", () => {
46 let graph = new Graph();
47 assert.throws(() => {
48 graph.replaceNodesConnectedTo({id: 'dne', value: null}, []);
49 }, /Does not have node/);
50 });
51
52 it("errors when adding an edge to a node that doesn't exist", () => {
53 let graph = new Graph();
54 graph.addNode({id: 'foo', value: null});
55 assert.throws(() => {
56 graph.addEdge('foo', 'dne');
57 }, /"to" node 'dne' not found/);
58 });
59
60 it("errors when adding an edge from a node that doesn't exist", () => {
61 let graph = new Graph();
62 graph.addNode({id: 'foo', value: null});
63 assert.throws(() => {
64 graph.addEdge('dne', 'foo');
65 }, /"from" node 'dne' not found/);
66 });
67
68 it('hasNode should return a boolean based on whether the node exists in the graph', () => {
69 let graph = new Graph();
70 let node = {id: 'a', value: 'a'};
71 graph.addNode(node);
72 assert(graph.hasNode(node.id));
73 assert(!graph.hasNode('b'));
74 });
75
76 it('addEdge should add an edge to the graph', () => {
77 let graph = new Graph();
78 graph.addNode({id: 'a', value: null});
79 graph.addNode({id: 'b', value: null});
80 graph.addEdge('a', 'b');
81 assert(graph.hasEdge('a', 'b'));
82 });
83
84 it('isOrphanedNode should return true or false if the node is orphaned or not', () => {
85 let graph = new Graph();
86 let nodeA = {id: 'a', value: 'a'};
87 let nodeB = {id: 'b', value: 'b'};
88 let nodeC = {id: 'c', value: 'c'};
89 graph.addNode(nodeA);
90 graph.addNode(nodeB);
91 graph.addNode(nodeC);
92 graph.addEdge('a', 'b');
93 graph.addEdge('a', 'c', 'edgetype');
94 assert(graph.isOrphanedNode(nodeA));
95 assert(!graph.isOrphanedNode(nodeB));
96 assert(!graph.isOrphanedNode(nodeC));
97 });
98
99 it('removeEdge should prune the graph at that edge', () => {
100 // a
101 // / \
102 // b - d
103 // /
104 // c
105 let graph = new Graph();
106 graph.addNode({id: 'a', value: 'a'});
107 graph.addNode({id: 'b', value: 'b'});
108 graph.addNode({id: 'c', value: 'c'});
109 graph.addNode({id: 'd', value: 'd'});
110 graph.addEdge('a', 'b');
111 graph.addEdge('a', 'd');
112 graph.addEdge('b', 'c');
113 graph.addEdge('b', 'd');
114
115 graph.removeEdge('a', 'b');
116 assert(graph.nodes.has('a'));
117 assert(graph.nodes.has('d'));
118 assert(!graph.nodes.has('b'));
119 assert(!graph.nodes.has('c'));
120 assert.deepEqual(graph.getAllEdges(), [{from: 'a', to: 'd', type: null}]);
121 });
122
123 it('removing a node recursively deletes orphaned nodes', () => {
124 // before:
125 // a
126 // / \
127 // b c
128 // / \ \
129 // d e f
130 // /
131 // g
132 //
133
134 // after:
135 // a
136 // \
137 // c
138 // \
139 // f
140
141 let graph = new Graph();
142 graph.addNode({id: 'a', value: 'a'});
143 graph.addNode({id: 'b', value: 'b'});
144 graph.addNode({id: 'c', value: 'c'});
145 graph.addNode({id: 'd', value: 'd'});
146 graph.addNode({id: 'e', value: 'e'});
147 graph.addNode({id: 'f', value: 'f'});
148 graph.addNode({id: 'g', value: 'g'});
149
150 graph.addEdge('a', 'b');
151 graph.addEdge('a', 'c');
152 graph.addEdge('b', 'd');
153 graph.addEdge('b', 'e');
154 graph.addEdge('c', 'f');
155 graph.addEdge('d', 'g');
156
157 graph.removeById('b');
158
159 assert.deepEqual([...graph.nodes.values()].map(node => node.id), [
160 'a',
161 'c',
162 'f'
163 ]);
164 assert.deepEqual(graph.getAllEdges(), [
165 {from: 'a', to: 'c', type: null},
166 {from: 'c', to: 'f', type: null}
167 ]);
168 });
169
170 it('removing a node recursively deletes orphaned nodes if there is no path to the root', () => {
171 // before:
172 // a
173 // / \
174 // b c
175 // / \ \
176 // |-d e f
177 // |/
178 // g
179 //
180
181 // after:
182 // a
183 // \
184 // c
185 // \
186 // f
187
188 let graph = new Graph();
189 graph.setRootNode({id: 'a', value: 'a'});
190 graph.addNode({id: 'b', value: 'b'});
191 graph.addNode({id: 'c', value: 'c'});
192 graph.addNode({id: 'd', value: 'd'});
193 graph.addNode({id: 'e', value: 'e'});
194 graph.addNode({id: 'f', value: 'f'});
195 graph.addNode({id: 'g', value: 'g'});
196
197 graph.addEdge('a', 'b');
198 graph.addEdge('a', 'c');
199 graph.addEdge('b', 'd');
200 graph.addEdge('g', 'd');
201 graph.addEdge('b', 'e');
202 graph.addEdge('c', 'f');
203 graph.addEdge('d', 'g');
204
205 graph.removeById('b');
206
207 assert.deepEqual([...graph.nodes.values()].map(node => node.id), [
208 'a',
209 'c',
210 'f'
211 ]);
212 assert.deepEqual(graph.getAllEdges(), [
213 {from: 'a', to: 'c', type: null},
214 {from: 'c', to: 'f', type: null}
215 ]);
216 });
217
218 it('removing an edge to a node that cycles does not remove it if there is a path to the root', () => {
219 // a
220 // |
221 // b <----
222 // / \ |
223 // c d |
224 // \ / |
225 // e -----
226 let graph = new Graph();
227 graph.setRootNode({id: 'a', value: 'a'});
228 graph.addNode({id: 'b', value: 'b'});
229 graph.addNode({id: 'c', value: 'c'});
230 graph.addNode({id: 'd', value: 'd'});
231 graph.addNode({id: 'e', value: 'e'});
232
233 graph.addEdge('a', 'b');
234 graph.addEdge('b', 'c');
235 graph.addEdge('b', 'd');
236 graph.addEdge('c', 'e');
237 graph.addEdge('d', 'e');
238 graph.addEdge('e', 'b');
239
240 const getNodeIds = () => [...graph.nodes.values()].map(node => node.id);
241 let nodesBefore = getNodeIds();
242
243 graph.removeEdge('c', 'e');
244
245 assert.deepEqual(nodesBefore, getNodeIds());
246 assert.deepEqual(graph.getAllEdges(), [
247 {from: 'a', to: 'b', type: null},
248 {from: 'b', to: 'c', type: null},
249 {from: 'b', to: 'd', type: null},
250 {from: 'd', to: 'e', type: null},
251 {from: 'e', to: 'b', type: null}
252 ]);
253 });
254
255 it('removing a node with only one inbound edge does not cause it to be removed as an orphan', () => {
256 let graph = new Graph();
257
258 graph.setRootNode({id: 'a', value: 'a'});
259 graph.addNode({id: 'b', value: 'b'});
260 graph.addEdge('a', 'b');
261
262 let spy = sinon.spy(graph, 'removeNode');
263 try {
264 graph.removeById('b');
265
266 assert(spy.calledOnceWithExactly({id: 'b', value: 'b'}));
267 } finally {
268 spy.restore();
269 }
270 });
271
272 it("replaceNodesConnectedTo should update a node's downstream nodes", () => {
273 let graph = new Graph();
274 let nodeA = graph.addNode({id: 'a', value: 'a'});
275 let nodeB = graph.addNode({id: 'b', value: 'b'});
276 graph.addNode({id: 'c', value: 'c'});
277 graph.addEdge('a', 'b');
278 graph.addEdge('a', 'c');
279
280 let nodeD = {id: 'd', value: 'd'};
281 graph.replaceNodesConnectedTo(nodeA, [nodeB, nodeD]);
282
283 assert(graph.nodes.has('a'));
284 assert(graph.nodes.has('b'));
285 assert(!graph.nodes.has('c'));
286 assert(graph.nodes.has('d'));
287 assert.deepEqual(graph.getAllEdges(), [
288 {from: 'a', to: 'b', type: null},
289 {from: 'a', to: 'd', type: null}
290 ]);
291 });
292
293 it('traverses along edge types if a filter is given', () => {
294 let graph = new Graph();
295 graph.addNode({id: 'a', value: 'a'});
296 graph.addNode({id: 'b', value: 'b'});
297 graph.addNode({id: 'c', value: 'c'});
298 graph.addNode({id: 'd', value: 'd'});
299
300 graph.addEdge('a', 'b', 'edgetype');
301 graph.addEdge('a', 'd');
302 graph.addEdge('b', 'c');
303 graph.addEdge('b', 'd', 'edgetype');
304
305 graph.rootNodeId = 'a';
306
307 let visited = [];
308 graph.traverse(
309 node => {
310 visited.push(node.id);
311 },
312 null, // use root as startNode
313 'edgetype'
314 );
315 assert.deepEqual(visited, ['a', 'b', 'd']);
316 });
317});