3 var path = require('path');
5 var globule = require('../lib/globule.js');
8 ======== A Handy Little Nodeunit Reference ========
9 https://github.com/caolan/nodeunit
12 test.expect(numAssertions)
15 test.ok(value, [message])
16 test.equal(actual, expected, [message])
17 test.notEqual(actual, expected, [message])
18 test.deepEqual(actual, expected, [message])
19 test.notDeepEqual(actual, expected, [message])
20 test.strictEqual(actual, expected, [message])
21 test.notStrictEqual(actual, expected, [message])
22 test.throws(block, [error], [message])
23 test.doesNotThrow(block, [error], [message])
28 'empty set': function(test) {
30 // Should return empty set if a required argument is missing or an empty set.
31 test.deepEqual(globule.match(null, 'foo.js'), [], 'should return empty set.');
32 test.deepEqual(globule.match('*.js', null), [], 'should return empty set.');
33 test.deepEqual(globule.match([], 'foo.js'), [], 'should return empty set.');
34 test.deepEqual(globule.match('*.js', []), [], 'should return empty set.');
35 test.deepEqual(globule.match(null, ['foo.js']), [], 'should return empty set.');
36 test.deepEqual(globule.match(['*.js'], null), [], 'should return empty set.');
39 'basic matching': function(test) {
41 test.deepEqual(globule.match('*.js', 'foo.js'), ['foo.js'], 'should match correctly.');
42 test.deepEqual(globule.match('*.js', ['foo.js']), ['foo.js'], 'should match correctly.');
43 test.deepEqual(globule.match('*.js', ['foo.js', 'bar.css']), ['foo.js'], 'should match correctly.');
44 test.deepEqual(globule.match(['*.js', '*.css'], 'foo.js'), ['foo.js'], 'should match correctly.');
45 test.deepEqual(globule.match(['*.js', '*.css'], ['foo.js']), ['foo.js'], 'should match correctly.');
46 test.deepEqual(globule.match(['*.js', '*.css'], ['foo.js', 'bar.css']), ['foo.js', 'bar.css'], 'should match correctly.');
49 'no matches': function(test) {
51 test.deepEqual(globule.match('*.js', 'foo.css'), [], 'should fail to match.');
52 test.deepEqual(globule.match('*.js', ['foo.css', 'bar.css']), [], 'should fail to match.');
55 'unique': function(test) {
57 test.deepEqual(globule.match('*.js', ['foo.js', 'foo.js']), ['foo.js'], 'should return a uniqued set.');
58 test.deepEqual(globule.match(['*.js', '*.*'], ['foo.js', 'foo.js']), ['foo.js'], 'should return a uniqued set.');
61 'flatten': function(test) {
63 test.deepEqual(globule.match([['*.js', '*.css'], ['*.*', '*.js']], ['foo.js', 'bar.css']),
64 ['foo.js', 'bar.css'],
65 'should process nested pattern arrays correctly.');
68 'exclusion': function(test) {
70 test.deepEqual(globule.match(['!*.js'], ['foo.js', 'bar.js']), [], 'solitary exclusion should match nothing');
71 test.deepEqual(globule.match(['*.js', '!*.js'], ['foo.js', 'bar.js']), [], 'exclusion should cancel match');
72 test.deepEqual(globule.match(['*.js', '!f*.js'], ['foo.js', 'bar.js', 'baz.js']),
74 'partial exclusion should partially cancel match');
75 test.deepEqual(globule.match(['*.js', '!*.js', 'b*.js'], ['foo.js', 'bar.js', 'baz.js']),
77 'inclusion / exclusion order matters');
78 test.deepEqual(globule.match(['*.js', '!f*.js', '*.js'], ['foo.js', 'bar.js', 'baz.js']),
79 ['bar.js', 'baz.js', 'foo.js'],
80 'inclusion / exclusion order matters');
83 'options.matchBase': function(test) {
85 test.deepEqual(globule.match('*.js', ['foo.js', 'bar', 'baz/xyz.js'], {matchBase: true}),
86 ['foo.js', 'baz/xyz.js'],
87 'should matchBase (minimatch) when specified.');
88 test.deepEqual(globule.match('*.js', ['foo.js', 'bar', 'baz/xyz.js']),
90 'should not matchBase (minimatch) by default.');
95 exports['isMatch'] = {
96 'basic matching': function(test) {
98 test.ok(globule.isMatch('*.js', 'foo.js'), 'should match correctly.');
99 test.ok(globule.isMatch('*.js', ['foo.js']), 'should match correctly.');
100 test.ok(globule.isMatch('*.js', ['foo.js', 'bar.css']), 'should match correctly.');
101 test.ok(globule.isMatch(['*.js', '*.css'], 'foo.js'), 'should match correctly.');
102 test.ok(globule.isMatch(['*.js', '*.css'], ['foo.js']), 'should match correctly.');
103 test.ok(globule.isMatch(['*.js', '*.css'], ['foo.js', 'bar.css']), 'should match correctly.');
106 'no matches': function(test) {
108 test.ok(!globule.isMatch('*.js', 'foo.css'), 'should fail to match.');
109 test.ok(!globule.isMatch('*.js', ['foo.css', 'bar.css']), 'should fail to match.');
110 test.ok(!globule.isMatch(null, 'foo.css'), 'should fail to match.');
111 test.ok(!globule.isMatch('*.js', null), 'should fail to match.');
112 test.ok(!globule.isMatch([], 'foo.css'), 'should fail to match.');
113 test.ok(!globule.isMatch('*.js', []), 'should fail to match.');
116 'options.matchBase': function(test) {
118 test.ok(globule.isMatch('*.js', ['baz/xyz.js'], {matchBase: true}), 'should matchBase (minimatch) when specified.');
119 test.ok(!globule.isMatch('*.js', ['baz/xyz.js']), 'should not matchBase (minimatch) by default.');
125 setUp: function(done) {
126 this.cwd = process.cwd();
127 process.chdir('test/fixtures/expand');
130 tearDown: function(done) {
131 process.chdir(this.cwd);
134 'basic matching': function(test) {
136 test.deepEqual(globule.find('**/*.js'), ['js/bar.js', 'js/foo.js'], 'single pattern argument should match.');
137 test.deepEqual(globule.find('**/*.js', '**/*.css'),
138 ['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css'],
139 'multiple pattern arguments should match.');
140 test.deepEqual(globule.find(['**/*.js', '**/*.css']),
141 ['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css'],
142 'array of patterns should match.');
143 test.deepEqual(globule.find([['**/*.js'], [['**/*.css', 'js/*.js']]]),
144 ['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css'],
145 'array of arrays of patterns should be flattened.');
146 test.deepEqual(globule.find('*.xyz'), [], 'bad pattern should fail to match.');
149 'unique': function(test) {
151 test.deepEqual(globule.find('**/*.js', 'js/*.js'),
152 ['js/bar.js', 'js/foo.js'],
153 'file list should be uniqed.');
154 test.deepEqual(globule.find('**/*.js', '**/*.css', 'js/*.js'), ['js/bar.js', 'js/foo.js',
155 'css/baz.css', 'css/qux.css'],
156 'file list should be uniqed.');
157 test.deepEqual(globule.find('js', 'js/'),
159 'mixed non-ending-/ and ending-/ dirs will not be uniqed by default.');
160 test.deepEqual(globule.find('js', 'js/', {mark: true}),
162 'mixed non-ending-/ and ending-/ dirs will be uniqed when "mark" is specified.');
165 'file order': function(test) {
167 var actual = globule.find('**/*.{js,css}');
168 var expected = ['css/baz.css', 'css/qux.css', 'js/bar.js', 'js/foo.js'];
169 test.deepEqual(actual, expected, 'should select 4 files in this order, by default.');
171 actual = globule.find('js/foo.js', 'js/bar.js', '**/*.{js,css}');
172 expected = ['js/foo.js', 'js/bar.js', 'css/baz.css', 'css/qux.css'];
173 test.deepEqual(actual, expected, 'specifically-specified-up-front file order should be maintained.');
175 actual = globule.find('js/bar.js', 'js/foo.js', '**/*.{js,css}');
176 expected = ['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css'];
177 test.deepEqual(actual, expected, 'specifically-specified-up-front file order should be maintained.');
179 actual = globule.find('**/*.{js,css}', '!css/qux.css', 'css/qux.css');
180 expected = ['css/baz.css', 'js/bar.js', 'js/foo.js', 'css/qux.css'];
181 test.deepEqual(actual, expected, 'if a file is excluded and then re-added, it should be added at the end.');
183 actual = globule.find('js/foo.js', '**/*.{js,css}', '!css/qux.css', 'css/qux.css');
184 expected = ['js/foo.js', 'css/baz.css', 'js/bar.js', 'css/qux.css'];
185 test.deepEqual(actual, expected, 'should be able to combine specified-up-front and excluded/added-at-end.');
188 'exclusion': function(test) {
190 test.deepEqual(globule.find(['!js/*.js']), [], 'solitary exclusion should match nothing');
191 test.deepEqual(globule.find(['js/bar.js','!js/bar.js']), [], 'exclusion should negate match');
192 test.deepEqual(globule.find(['**/*.js', '!js/foo.js']),
194 'should omit single file from matched set');
195 test.deepEqual(globule.find(['!js/foo.js', '**/*.js']),
196 ['js/bar.js', 'js/foo.js'],
197 'inclusion / exclusion order matters');
198 test.deepEqual(globule.find(['**/*.js', '**/*.css', '!js/bar.js', '!css/baz.css']),
199 ['js/foo.js','css/qux.css'],
200 'multiple exclusions should be removed from the set');
201 test.deepEqual(globule.find(['**/*.js', '**/*.css', '!**/*.css']),
202 ['js/bar.js', 'js/foo.js'],
203 'excluded wildcards should be removed from the matched set');
204 test.deepEqual(globule.find(['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css', '!**/b*.*']),
205 ['js/foo.js', 'css/qux.css'],
206 'different pattern for exclusion should still work');
207 test.deepEqual(globule.find(['js/bar.js', '!**/b*.*', 'js/foo.js', 'css/baz.css', 'css/qux.css']),
208 ['js/foo.js', 'css/baz.css', 'css/qux.css'],
209 'inclusion / exclusion order matters');
212 'options.mark': function(test) {
214 test.deepEqual(globule.find('**d*/**'), [
218 'deep/deeper/deeper.txt',
219 'deep/deeper/deepest',
220 'deep/deeper/deepest/deepest.txt'], 'should match files and directories.');
221 test.deepEqual(globule.find('**d*/**/'), [
224 'deep/deeper/deepest/'], 'trailing / in pattern should match directories only, matches end in /.');
225 test.deepEqual(globule.find('**d*/**', {mark: true}), [
229 'deep/deeper/deeper.txt',
230 'deep/deeper/deepest/',
231 'deep/deeper/deepest/deepest.txt'], 'the minimatch "mark" option ensures directories end in /.');
232 test.deepEqual(globule.find('**d*/**/', {mark: true}), [
235 'deep/deeper/deepest/'], 'the minimatch "mark" option should not remove trailing / from matched paths.');
238 'options.filter': function(test) {
240 test.deepEqual(globule.find('**d*/**', {filter: 'isFile'}), [
242 'deep/deeper/deeper.txt',
243 'deep/deeper/deepest/deepest.txt'
244 ], 'should match files only.');
245 test.deepEqual(globule.find('**d*/**', {filter: 'isDirectory'}), [
248 'deep/deeper/deepest'
249 ], 'should match directories only.');
250 test.deepEqual(globule.find('**', {
251 arbitraryProp: /deepest/,
252 filter: function(filepath, options) {
253 return options.arbitraryProp.test(filepath);
256 'deep/deeper/deepest',
257 'deep/deeper/deepest/deepest.txt',
258 ], 'should filter arbitrarily.');
259 test.deepEqual(globule.find('js', 'css', {filter: 'isFile'}), [], 'should fail to match.');
260 test.deepEqual(globule.find('**/*.js', {filter: 'isDirectory'}), [], 'should fail to match.');
263 'options.matchBase': function(test) {
265 test.deepEqual(globule.find('*.js'), [], 'should not matchBase (minimatch) by default.');
266 test.deepEqual(globule.find('*.js', {matchBase: true}),
267 ['js/bar.js', 'js/foo.js'],
268 'matchBase option should be passed through to minimatch.');
269 test.deepEqual(globule.find('*.js', '*.css', {matchBase: true}),
270 ['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css'],
271 'matchBase option should be passed through to minimatch.');
274 'options.srcBase': function(test) {
276 test.deepEqual(globule.find(['**/deep*.txt'], {srcBase: 'deep'}),
277 ['deep.txt', 'deeper/deeper.txt', 'deeper/deepest/deepest.txt'],
278 'should find paths matching pattern relative to srcBase.');
279 test.deepEqual(globule.find(['**/deep*.txt'], {cwd: 'deep'}),
280 ['deep.txt', 'deeper/deeper.txt', 'deeper/deepest/deepest.txt'],
281 'cwd and srcBase should do the same thing.');
282 test.deepEqual(globule.find(['**/deep*'], {srcBase: 'deep', filter: 'isFile'}),
283 ['deep.txt', 'deeper/deeper.txt', 'deeper/deepest/deepest.txt'],
284 'srcBase should not prevent filtering.');
285 test.deepEqual(globule.find(['**/deep*'], {srcBase: 'deep', filter: 'isDirectory'}),
286 ['deeper', 'deeper/deepest'],
287 'srcBase should not prevent filtering.');
288 test.deepEqual(globule.find(['**/deep*.txt', '!**/deeper**'], {srcBase: 'deep'}),
289 ['deep.txt', 'deeper/deepest/deepest.txt'],
290 'srcBase should not prevent exclusions.');
293 'options.prefixBase': function(test) {
295 test.deepEqual(globule.find(['**/deep*.txt'], {srcBase: 'deep', prefixBase: false}),
296 ['deep.txt', 'deeper/deeper.txt', 'deeper/deepest/deepest.txt'],
297 'should not prefix srcBase to returned paths.');
298 test.deepEqual(globule.find(['**/deep*.txt'], {srcBase: 'deep', prefixBase: true}),
299 ['deep/deep.txt', 'deep/deeper/deeper.txt', 'deep/deeper/deepest/deepest.txt'],
300 'should prefix srcBase to returned paths.');
303 'options.nonull': function(test) {
305 test.deepEqual(globule.find(['*omg*'], {nonull: true}),
307 'non-matching patterns should be returned in result set.');
308 test.deepEqual(globule.find(['js/a*', 'js/b*', 'js/c*'], {nonull: true}),
309 ['js/a*', 'js/bar.js', 'js/c*'],
310 'non-matching patterns should be returned in result set.');
311 test.deepEqual(globule.find(['js/foo.js', 'js/bar.js', 'js/nonexistent.js'], {nonull: true}),
312 ['js/foo.js', 'js/bar.js', 'js/nonexistent.js'],
313 'non-matching filenames should be returned in result set.');
318 exports['mapping'] = {
319 'basic mapping': function(test) {
322 var actual = globule.mapping(['a.txt', 'b.txt', 'c.txt']);
324 {dest: 'a.txt', src: ['a.txt']},
325 {dest: 'b.txt', src: ['b.txt']},
326 {dest: 'c.txt', src: ['c.txt']},
328 test.deepEqual(actual, expected, 'default options should create same-to-same src-dest mappings.');
332 'options.srcBase': function(test) {
334 var actual, expected;
335 actual = globule.mapping(['a.txt', 'bar/b.txt', 'bar/baz/c.txt'], {srcBase: 'foo'});
337 {dest: 'a.txt', src: ['foo/a.txt']},
338 {dest: 'bar/b.txt', src: ['foo/bar/b.txt']},
339 {dest: 'bar/baz/c.txt', src: ['foo/bar/baz/c.txt']},
341 test.deepEqual(actual, expected, 'srcBase should be prefixed to src paths (no trailing /).');
343 actual = globule.mapping(['a.txt', 'bar/b.txt', 'bar/baz/c.txt'], {srcBase: 'foo/'});
344 test.deepEqual(actual, expected, 'srcBase should be prefixed to src paths (trailing /).');
348 'options.destBase': function(test) {
350 var actual, expected;
352 actual = globule.mapping(['a.txt', 'bar/b.txt', 'bar/baz/c.txt'], {destBase: 'dest'});
354 {dest: 'dest/a.txt', src: ['a.txt']},
355 {dest: 'dest/bar/b.txt', src: ['bar/b.txt']},
356 {dest: 'dest/bar/baz/c.txt', src: ['bar/baz/c.txt']},
358 test.deepEqual(actual, expected, 'destBase should be prefixed to dest paths (no trailing /).');
360 actual = globule.mapping(['a.txt', 'bar/b.txt', 'bar/baz/c.txt'], {destBase: 'dest/'});
361 test.deepEqual(actual, expected, 'destBase should be prefixed to dest paths (trailing /).');
365 'options.flatten': function(test) {
367 var actual, expected;
369 actual = globule.mapping(['a.txt', 'bar/b.txt', 'bar/baz/c.txt'], {flatten: true});
371 {dest: 'a.txt', src: ['a.txt']},
372 {dest: 'b.txt', src: ['bar/b.txt']},
373 {dest: 'c.txt', src: ['bar/baz/c.txt']},
375 test.deepEqual(actual, expected, 'flatten should remove all src path parts from dest.');
379 'options.flatten + options.destBase': function(test) {
381 var actual, expected;
383 actual = globule.mapping(['a.txt', 'bar/b.txt', 'bar/baz/c.txt'], {destBase: 'dest', flatten: true});
385 {dest: 'dest/a.txt', src: ['a.txt']},
386 {dest: 'dest/b.txt', src: ['bar/b.txt']},
387 {dest: 'dest/c.txt', src: ['bar/baz/c.txt']},
389 test.deepEqual(actual, expected, 'flatten and destBase should work together.');
393 'options.ext': function(test) {
395 var actual, expected;
397 actual = globule.mapping(['x/a.js', 'x.y/b.min.js', 'x.y/z.z/c'], {ext: '.foo'});
399 {dest: 'x/a.foo', src: ['x/a.js']},
400 {dest: 'x.y/b.foo', src: ['x.y/b.min.js']},
401 {dest: 'x.y/z.z/c.foo', src: ['x.y/z.z/c']},
403 test.deepEqual(actual, expected, 'by default, ext should replace everything after the first dot in the filename.');
407 'options.extDot': function(test) {
409 var actual, expected;
411 actual = globule.mapping(['x/a.js', 'x.y/b.bbb.min.js', 'x.y/z.z/c'], {ext: '.foo', extDot: 'first'});
413 {dest: 'x/a.foo', src: ['x/a.js']},
414 {dest: 'x.y/b.foo', src: ['x.y/b.bbb.min.js']},
415 {dest: 'x.y/z.z/c.foo', src: ['x.y/z.z/c']},
417 test.deepEqual(actual, expected, 'extDot of "first" should replace everything after the first dot in the filename.');
419 actual = globule.mapping(['x/a.js', 'x.y/b.bbb.min.js', 'x.y/z.z/c'], {ext: '.foo', extDot: 'last'});
421 {dest: 'x/a.foo', src: ['x/a.js']},
422 {dest: 'x.y/b.bbb.min.foo', src: ['x.y/b.bbb.min.js']},
423 {dest: 'x.y/z.z/c.foo', src: ['x.y/z.z/c']},
425 test.deepEqual(actual, expected, 'extDot of "last" should replace everything after the last dot in the filename.');
429 'options.rename': function(test) {
431 var actual, expected;
432 actual = globule.mapping(['a.txt', 'bar/b.txt', 'bar/baz/c.txt'], {
433 arbitraryProp: 'FOO',
434 rename: function(dest, options) {
435 return path.join(options.arbitraryProp, dest.toUpperCase());
439 {dest: 'FOO/A.TXT', src: ['a.txt']},
440 {dest: 'FOO/BAR/B.TXT', src: ['bar/b.txt']},
441 {dest: 'FOO/BAR/BAZ/C.TXT', src: ['bar/baz/c.txt']},
443 test.deepEqual(actual, expected, 'allow arbitrary renaming of files.');
449 exports['findMapping'] = {
450 setUp: function(done) {
451 this.cwd = process.cwd();
452 process.chdir('test/fixtures');
455 tearDown: function(done) {
456 process.chdir(this.cwd);
459 'basic matching': function(test) {
462 var actual = globule.findMapping(['expand/**/*.txt']);
464 {dest: 'expand/deep/deep.txt', src: ['expand/deep/deep.txt']},
465 {dest: 'expand/deep/deeper/deeper.txt', src: ['expand/deep/deeper/deeper.txt']},
466 {dest: 'expand/deep/deeper/deepest/deepest.txt', src: ['expand/deep/deeper/deepest/deepest.txt']},
468 test.deepEqual(actual, expected, 'default options');
470 expected = globule.mapping(globule.find(['expand/**/*.txt']));
471 test.deepEqual(actual, expected, 'this is what it\'s doing under the hood, anwyays.');
475 'options.srcBase': function(test) {
477 var actual = globule.findMapping(['**/*.txt'], {destBase: 'dest', srcBase: 'expand/deep'});
479 {dest: 'dest/deep.txt', src: ['expand/deep/deep.txt']},
480 {dest: 'dest/deeper/deeper.txt', src: ['expand/deep/deeper/deeper.txt']},
481 {dest: 'dest/deeper/deepest/deepest.txt', src: ['expand/deep/deeper/deepest/deepest.txt']},
483 test.deepEqual(actual, expected, 'srcBase should be stripped from front of destPath, pre-destBase+destPath join');