UNPKG

20 kBJavaScriptView Raw
1
2'use strict';
3
4let assert = require('chai').assert;
5let File = require('../lib/file');
6let Tree = require('../lib/tree');
7
8describe('Tree()', function () {
9 it('should be a constructor function', function () {
10 assert.instanceOf(new Tree(), Tree);
11 });
12
13 it('should be empty by default', function () {
14 let tree = new Tree();
15 assert.equal(tree.size(), 0);
16 });
17
18 describe('#hasFile(location)', function () {
19 let tree = new Tree();
20 tree.addFile('a');
21
22 it('should return false for a missing node', function () {
23 assert.isFalse(tree.hasFile('z'));
24 });
25
26 it('should return true for an existing node', function () {
27 assert.isTrue(tree.hasFile('a'));
28 });
29 });
30
31 describe('#addFile(location, [entry])', function () {
32 it('should add a new vertex', function () {
33 let tree = new Tree();
34 tree.addFile('a');
35
36 assert.isTrue(tree.hasFile('a'));
37 });
38
39 it('should not overwrite the file object', function () {
40 let tree = new Tree();
41 let file1 = tree.addFile('a');
42 let file2 = tree.addFile('a');
43
44 assert.strictEqual(file1, file2);
45 });
46
47 context('with entry', function () {
48 it('should set the file as an entry', function () {
49 let tree = new Tree();
50 let a = tree.addFile('a', true);
51
52 assert.isTrue(a.entry);
53 });
54
55 it('should leave the file as not an entry by default', function () {
56 let tree = new Tree();
57 let a = tree.addFile('a');
58
59 assert.isFalse(a.entry);
60 });
61 });
62 });
63
64 describe('#getFile(location)', function () {
65 let tree = new Tree();
66 tree.addFile('a');
67
68 it('should return a file instance', function () {
69 let file = tree.getFile('a');
70 assert.instanceOf(file, File);
71 assert.strictEqual(file.path, 'a');
72 });
73
74 it('should throw when the file does not exist', function () {
75 assert.isUndefined(tree.getFile('z'));
76 });
77 });
78
79 describe('#getFiles([options])', function () {
80 // a <- b <- c <- d
81 // <- e
82 let tree = new Tree();
83 tree.addFile('a');
84 tree.addFile('b');
85 tree.addFile('c');
86 tree.addFile('d');
87 tree.addFile('e');
88 tree.addDependency('a', 'b');
89 tree.addDependency('a', 'e');
90 tree.addDependency('b', 'c');
91 tree.addDependency('c', 'd');
92
93 it('should return a list of all the files in the tree', function () {
94 assert.deepEqual(tree.getFiles(), [ 'a', 'b', 'c', 'd', 'e' ]);
95 });
96
97 context('with options', function () {
98 context('.topological', function () {
99 it('should sort the results topologically', function () {
100 assert.deepEqual(tree.getFiles({ topological: true }), [ 'd', 'e', 'c', 'b', 'a' ]);
101 });
102 });
103
104 context('.objects', function () {
105 it('should return the file objects', function () {
106 tree.getFiles({ objects: true }).forEach(file => assert.instanceOf(file, File));
107 });
108
109 it('should even work with options.topological', function () {
110 tree.getFiles({ topological: true, objects: true }).forEach(file => assert.instanceOf(file, File));
111 });
112 });
113 });
114 });
115
116 describe('#removeFile(location, [options])', function () {
117 it('should remove the file from the tree', function () {
118 let tree = new Tree();
119 tree.addFile('a');
120 tree.removeFile('a');
121
122 assert.isFalse(tree.hasFile('a'));
123 });
124
125 it('should fail if there are still dependencies defined', function () {
126 // a <- b
127 let tree = new Tree();
128 tree.addFile('a');
129 tree.addFile('b');
130 tree.addDependency('a', 'b');
131
132 assert.throws(function () {
133 tree.removeFile('a');
134 });
135 });
136
137 context('with options', function () {
138 context('.force', function () {
139 // a <- b
140 let tree = new Tree();
141 tree.addFile('a');
142 tree.addFile('b');
143 tree.addDependency('a', 'b');
144
145 tree.removeFile('a', { force: true });
146 assert.isFalse(tree.hasFile('a'));
147 assert.isTrue(tree.hasFile('b'));
148 });
149 });
150 });
151
152 describe('#getEntries([options])', function () {
153 it('should return an empty list', function () {
154 let tree = new Tree();
155 assert.deepEqual(tree.getEntries(), []);
156 });
157
158 it('should return only the top-level entry', function () {
159 // a <- b <- c
160 // <- d
161 let tree = new Tree();
162 tree.addFile('a', true);
163 tree.addFile('b');
164 tree.addFile('c');
165 tree.addFile('d');
166 tree.addDependency('a', 'b');
167 tree.addDependency('a', 'd');
168 tree.addDependency('b', 'c');
169
170 assert.deepEqual(tree.getEntries(), [ 'a' ]);
171 });
172
173 it('should return all the top-level entries', function () {
174 // a <- b
175 // c <- d <- e
176 let tree = new Tree();
177 tree.addFile('a', true);
178 tree.addFile('b');
179 tree.addFile('c', true);
180 tree.addFile('d');
181 tree.addFile('e');
182 tree.addDependency('a', 'b');
183 tree.addDependency('c', 'd');
184 tree.addDependency('d', 'e');
185
186 assert.deepEqual(tree.getEntries(), [ 'a', 'c' ]);
187 });
188
189 context('with options', function () {
190 // a <- b
191 // c <- d <- e
192 let tree = new Tree();
193 tree.addFile('a', true);
194 tree.addFile('b');
195 tree.addFile('c', true);
196 tree.addFile('d');
197 tree.addFile('e');
198 tree.addDependency('a', 'b');
199 tree.addDependency('c', 'd');
200 tree.addDependency('d', 'e');
201
202 context('.from', function () {
203 it('should only return all the linked entries', function () {
204 assert.deepEqual(tree.getEntries({ from: 'e' }), [ 'c' ]);
205 });
206 });
207
208 context('.objects', function () {
209 it('should return the file objects', function () {
210 tree.getEntries({ objects: true }).forEach(file => assert.instanceOf(file, File));
211 });
212 });
213 });
214 });
215
216 describe('#isEntry(location)', function () {
217 // a <- b
218 let tree = new Tree();
219 tree.addFile('a', true);
220 tree.addFile('b');
221 tree.addDependency('a', 'b');
222
223 it('should return true when the file is flagged as an entry', function () {
224 assert.isTrue(tree.isEntry('a'));
225 });
226
227 it('should return false when the file is not flagged as an entry', function () {
228 assert.isFalse(tree.isEntry('b'));
229 });
230 });
231
232 describe('#hasDependency(parent, child)', function () {
233 // a <- b
234 let tree = new Tree();
235 tree.addFile('a');
236 tree.addFile('b');
237 tree.addDependency('a', 'b');
238
239 it('should return false for a missing dependency', function () {
240 assert.isFalse(tree.hasDependency('a', 'z'));
241 });
242
243 it('should return true for an existing dependency', function () {
244 assert.isTrue(tree.hasDependency('a', 'b'));
245 });
246 });
247
248 describe('#addDependency(parent, child)', function () {
249 it('should create an edge between the parent and child', function () {
250 let tree = new Tree();
251 tree.addFile('a');
252 tree.addFile('b');
253 tree.addDependency('a', 'b');
254
255 assert.isTrue(tree.hasDependency('a', 'b'));
256 });
257
258 it('should throw if the parent was not already defined', function () {
259 let tree = new Tree();
260
261 assert.throws(function () {
262 tree.addDependency('a', 'b');
263 });
264 });
265
266 it('should automatically create the child if not previously defined', function () {
267 let tree = new Tree();
268 tree.addFile('a');
269 tree.addDependency('a', 'b');
270
271 assert.isTrue(tree.hasFile('b'));
272 });
273
274 it('should return the new child object', function () {
275 let tree = new Tree();
276 tree.addFile('a');
277 let child = tree.addDependency('a', 'b');
278
279 assert.strictEqual(tree.getFile('b'), child);
280 });
281
282 it('should not clobber the child object', function () {
283 let tree = new Tree();
284 tree.addFile('a');
285 let file1 = tree.addFile('b');
286 let file2 = tree.addDependency('a', 'b');
287
288 assert.strictEqual(file1, file2);
289 });
290 });
291
292 describe('#removeDependency(parent, child)', function () {
293 it('should remove the edge from the graph', function () {
294 // a <- b
295 let tree = new Tree();
296 tree.addFile('a');
297 tree.addFile('b');
298 tree.addDependency('a', 'b');
299
300 // a
301 tree.removeDependency('a', 'b');
302
303 assert.isFalse(tree.hasDependency('a', 'b'));
304 });
305 });
306
307 describe('#removeDependencies(parent)', function () {
308 it('should remove the link between parent and all children', function () {
309 // a <- b
310 // <- c
311 let tree = new Tree();
312 tree.addFile('a', true);
313 tree.addFile('b');
314 tree.addFile('c');
315 tree.addDependency('a', 'b');
316 tree.addDependency('a', 'c');
317
318 tree.removeDependencies('a');
319
320 assert.isTrue(tree.hasFile('a'));
321 assert.isTrue(tree.hasFile('b'));
322 assert.isTrue(tree.hasFile('c'));
323 assert.isFalse(tree.hasDependency('a', 'b'));
324 assert.isFalse(tree.hasDependency('a', 'c'));
325 });
326 });
327
328 describe('#dependenciesOf(node, [options])', function () {
329 // a <- b <- c <- d
330 let tree = new Tree();
331 tree.addFile('a');
332 tree.addFile('b');
333 tree.addFile('c');
334 tree.addFile('d');
335 tree.addDependency('a', 'b');
336 tree.addDependency('b', 'c');
337 tree.addDependency('c', 'd');
338
339 it('should return the direct dependencies of node', function () {
340 assert.deepEqual(tree.dependenciesOf('a'), [ 'b' ]);
341 });
342
343 context('with options', function () {
344 context('.recursive', function () {
345 it('should all the dependencies of node', function () {
346 assert.deepEqual(tree.dependenciesOf('a', { recursive: true }), [ 'b', 'c', 'd' ]);
347 });
348 });
349
350 context('.objects', function () {
351 it('should return the file objects', function () {
352 tree.dependenciesOf('a', { objects: true }).forEach(file => assert.instanceOf(file, File));
353 });
354 });
355 });
356 });
357
358 describe('#hasDependant(child, parent)', function () {
359 // a <- b
360 let tree = new Tree();
361 tree.addFile('a');
362 tree.addFile('b');
363 tree.addDependant('b', 'a');
364
365 it('should return false for a missing dependency', function () {
366 assert.isFalse(tree.hasDependant('b', 'z'));
367 });
368
369 it('should return true for an existing dependency', function () {
370 assert.isTrue(tree.hasDependant('b', 'a'));
371 });
372 });
373
374 describe('#addDependant(child, parent)', function () {
375 it('should create an edge between the child and parent', function () {
376 // a <- b
377 let tree = new Tree();
378 tree.addFile('a');
379 tree.addFile('b');
380 tree.addDependant('b', 'a');
381
382 assert.isTrue(tree.hasDependant('b', 'a'));
383 });
384
385 it('should throw if the parent was not already defined', function () {
386 let tree = new Tree();
387
388 assert.throws(function () {
389 tree.addDependant('b', 'a');
390 });
391 });
392
393 it('should automatically create the parent if not previously defined', function () {
394 let tree = new Tree();
395 tree.addFile('b');
396 tree.addDependant('b', 'a');
397
398 assert.isTrue(tree.hasFile('a'));
399 });
400
401 it('should return the new parent object', function () {
402 let tree = new Tree();
403 tree.addFile('b');
404 let child = tree.addDependant('b', 'a');
405
406 assert.strictEqual(tree.getFile('a'), child);
407 });
408
409 it('should not clobber the child object', function () {
410 // a <- b
411 let tree = new Tree();
412 tree.addFile('b');
413 let file1 = tree.addFile('a');
414 let file2 = tree.addDependant('b', 'a');
415
416 assert.strictEqual(file1, file2);
417 });
418 });
419
420 describe('#removeDependant(child, parent)', function () {
421 it('should remove the edge from the graph', function () {
422 // a <- b
423 let tree = new Tree();
424 tree.addFile('a');
425 tree.addFile('b');
426 tree.addDependant('b', 'a');
427
428 // a
429 tree.removeDependant('b', 'a');
430
431 assert.isFalse(tree.hasDependant('b', 'a'));
432 });
433 });
434
435 describe('#removeDependants(child)', function () {
436 it('should remove the link between child and all parents', function () {
437 // a <- c
438 // b <-
439 let tree = new Tree();
440 tree.addFile('a', true);
441 tree.addFile('b', true);
442 tree.addFile('c');
443 tree.addDependant('c', 'a');
444 tree.addDependant('c', 'b');
445
446 tree.removeDependants('c');
447
448 assert.isTrue(tree.hasFile('a'));
449 assert.isTrue(tree.hasFile('b'));
450 assert.isTrue(tree.hasFile('c'));
451 assert.isFalse(tree.hasDependant('c', 'a'));
452 assert.isFalse(tree.hasDependant('c', 'b'));
453 });
454 });
455
456 describe('#dependantsOf(node, [options])', function () {
457 // a <- b <- c <- d
458 let tree = new Tree();
459 tree.addFile('a');
460 tree.addFile('b');
461 tree.addFile('c');
462 tree.addFile('d');
463 tree.addDependency('a', 'b');
464 tree.addDependency('b', 'c');
465 tree.addDependency('c', 'd');
466
467 it('should return the direct dependencies of node', function () {
468 assert.deepEqual(tree.dependantsOf('d'), [ 'c' ]);
469 });
470
471 context('with options', function () {
472 context('.recursive', function () {
473 it('should all the dependencies of node', function () {
474 assert.deepEqual(tree.dependantsOf('d', { recursive: true }), [ 'c', 'b', 'a' ]);
475 });
476 });
477
478 context('.objects', function () {
479 it('should return the file objects', function () {
480 tree.dependantsOf('d', { objects: true }).forEach(file => assert.instanceOf(file, File));
481 });
482 });
483 });
484 });
485
486 describe('#size()', function () {
487 // a <- b
488 // <- c
489 let tree = new Tree();
490 tree.addFile('a');
491 tree.addFile('b');
492 tree.addFile('c');
493 tree.addDependency('a', 'b');
494 tree.addDependency('a', 'c');
495
496 it('should return the number of files in the tree', function () {
497 assert.strictEqual(tree.size(), 3);
498 });
499 });
500
501 describe('#clone()', function () {
502 // a <- b
503 // <- c
504 let tree = new Tree();
505 tree.addFile('a');
506 tree.addFile('b');
507 tree.addFile('c');
508 tree.addDependency('a', 'b');
509 tree.addDependency('a', 'c');
510
511 it('should make a clone of the original', function () {
512 let clone = tree.clone();
513
514 assert.notStrictEqual(tree, clone);
515 assert.instanceOf(clone, Tree);
516 assert.strictEqual(tree.size(), clone.size());
517 assert.deepEqual(tree.getFiles({ topolical: true }), clone.getFiles({ topolical: true }));
518 });
519 });
520
521 describe('#prune([entries])', function () {
522 it('should only remove orphaned files', function () {
523 // a* <- b
524 // c
525 let tree = new Tree();
526 tree.addFile('a', true);
527 tree.addFile('b');
528 tree.addFile('c');
529 tree.addDependency('a', 'b');
530
531 tree.prune();
532
533 assert.strictEqual(tree.size(), 2);
534 assert.isFalse(tree.hasFile('c'));
535 });
536
537 it('should recursively remove orphaned trees', function () {
538 // a* <- b
539 // c <- d
540 let tree = new Tree();
541 tree.addFile('a', true);
542 tree.addFile('b');
543 tree.addFile('c');
544 tree.addFile('d');
545 tree.addDependency('a', 'b');
546 tree.addDependency('c', 'd');
547
548 tree.prune();
549
550 assert.strictEqual(tree.size(), 2);
551 assert.isFalse(tree.hasFile('c'));
552 assert.isFalse(tree.hasFile('d'));
553 });
554
555 it('should not remove dependencies that are still depended on elsewhere', function () {
556 // a* <- b <- c
557 // d <-
558 let tree = new Tree();
559 tree.addFile('a', true);
560 tree.addFile('b');
561 tree.addFile('c');
562 tree.addFile('d');
563 tree.addDependency('a', 'b');
564 tree.addDependency('b', 'c');
565 tree.addDependency('d', 'b');
566
567 tree.prune();
568
569 assert.deepEqual(tree.getFiles({ topological: true }), [ 'c', 'b', 'a' ]);
570 });
571
572 it('should properly handle a complex case', function () {
573 // a* <- b <- c <- d
574 // e <- f <-
575 let tree = new Tree();
576 tree.addFile('a', true);
577 tree.addFile('b');
578 tree.addFile('c');
579 tree.addFile('d');
580 tree.addFile('e');
581 tree.addFile('f');
582 tree.addDependency('a', 'b');
583 tree.addDependency('b', 'c');
584 tree.addDependency('c', 'd');
585 tree.addDependency('e', 'f');
586 tree.addDependency('f', 'c');
587
588 tree.prune();
589
590 assert.deepEqual(tree.getFiles({ topological: true }), [ 'd', 'c', 'b', 'a' ]);
591 });
592
593 context('with entries', function () {
594 it('should prune anything that cannot reach the provided list of files', function () {
595 // a* <- b
596 // c* <- d
597 let tree = new Tree();
598 tree.addFile('a', true);
599 tree.addFile('b');
600 tree.addFile('c', true);
601 tree.addFile('d');
602 tree.addDependency('a', 'b');
603 tree.addDependency('c', 'd');
604
605 tree.prune([ 'c' ]);
606
607 assert.deepEqual(tree.getFiles({ topological: true }), [ 'd', 'c' ]);
608 });
609 });
610 });
611
612 describe('#removeCycles()', function () {
613 it('should remove shallow cycles', function () {
614 // a <- b <- c*
615 // <- c <- b*
616 let tree = new Tree();
617 tree.addFile('a', true);
618 tree.addFile('b');
619 tree.addFile('c');
620 tree.addDependency('a', 'b');
621 tree.addDependency('a', 'c');
622 tree.addDependency('b', 'c');
623 tree.addDependency('c', 'b');
624
625 tree.removeCycles();
626
627 assert.deepEqual(tree.getFiles({ topological: true }), [ 'c', 'b', 'a' ]);
628 });
629
630 it('should remove cycles found deeper in the graph', function () {
631 // a <- b <- c <- d*
632 // <- d <- c*
633 let tree = new Tree();
634 tree.addFile('a', true);
635 tree.addFile('b');
636 tree.addFile('c');
637 tree.addFile('d');
638 tree.addDependency('a', 'b');
639 tree.addDependency('b', 'c');
640 tree.addDependency('b', 'd');
641 tree.addDependency('c', 'd');
642 tree.addDependency('d', 'c');
643
644 tree.removeCycles();
645
646 assert.deepEqual(tree.getFiles({ topological: true }), [ 'd', 'c', 'b', 'a' ]);
647 });
648
649 it('should remove large cycles in the graph', function () {
650 // a <- b <- c <- d <- b*
651 let tree = new Tree();
652 tree.addFile('a', true);
653 tree.addFile('b');
654 tree.addFile('c');
655 tree.addFile('d');
656 tree.addDependency('a', 'b');
657 tree.addDependency('b', 'c');
658 tree.addDependency('c', 'd');
659 tree.addDependency('d', 'b');
660
661 tree.removeCycles();
662
663 assert.deepEqual(tree.getFiles({ topological: true }), [ 'd', 'c', 'b', 'a' ]);
664 });
665 });
666
667 describe('#toJSON()', function () {
668 it('should return a list of vertices and edges for reconstructing the graph', function () {
669 // a <- b
670 let tree = new Tree();
671 let a = tree.addFile('a', true);
672 let b = tree.addFile('b');
673 tree.addDependency('a', 'b');
674
675 assert.deepEqual(tree.toJSON(), {
676 vertices: [
677 [ 'a', a.toString() ],
678 [ 'b', b.toString() ]
679 ],
680 edges: [
681 [ 'b', 'a' ]
682 ]
683 });
684 });
685 });
686
687 describe('#toString([space])', function () {
688 it('should completely stringify to JSON', function () {
689 // a <- b
690 let tree = new Tree();
691 let a = tree.addFile('a', true);
692 let b = tree.addFile('b');
693 tree.addDependency('a', 'b');
694
695 assert.strictEqual(tree.toString(), JSON.stringify({
696 vertices: [
697 [ 'a', a.toString() ],
698 [ 'b', b.toString() ]
699 ],
700 edges: [
701 [ 'b', 'a' ]
702 ]
703 }));
704 });
705 });
706
707 describe('.fromString(input)', function () {
708 it('should parse a JSON string into a tree instance', function () {
709 // a <- b
710 let tree = new Tree();
711 tree.addFile('a', true);
712 tree.addFile('b');
713 tree.addDependency('a', 'b');
714
715 let actual = Tree.fromString(tree.toString());
716 assert.instanceOf(actual, Tree);
717 assert.isTrue(actual.graph.equals(tree.graph, eqV, () => true));
718
719 function eqV(a, b) {
720 return a.path === b.path;
721 }
722 });
723 });
724});