1 |
|
2 | 'use strict';
|
3 |
|
4 | const os = require('os');
|
5 | const path = require('path');
|
6 | const fs = require('mz/fs');
|
7 | const madge = require('../lib/api');
|
8 |
|
9 | require('should');
|
10 |
|
11 | describe('API', () => {
|
12 | it('throws error on missing path argument', () => {
|
13 | (() => {
|
14 | madge();
|
15 | }).should.throw('path argument not provided');
|
16 | });
|
17 |
|
18 | it('returns a Promise', () => {
|
19 | madge(__dirname + '/cjs/a.js').should.be.Promise();
|
20 | });
|
21 |
|
22 | it('throws error if file or directory does not exists', (done) => {
|
23 | madge(__dirname + '/missing.js').catch((err) => {
|
24 | err.message.should.match(/no such file or directory/);
|
25 | done();
|
26 | }).catch(done);
|
27 | });
|
28 |
|
29 | it('takes single file as path', (done) => {
|
30 | madge(__dirname + '/cjs/a.js').then((res) => {
|
31 | res.obj().should.eql({
|
32 | 'a.js': ['b.js', 'c.js'],
|
33 | 'b.js': ['c.js'],
|
34 | 'c.js': []
|
35 | });
|
36 | done();
|
37 | }).catch(done);
|
38 | });
|
39 |
|
40 | it('takes an array of files as path and combines the result', (done) => {
|
41 | madge([__dirname + '/cjs/a.js', __dirname + '/cjs/normal/d.js']).then((res) => {
|
42 | res.obj().should.eql({
|
43 | 'a.js': ['b.js', 'c.js'],
|
44 | 'b.js': ['c.js'],
|
45 | 'c.js': [],
|
46 | 'normal/d.js': []
|
47 | });
|
48 | done();
|
49 | }).catch(done);
|
50 | });
|
51 |
|
52 | it('take a single directory as path and find files in it', (done) => {
|
53 | madge(__dirname + '/cjs/normal').then((res) => {
|
54 | res.obj().should.eql({
|
55 | 'a.js': ['sub/b.js'],
|
56 | 'd.js': [],
|
57 | 'sub/b.js': ['sub/c.js'],
|
58 | 'sub/c.js': ['d.js']
|
59 | });
|
60 | done();
|
61 | }).catch(done);
|
62 | });
|
63 |
|
64 | it('takes an array of directories as path and compute the basedir correctly', (done) => {
|
65 | madge([__dirname + '/cjs/multibase/1', __dirname + '/cjs/multibase/2']).then((res) => {
|
66 | res.obj().should.eql({
|
67 | '1/a.js': [],
|
68 | '2/b.js': []
|
69 | });
|
70 | done();
|
71 | }).catch(done);
|
72 | });
|
73 |
|
74 | it('takes a predefined tree', (done) => {
|
75 | madge({
|
76 | a: ['b', 'c', 'd'],
|
77 | b: ['c'],
|
78 | c: [],
|
79 | d: ['a']
|
80 | }).then((res) => {
|
81 | res.obj().should.eql({
|
82 | a: ['b', 'c', 'd'],
|
83 | b: ['c'],
|
84 | c: [],
|
85 | d: ['a']
|
86 | });
|
87 | done();
|
88 | }).catch(done);
|
89 | });
|
90 |
|
91 | it('can exclude modules using RegExp', (done) => {
|
92 | madge(__dirname + '/cjs/a.js', {
|
93 | excludeRegExp: ['^b.js$']
|
94 | }).then((res) => {
|
95 | res.obj().should.eql({
|
96 | 'a.js': ['c.js'],
|
97 | 'c.js': []
|
98 | });
|
99 | done();
|
100 | }).catch(done);
|
101 | });
|
102 |
|
103 | it('extracts dependencies but excludes .git', (done) => {
|
104 |
|
105 | fs.renameSync(`${__dirname}/git/.git_tmp`, `${__dirname}/git/.git`);
|
106 |
|
107 | madge(__dirname + '/git/a.js', {}).then((res) => {
|
108 | res.obj().should.eql({
|
109 | 'a.js': ['b.js', 'c.js'],
|
110 | 'b.js': ['c.js'],
|
111 | 'c.js': []
|
112 | });
|
113 | done();
|
114 | }).catch(() => {
|
115 | done();
|
116 | }).finally(() => {
|
117 |
|
118 | fs.renameSync(`${__dirname}/git/.git`, `${__dirname}/git/.git_tmp`);
|
119 | });
|
120 | });
|
121 |
|
122 | describe('dependencyFilter', () => {
|
123 | it('will stop traversing when returning false', (done) => {
|
124 | madge(__dirname + '/cjs/a.js', {
|
125 | dependencyFilter: () => {
|
126 | return false;
|
127 | }
|
128 | }).then((res) => {
|
129 | res.obj().should.eql({
|
130 | 'a.js': []
|
131 | });
|
132 | done();
|
133 | }).catch(done);
|
134 | });
|
135 |
|
136 | it('will not stop traversing when not returning anything', (done) => {
|
137 | madge(__dirname + '/cjs/a.js', {
|
138 | dependencyFilter: () => {}
|
139 | }).then((res) => {
|
140 | res.obj().should.eql({
|
141 | 'a.js': ['b.js', 'c.js'],
|
142 | 'b.js': ['c.js'],
|
143 | 'c.js': []
|
144 | });
|
145 | done();
|
146 | }).catch(done);
|
147 | });
|
148 |
|
149 | it('will pass arguments to the function', (done) => {
|
150 | let counter = 0;
|
151 |
|
152 | madge(__dirname + '/cjs/a.js', {
|
153 | dependencyFilter: (dependencyFilePath, traversedFilePath, baseDir) => {
|
154 | if (counter === 0) {
|
155 | dependencyFilePath.should.match(/test\/cjs\/b\.js$/);
|
156 | traversedFilePath.should.match(/test\/cjs\/a\.js$/);
|
157 | baseDir.should.match(/test\/cjs$/);
|
158 | }
|
159 |
|
160 | if (counter === 1) {
|
161 | dependencyFilePath.should.match(/test\/cjs\/c\.js$/);
|
162 | traversedFilePath.should.match(/test\/cjs\/a\.js$/);
|
163 | baseDir.should.match(/test\/cjs$/);
|
164 | }
|
165 |
|
166 | if (counter === 2) {
|
167 | dependencyFilePath.should.match(/test\/cjs\/c\.js$/);
|
168 | traversedFilePath.should.match(/test\/cjs\/b\.js$/);
|
169 | baseDir.should.match(/test\/cjs$/);
|
170 | }
|
171 |
|
172 | counter++;
|
173 | }
|
174 | }).then(() => {
|
175 | done();
|
176 | }).catch(done);
|
177 | });
|
178 | });
|
179 |
|
180 | describe('obj()', () => {
|
181 | it('returns dependency object', (done) => {
|
182 | madge(__dirname + '/cjs/a.js').then((res) => {
|
183 | res.obj().should.eql({
|
184 | 'a.js': ['b.js', 'c.js'],
|
185 | 'b.js': ['c.js'],
|
186 | 'c.js': []
|
187 | });
|
188 | done();
|
189 | }).catch(done);
|
190 | });
|
191 | });
|
192 |
|
193 | describe('warnings()', () => {
|
194 | it('returns an array of skipped files', (done) => {
|
195 | madge(__dirname + '/cjs/missing.js').then((res) => {
|
196 | res.obj().should.eql({
|
197 | 'missing.js': ['c.js'],
|
198 | 'c.js': []
|
199 | });
|
200 | res.warnings().should.eql({
|
201 | skipped: ['./path/non/existing/file']
|
202 | });
|
203 | done();
|
204 | }).catch(done);
|
205 | });
|
206 | });
|
207 |
|
208 | describe('dot()', () => {
|
209 | it('returns a promise resolved with graphviz DOT output', async () => {
|
210 | const res = await madge(__dirname + '/cjs/b.js');
|
211 | const output = await res.dot();
|
212 | output.should.match(/digraph G/);
|
213 | output.should.match(/bgcolor="#111111"/);
|
214 | output.should.match(/fontcolor="#c6c5fe"/);
|
215 | output.should.match(/color="#757575"/);
|
216 | output.should.match(/fontcolor="#cfffac"/);
|
217 | });
|
218 | });
|
219 |
|
220 | describe('depends()', () => {
|
221 | it('returns modules that depends on another', (done) => {
|
222 | madge(__dirname + '/cjs/a.js').then((res) => {
|
223 | res.depends('c.js').should.eql(['a.js', 'b.js']);
|
224 | done();
|
225 | }).catch(done);
|
226 | });
|
227 | });
|
228 |
|
229 | describe('orphans()', () => {
|
230 | it('returns modules that no one is depending on', (done) => {
|
231 | madge(__dirname + '/cjs/normal').then((res) => {
|
232 | res.orphans().should.eql(['a.js']);
|
233 | done();
|
234 | }).catch(done);
|
235 | });
|
236 | });
|
237 |
|
238 | describe('leaves()', () => {
|
239 | it('returns modules that have no dependencies', (done) => {
|
240 | madge(__dirname + '/cjs/normal').then((res) => {
|
241 | res.leaves().should.eql(['d.js']);
|
242 | done();
|
243 | }).catch(done);
|
244 | });
|
245 | });
|
246 |
|
247 | describe('svg()', () => {
|
248 | it('returns a promise resolved with XML SVG output in a Buffer', (done) => {
|
249 | madge(__dirname + '/cjs/b.js')
|
250 | .then((res) => res.svg())
|
251 | .then((output) => {
|
252 | output.should.instanceof(Buffer);
|
253 | output.toString().should.match(/<svg.*/);
|
254 | done();
|
255 | })
|
256 | .catch(done);
|
257 | });
|
258 | });
|
259 |
|
260 | describe('image()', () => {
|
261 | let imagePath;
|
262 |
|
263 | beforeEach(() => {
|
264 | imagePath = path.join(os.tmpdir(), 'madge_' + Date.now() + '_image.png');
|
265 | });
|
266 |
|
267 | afterEach(() => {
|
268 | return fs.unlink(imagePath).catch(() => {});
|
269 | });
|
270 |
|
271 | it('rejects if a filename is not supplied', (done) => {
|
272 | madge(__dirname + '/cjs/a.js')
|
273 | .then((res) => res.image())
|
274 | .catch((err) => {
|
275 | err.message.should.eql('imagePath not provided');
|
276 | done();
|
277 | });
|
278 | });
|
279 |
|
280 | it('rejects on unsupported image format', (done) => {
|
281 | madge(__dirname + '/cjs/a.js')
|
282 | .then((res) => res.image('image.zyx'))
|
283 | .catch((err) => {
|
284 | err.message.should.match(/Format: "zyx" not recognized/);
|
285 | done();
|
286 | });
|
287 | });
|
288 |
|
289 | it('rejects if graphviz is not installed', (done) => {
|
290 | madge(__dirname + '/cjs/a.js', {graphVizPath: '/invalid/path'})
|
291 | .then((res) => res.image('image.png'))
|
292 | .catch((err) => {
|
293 | err.message.should.match(/Could not execute .*gvpr \-V/);
|
294 | done();
|
295 | });
|
296 | });
|
297 |
|
298 | it('writes image to file', (done) => {
|
299 | madge(__dirname + '/cjs/a.js')
|
300 | .then((res) => res.image(imagePath))
|
301 | .then((writtenImagePath) => {
|
302 | writtenImagePath.should.eql(imagePath);
|
303 |
|
304 | return fs
|
305 | .exists(imagePath)
|
306 | .then((exists) => {
|
307 | if (!exists) {
|
308 | throw new Error(imagePath + ' not created');
|
309 | }
|
310 | done();
|
311 | });
|
312 | })
|
313 | .catch(done);
|
314 | });
|
315 | });
|
316 | });
|