diff --git a/index.js b/index.js index 972316e..9e246e0 100644 --- a/index.js +++ b/index.js @@ -1,79 +1,293 @@ 'use strict'; -var Combine = require('ordered-read-streams'); -var unique = require('unique-stream'); -var pumpify = require('pumpify'); +var fs = require('fs'); +var path = require('path'); +var EventEmitter = require('events'); + +var fastq = require('fastq'); +var anymatch = require('anymatch'); +var Readable = require('readable-stream').Readable; +var isGlob = require('is-glob'); +var globParent = require('glob-parent'); +var normalizePath = require('normalize-path'); var isNegatedGlob = require('is-negated-glob'); +var toAbsoluteGlob = require('@gulpjs/to-absolute-glob'); -var GlobStream = require('./readable'); +var globErrMessage1 = 'File not found with singular glob: '; +var globErrMessage2 = ' (if this was purposeful, use `allowEmpty` option)'; -function globStream(globs, opt) { - if (!opt) { - opt = {}; +function isFound(glob) { + // All globs are "found", while singular globs are only found when matched successfully + // This is due to the fact that a glob can match any number of files (0..Infinity) but + // a signular glob is always expected to match + return isGlob(glob); +} + +function walkdir() { + var readdirOpts = { + withFileTypes: true, + }; + + var ee = new EventEmitter(); + + var queue = fastq(readdir, 1); + queue.drain = function () { + ee.emit('end'); + }; + queue.error(onError); + + function onError(err) { + if (err) { + ee.emit('error', err); + } } - var ourOpt = Object.assign({}, opt); - var ignore = ourOpt.ignore; + ee.pause = function () { + queue.pause(); + }; + ee.resume = function () { + queue.resume(); + }; + ee.end = function () { + queue.kill(); + }; + ee.walk = function (filepath) { + queue.push(filepath); + }; - ourOpt.cwd = typeof ourOpt.cwd === 'string' ? ourOpt.cwd : process.cwd(); - ourOpt.dot = typeof ourOpt.dot === 'boolean' ? ourOpt.dot : false; - ourOpt.silent = typeof ourOpt.silent === 'boolean' ? ourOpt.silent : true; - ourOpt.cwdbase = typeof ourOpt.cwdbase === 'boolean' ? ourOpt.cwdbase : false; - ourOpt.uniqueBy = - typeof ourOpt.uniqueBy === 'string' || typeof ourOpt.uniqueBy === 'function' - ? ourOpt.uniqueBy - : 'path'; + function readdir(filepath, cb) { + fs.readdir(filepath, readdirOpts, onReaddir); - if (ourOpt.cwdbase) { - ourOpt.base = ourOpt.cwd; + function onReaddir(err, dirents) { + if (err) { + return cb(err); + } + + dirents.forEach(processDirent); + + cb(); + } + + function processDirent(dirent) { + var nextpath = path.join(filepath, dirent.name); + ee.emit('path', nextpath, dirent); + + if (dirent.isDirectory()) { + queue.push(nextpath); + } + } + } + + return ee; +} + +function validateGlobs(globs) { + var hasPositiveGlob = false; + + globs.forEach(validateGlobs); + + function validateGlobs(globString, index) { + if (typeof globString !== 'string') { + throw new Error('Invalid glob at index ' + index); + } + + var result = isNegatedGlob(globString); + if (result.negated === false) { + hasPositiveGlob = true; + } + } + + if (hasPositiveGlob === false) { + throw new Error('Missing positive glob'); + } +} + +function validateOptions(opts) { + if (typeof opts.cwd !== 'string') { + throw new Error('The `cwd` option must be a string'); + } + + if (typeof opts.dot !== 'boolean') { + throw new Error('The `dot` option must be a boolean'); + } + + if (typeof opts.cwdbase !== 'boolean') { + throw new Error('The `cwdbase` option must be a boolean'); + } + + if ( + typeof opts.uniqueBy !== 'string' && + typeof opts.uniqueBy !== 'function' + ) { + throw new Error('The `uniqueBy` option must be a string or function'); + } + + if (typeof opts.allowEmpty !== 'boolean') { + throw new Error('The `allowEmpty` option must be a boolean'); + } + + if (opts.base && typeof opts.base !== 'string') { + throw new Error('The `base` option must be a string if specified'); } - // Normalize string `ignore` to array - if (typeof ignore === 'string') { - ignore = [ignore]; + + if (!Array.isArray(opts.ignore)) { + throw new Error('The `ignore` option must be a string or array'); } - // Ensure `ignore` is an array - if (!Array.isArray(ignore)) { - ignore = []; +} + +function uniqueBy(comparator) { + var seen = new Set(); + + if (typeof comparator === 'string') { + return isUniqueByKey; + } else { + return isUniqueByFunc; } - // Only one glob no need to aggregate + function isUnique(value) { + if (seen.has(value)) { + return false; + } else { + seen.add(value); + return true; + } + } + + function isUniqueByKey(obj) { + return isUnique(obj[comparator]); + } + + function isUniqueByFunc(obj) { + return isUnique(comparator(obj)); + } +} + +function globStream(globs, opt) { if (!Array.isArray(globs)) { globs = [globs]; } - var positives = []; - var negatives = []; + validateGlobs(globs); - globs.forEach(sortGlobs); + var ourOpt = Object.assign( + {}, + { + highWaterMark: 16, + cwd: process.cwd(), + dot: false, + cwdbase: false, + uniqueBy: 'path', + allowEmpty: false, + ignore: [], + }, + opt + ); + // Normalize `ignore` to array + ourOpt.ignore = + typeof ourOpt.ignore === 'string' ? [ourOpt.ignore] : ourOpt.ignore; - function sortGlobs(globString, index) { - if (typeof globString !== 'string') { - throw new Error('Invalid glob at index ' + index); - } + validateOptions(ourOpt); + + var base = ourOpt.base; + if (ourOpt.cwdbase) { + base = ourOpt.cwd; + } + + var walker = walkdir(); - var glob = isNegatedGlob(globString); - var globArray = glob.negated ? negatives : positives; + var stream = new Readable({ + objectMode: true, + highWaterMark: ourOpt.highWaterMark, + read: read, + destroy: destroy, + }); - globArray.push(glob.pattern); + // Remove path relativity to make globs make sense + var ourGlobs = globs.map(resolveGlob); + ourOpt.ignore = ourOpt.ignore.map(resolveGlob); + + var found = ourGlobs.map(isFound); + + var matcher = anymatch(ourGlobs, null, ourOpt); + + var isUnique = uniqueBy(ourOpt.uniqueBy); + + walker.on('path', onPath); + walker.once('end', onEnd); + walker.once('error', onError); + walker.walk(ourOpt.cwd); + + function read() { + walker.resume(); } - if (positives.length === 0) { - throw new Error('Missing positive glob'); + function destroy(err) { + walker.end(); + + process.nextTick(function () { + if (err) { + stream.emit('error', err); + } + stream.emit('close'); + }); + } + + function resolveGlob(glob) { + return toAbsoluteGlob(glob, ourOpt); + } + + function onPath(filepath, dirent) { + var matchIdx = matcher(filepath, true); + // If the matcher doesn't match (but it is a directory), + // we want to add a trailing separator to check the match again + if (matchIdx === -1 && dirent.isDirectory()) { + matchIdx = matcher(filepath + path.sep, true); + } + if (matchIdx !== -1) { + found[matchIdx] = true; + + // Extract base path from glob + var basePath = base || globParent(ourGlobs[matchIdx]); + + var obj = { + cwd: ourOpt.cwd, + base: basePath, + // We always want to normalize the path to posix-style slashes + path: normalizePath(filepath, true), + }; + + var unique = isUnique(obj); + if (unique) { + var drained = stream.push(obj); + if (!drained) { + walker.pause(); + } + } + } } - // Create all individual streams - var streams = positives.map(streamFromPositive); + function onEnd() { + var destroyed = false; - // Then just pipe them to a single unique stream and return it - var aggregate = new Combine(streams); - var uniqueStream = unique(ourOpt.uniqueBy); + found.forEach(function (matchFound, idx) { + if (ourOpt.allowEmpty !== true && !matchFound) { + destroyed = true; + var err = new Error(globErrMessage1 + ourGlobs[idx] + globErrMessage2); - return pumpify.obj(aggregate, uniqueStream); + return stream.destroy(err); + } + }); - function streamFromPositive(positive) { - var negativeGlobs = negatives.concat(ignore); - return new GlobStream(positive, negativeGlobs, ourOpt); + if (destroyed === false) { + stream.push(null); + } } + + function onError(err) { + stream.destroy(err); + } + + return stream; } module.exports = globStream; diff --git a/package.json b/package.json index 5d450fb..0d1b19e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "main": "index.js", "files": [ "index.js", - "readable.js", "LICENSE" ], "scripts": { @@ -24,15 +23,14 @@ "test": "nyc mocha --async-only" }, "dependencies": { - "glob": "^8.0.3", + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.1", - "pumpify": "^2.0.1", - "readable-stream": "^3.6.0", - "remove-trailing-separator": "^1.1.0", - "to-absolute-glob": "^3.0.0", - "unique-stream": "^2.3.1" + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "devDependencies": { "eslint": "^7.0.0", diff --git a/readable.js b/readable.js deleted file mode 100644 index 0d9e523..0000000 --- a/readable.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -var inherits = require('util').inherits; - -var glob = require('glob'); -var Readable = require('readable-stream').Readable; -var globParent = require('glob-parent'); -var toAbsoluteGlob = require('to-absolute-glob'); -var removeTrailingSeparator = require('remove-trailing-separator'); - -var globErrMessage1 = 'File not found with singular glob: '; -var globErrMessage2 = ' (if this was purposeful, use `allowEmpty` option)'; - -function getBasePath(ourGlob, opt) { - return globParent(toAbsoluteGlob(ourGlob, opt)); -} - -function globIsSingular(glob) { - var globSet = glob.minimatch.set; - if (globSet.length !== 1) { - return false; - } - - return globSet[0].every(function isString(value) { - return typeof value === 'string'; - }); -} - -function GlobStream(ourGlob, negatives, opt) { - if (!(this instanceof GlobStream)) { - return new GlobStream(ourGlob, negatives, opt); - } - - var ourOpt = Object.assign({}, opt); - - Readable.call(this, { - objectMode: true, - highWaterMark: ourOpt.highWaterMark || 16, - }); - - // Delete `highWaterMark` after inheriting from Readable - delete ourOpt.highWaterMark; - - var self = this; - - function resolveNegatives(negative) { - return toAbsoluteGlob(negative, ourOpt); - } - - var ourNegatives = negatives.map(resolveNegatives); - ourOpt.ignore = ourNegatives; - - var cwd = ourOpt.cwd; - var allowEmpty = ourOpt.allowEmpty || false; - - // Extract base path from glob - var basePath = ourOpt.base || getBasePath(ourGlob, ourOpt); - - // Remove path relativity to make globs make sense - ourGlob = toAbsoluteGlob(ourGlob, ourOpt); - // Delete `root` after all resolving done - delete ourOpt.root; - - var globber = new glob.Glob(ourGlob, ourOpt); - this._globber = globber; - - var found = false; - - globber.on('match', function (filepath) { - found = true; - var obj = { - cwd: cwd, - base: basePath, - path: removeTrailingSeparator(filepath), - }; - if (!self.push(obj)) { - globber.pause(); - } - }); - - globber.once('end', function () { - if (allowEmpty !== true && !found && globIsSingular(globber)) { - var err = new Error(globErrMessage1 + ourGlob + globErrMessage2); - - return self.destroy(err); - } - - self.push(null); - }); - - function onError(err) { - self.destroy(err); - } - - globber.once('error', onError); -} -inherits(GlobStream, Readable); - -GlobStream.prototype._read = function () { - this._globber.resume(); -}; - -GlobStream.prototype.destroy = function (err) { - var self = this; - - this._globber.abort(); - - process.nextTick(function () { - if (err) { - self.emit('error', err); - } - self.emit('close'); - }); -}; - -module.exports = GlobStream; diff --git a/test/index.js b/test/index.js index 56e37bc..7fec96f 100644 --- a/test/index.js +++ b/test/index.js @@ -2,6 +2,10 @@ var expect = require('expect'); var miss = require('mississippi'); +var sinon = require('sinon'); + +// Need to wrap this to cause walker to emit an error +var fs = require('fs'); var globStream = require('../'); @@ -119,7 +123,7 @@ describe('glob-stream', function () { pipe( [ - globStream('./fixtures/has (parens)/*.dmc', { cwd: dir }), + globStream('./fixtures/has \\(parens\\)/*.dmc', { cwd: dir }), concat(assert), ], done @@ -213,7 +217,7 @@ describe('glob-stream', function () { ); }); - it('properly orders objects when given multiple paths and specified base', function (done) { + it('emits all objects (unordered) when given multiple paths and specified base', function (done) { var base = dir + '/fixtures'; var expected = [ @@ -242,13 +246,15 @@ describe('glob-stream', function () { function assert(pathObjs) { expect(pathObjs.length).toEqual(3); - expect(pathObjs).toEqual(expected); + expect(pathObjs).toContainEqual(expected[0]); + expect(pathObjs).toContainEqual(expected[1]); + expect(pathObjs).toContainEqual(expected[2]); } pipe([globStream(paths, { cwd: base, base: base }), concat(assert)], done); }); - it('properly orders objects when given multiple paths and cwdbase', function (done) { + it('emits all objects (unordered) when given multiple paths and cwdbase', function (done) { var base = dir + '/fixtures'; var expected = [ @@ -277,7 +283,9 @@ describe('glob-stream', function () { function assert(pathObjs) { expect(pathObjs.length).toEqual(3); - expect(pathObjs).toEqual(expected); + expect(pathObjs).toContainEqual(expected[0]); + expect(pathObjs).toContainEqual(expected[1]); + expect(pathObjs).toContainEqual(expected[2]); } pipe( @@ -286,7 +294,7 @@ describe('glob-stream', function () { ); }); - it('properly orders objects when given multiple globs with globstars', function (done) { + it('emits all objects (unordered) when given multiple globs with globstars', function (done) { var expected = [ { cwd: dir, @@ -324,13 +332,17 @@ describe('glob-stream', function () { function assert(pathObjs) { expect(pathObjs.length).toEqual(5); - expect(pathObjs).toEqual(expected); + expect(pathObjs).toContainEqual(expected[0]); + expect(pathObjs).toContainEqual(expected[1]); + expect(pathObjs).toContainEqual(expected[2]); + expect(pathObjs).toContainEqual(expected[3]); + expect(pathObjs).toContainEqual(expected[4]); } pipe([globStream(globs, { cwd: dir }), concat(assert)], done); }); - it('properly orders objects when given multiple absolute paths and no cwd', function (done) { + it('emits all objects (unordered) when given multiple absolute paths and no cwd', function (done) { var expected = [ { cwd: process.cwd(), @@ -357,7 +369,9 @@ describe('glob-stream', function () { function assert(pathObjs) { expect(pathObjs.length).toEqual(3); - expect(pathObjs).toEqual(expected); + expect(pathObjs).toContainEqual(expected[0]); + expect(pathObjs).toContainEqual(expected[1]); + expect(pathObjs).toContainEqual(expected[2]); } pipe([globStream(paths), concat(assert)], done); @@ -640,6 +654,7 @@ describe('glob-stream', function () { it('emits an error when a singular path in multiple paths not found', function (done) { function assert(err) { + expect(err).toEqual(expect.anything()); expect(err.toString()).toMatch(/File not found with singular glob/); done(); } @@ -689,8 +704,6 @@ describe('options', function () { var defaultedOpts = { cwd: process.cwd(), dot: false, - silent: true, - nonull: false, cwdbase: false, }; @@ -703,33 +716,30 @@ describe('options', function () { pipe([stream, concat()], done); }); - describe('silent', function () { - it('accepts a boolean', function (done) { - pipe( - [ - globStream(dir + '/fixtures/stuff/run.dmc', { silent: false }), - concat(), - ], - done - ); - }); - }); - - describe('nonull', function () { - it('accepts a boolean', function (done) { - pipe([globStream('notfound{a,b}', { nonull: true }), concat()], done); - }); - - it('does not have any effect on our results', function (done) { - function assert(pathObjs) { - expect(pathObjs.length).toEqual(0); - } + it('throws on invalid options', function (done) { + expect(function () { + globStream('./fixtures/stuff/*.dmc', { cwd: 1234 }); + }).toThrow('must be a string'); + expect(function () { + globStream('./fixtures/stuff/*.dmc', { dot: 1234 }); + }).toThrow('must be a boolean'); + expect(function () { + globStream('./fixtures/stuff/*.dmc', { cwdbase: 1234 }); + }).toThrow('must be a boolean'); + expect(function () { + globStream('./fixtures/stuff/*.dmc', { uniqueBy: 1234 }); + }).toThrow('must be a string or function'); + expect(function () { + globStream('./fixtures/stuff/*.dmc', { allowEmpty: 1234 }); + }).toThrow('must be a boolean'); + expect(function () { + globStream('./fixtures/stuff/*.dmc', { base: 1234 }); + }).toThrow('must be a string if specified'); + expect(function () { + globStream('./fixtures/stuff/*.dmc', { ignore: 1234 }); + }).toThrow('must be a string or array'); - pipe( - [globStream('notfound{a,b}', { nonull: true }), concat(assert)], - done - ); - }); + done(); }); describe('ignore', function () { @@ -799,7 +809,7 @@ describe('options', function () { ); }); - it('merges ignore option and negative globs', function (done) { + it('can use both ignore option and negative globs', function (done) { var globs = ['./fixtures/stuff/*.dmc', '!./fixtures/stuff/test.dmc']; function assert(pathObjs) { @@ -815,4 +825,166 @@ describe('options', function () { ); }); }); + + it('emits an error if there are no matches', function (done) { + function assert(err) { + expect(err.message).toMatch(/^File not found with singular glob/g); + done(); + } + + pipe([globStream('notfound', { cwd: dir }), concat()], assert); + }); + + it('throws an error if you try to write to it', function (done) { + var gs = globStream('notfound', { cwd: dir }); + gs.on('error', function () {}); + + try { + gs.write({}); + } catch (err) { + expect(err).toEqual(expect.anything()); + done(); + } + }); + + it('does not throw an error if you push to it', function (done) { + var stub = { + cwd: dir, + base: dir, + path: dir, + }; + + var gs = globStream('./fixtures/test.coffee', { cwd: dir }); + + gs.push(stub); + + function assert(pathObjs) { + expect(pathObjs.length).toEqual(2); + expect(pathObjs[0]).toEqual(stub); + } + + pipe([gs, concat(assert)], done); + }); + + it('accepts a file path', function (done) { + var expected = { + cwd: dir, + base: dir + '/fixtures', + path: dir + '/fixtures/test.coffee', + }; + + function assert(pathObjs) { + expect(pathObjs.length).toBe(1); + expect(pathObjs[0]).toMatchObject(expected); + } + + pipe( + [globStream('./fixtures/test.coffee', { cwd: dir }), concat(assert)], + done + ); + }); + + it('accepts a glob', function (done) { + var expected = [ + { + cwd: dir, + base: dir + '/fixtures', + path: dir + '/fixtures/has (parens)/test.dmc', + }, + { + cwd: dir, + base: dir + '/fixtures', + path: dir + '/fixtures/stuff/run.dmc', + }, + { + cwd: dir, + base: dir + '/fixtures', + path: dir + '/fixtures/stuff/test.dmc', + }, + ]; + + function assert(pathObjs) { + expect(pathObjs.length).toBe(3); + expect(pathObjs).toContainEqual(expected[0]); + expect(pathObjs).toContainEqual(expected[1]); + expect(pathObjs).toContainEqual(expected[2]); + } + + pipe( + [globStream('./fixtures/**/*.dmc', { cwd: dir }), concat(assert)], + done + ); + }); + + it('pauses the globber upon backpressure', function (done) { + var gs = globStream('./fixtures/**/*.dmc', { cwd: dir, highWaterMark: 1 }); + + function waiter(pathObj, _, cb) { + setTimeout(function () { + cb(null, pathObj); + }, 500); + } + + function assert(pathObjs) { + expect(pathObjs.length).toEqual(3); + } + + pipe( + [gs, through2.obj({ highWaterMark: 1 }, waiter), concat(assert)], + done + ); + }); + + it('destroys the stream with an error if no match is found', function (done) { + var gs = globStream('notfound', { cwd: dir }); + + var spy = sinon.spy(gs, 'destroy'); + + function assert(err) { + sinon.restore(); + expect(spy.getCall(0).args[0]).toBe(err); + expect(err.toString()).toMatch(/File not found with singular glob/); + done(); + } + + pipe([gs, concat()], assert); + }); + + it('destroys the stream if walker errors', function (done) { + var expectedError = new Error('Stubbed error'); + + var gs = globStream('./fixtures/**/*.dmc', { cwd: dir }); + + function stubError(dirpath, opts, cb) { + cb(expectedError); + } + + var spy = sinon.spy(gs, 'destroy'); + sinon.stub(fs, 'readdir').callsFake(stubError); + + function assert(err) { + sinon.restore(); + expect(spy.called).toEqual(true); + expect(err).toBe(expectedError); + done(); + } + + pipe([gs, concat()], assert); + }); + + it('does not emit an error if stream is destroyed without an error', function (done) { + var gs = globStream('./fixtures/**/*.dmc', { cwd: dir }); + + var spy = sinon.spy(); + + gs.on('error', spy); + + gs.on('close', function () { + sinon.restore(); + expect(spy.called).toEqual(false); + done(); + }); + + gs.destroy(); + }); }); diff --git a/test/readable.js b/test/readable.js deleted file mode 100644 index 6025a71..0000000 --- a/test/readable.js +++ /dev/null @@ -1,169 +0,0 @@ -'use strict'; - -var expect = require('expect'); -var sinon = require('sinon'); -var miss = require('mississippi'); - -var stream = require('../readable'); - -// Need to wrap this to cause node-glob to emit an error -var fs = require('fs'); - -function deWindows(p) { - return p.replace(/\\/g, '/'); -} - -var pipe = miss.pipe; -var concat = miss.concat; -var through = miss.through; - -var dir = deWindows(__dirname); - -describe('readable stream', function () { - it('emits an error if there are no matches', function (done) { - function assert(err) { - expect(err.message).toMatch(/^File not found with singular glob/g); - done(); - } - - pipe([stream('notfound', [], { cwd: dir }), concat()], assert); - }); - - it('throws an error if you try to write to it', function (done) { - var gs = stream('notfound', [], { cwd: dir }); - gs.on('error', function () {}); - - try { - gs.write({}); - } catch (err) { - expect(err).toEqual(expect.anything()); - done(); - } - }); - - it('does not throw an error if you push to it', function (done) { - var stub = { - cwd: dir, - base: dir, - path: dir, - }; - - var gs = stream('./fixtures/test.coffee', [], { cwd: dir }); - - gs.push(stub); - - function assert(pathObjs) { - expect(pathObjs.length).toEqual(2); - expect(pathObjs[0]).toEqual(stub); - } - - pipe([gs, concat(assert)], done); - }); - - it('accepts a file path', function (done) { - var expected = { - cwd: dir, - base: dir + '/fixtures', - path: dir + '/fixtures/test.coffee', - }; - - function assert(pathObjs) { - expect(pathObjs.length).toBe(1); - expect(pathObjs[0]).toMatchObject(expected); - } - - pipe( - [stream('./fixtures/test.coffee', [], { cwd: dir }), concat(assert)], - done - ); - }); - - it('accepts a glob', function (done) { - var expected = [ - { - cwd: dir, - base: dir + '/fixtures', - path: dir + '/fixtures/has (parens)/test.dmc', - }, - { - cwd: dir, - base: dir + '/fixtures', - path: dir + '/fixtures/stuff/run.dmc', - }, - { - cwd: dir, - base: dir + '/fixtures', - path: dir + '/fixtures/stuff/test.dmc', - }, - ]; - - function assert(pathObjs) { - expect(pathObjs.length).toBe(3); - expect(pathObjs).toContainEqual(expected[0]); - expect(pathObjs).toContainEqual(expected[1]); - expect(pathObjs).toContainEqual(expected[2]); - } - - pipe( - [stream('./fixtures/**/*.dmc', [], { cwd: dir }), concat(assert)], - done - ); - }); - - it('pauses the globber upon backpressure', function (done) { - var gs = stream('./fixtures/**/*.dmc', [], { cwd: dir, highWaterMark: 1 }); - - var spy = sinon.spy(gs._globber, 'pause'); - - function waiter(pathObj, _, cb) { - setTimeout(function () { - cb(null, pathObj); - }, 500); - } - - function assert(pathObjs) { - expect(pathObjs.length).toEqual(3); - expect(spy.callCount).toEqual(2); - sinon.restore(); - } - - pipe([gs, through.obj({ highWaterMark: 1 }, waiter), concat(assert)], done); - }); - - it('destroys the stream with an error if no match is found', function (done) { - var gs = stream('notfound', []); - - var spy = sinon.spy(gs, 'destroy'); - - function assert(err) { - sinon.restore(); - expect(spy.getCall(0).args[0]).toBe(err); - expect(err.toString()).toMatch(/File not found with singular glob/); - done(); - } - - pipe([gs, concat()], assert); - }); - - it('destroys the stream if node-glob errors', function (done) { - var expectedError = new Error('Stubbed error'); - - var gs = stream('./fixtures/**/*.dmc', [], { cwd: dir, silent: true }); - - function stubError(dirpath, cb) { - cb(expectedError); - } - - var spy = sinon.spy(gs, 'destroy'); - sinon.stub(fs, 'readdir').callsFake(stubError); - - function assert(err) { - sinon.restore(); - expect(spy.called).toEqual(true); - expect(err).toBe(expectedError); - done(); - } - - pipe([gs, concat()], assert); - }); -});