diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5760be5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..a5a4a17 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "gulp" +} diff --git a/.gitignore b/.gitignore index b5ef13a..a2977d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,30 @@ -.DS_Store +# Logs +logs *.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules -build -*.node -components \ No newline at end of file + +# Artifacts +test/fixtures diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..703b33f --- /dev/null +++ b/.jscsrc @@ -0,0 +1,3 @@ +{ + "preset": "gulp" +} diff --git a/.travis.yml b/.travis.yml index 4b40d02..50ff086 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: node_js node_js: - - "0.10" - - "0.12" - - "iojs" + - 'stable' + - '4' + - '0.12' + - '0.10' after_script: - npm run coveralls diff --git a/LICENSE b/LICENSE index 4f482f9..9aedc0d 100755 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,21 @@ -Copyright (c) 2013 Fractal +The MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Copyright (c) 2015 Blaine Bublitz, Eric Schoffstall and other contributors -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 6bc1ba7..47bc767 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,126 @@ -# glob-watcher [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![Dependency Status][david-image]][david-url] - -## Information - - - - - - - - - - - - - -
Packageglob-watcher
DescriptionWatch globs
Node Version>= 0.10
- -## Usage - -```javascript +

+ + + +

+ +# glob-watcher + +[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![AppVeyor Build Status][appveyor-image]][appveyor-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![Gitter chat][gitter-image]][gitter-url] + +Watch globs and execute a function upon change, with intelligent defaults for debouncing and queueing. + +## Example + +```js var watch = require('glob-watcher'); -// callback interface -watch(['./*.js', '!./something.js'], function(){ - // this function will be called each time a globbed - // file is changed +watch(['./*.js', '!./something.js'], function(done){ + // This function will be called each time a globbed file is changed + // but is debounced with a 200ms delay (default) and queues subsequent calls - // if you need access to the `evt` object, listen + // Make sure to signal async completion with the callback + // or by returning a stream, promise, observable or child process + done(); + + // if you need access to the `path` or `stat` object, listen // for the `change` event (see below) + + // if you need to listen to specific events, use the returned + // watcher instance (see below) }); -// EE interface +// Raw chokidar instance var watcher = watch(['./*.js', '!./something.js']); -watcher.on('change', function(evt) { - // evt has what file changed and all that jazz + +// Listen for the 'change' event to get `path`/`stat` +// No async completion available because this is the raw chokidar instance +watcher.on('change', function(path, stat) { + // `path` is the path of the changed file + // `stat` is an `fs.Stat` object (not always available) }); -// add files after it has been created -watcher.add('./somefolder/somefile.js'); +// Listen for other events +// No async completion available because this is the raw chokidar instance +watcher.on('add', function(path, stat) { + // `path` is the path of the changed file + // `stat` is an `fs.Stat` object (not always available) +}); ``` +## API + +### `watch(globs[, options][, fn])` + +Takes a path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings as `globs` to watch on the filesystem. Also optionally takes `options` to configure the watcher and a `fn` to execute when a file changes. + +Returns an instance of [chokidar][chokidar]. + +#### `fn([callback])` + +If the `fn` is passed, it will be called when the watcher emits a `change`, `add` or `unlink` event. It is automatically debounced with a default delay of 200 milliseconds and subsequent calls will be queued and called upon completion. These defaults can be changed using the `options`. + +The `fn` is passed a single argument, `callback`, which is a function that must be called when work in the `fn` is complete. Instead of calling the `callback` function, [async completion][async-completion] can be signalled by: + * Returning a `Stream` or `EventEmitter` + * Returning a `Child Process` + * Returning a `Promise` + * Returning an `Observable` + +Once async completion is signalled, if another run is queued, it will be executed. + +#### `options` + +##### `options.ignoreInitial` + +If set to `false` the `fn` is called during [chokidar][chokidar] instantiation as it discovers the file paths. Useful if it is desirable to trigger the `fn` during startup. + +__Passed through to [chokidar][chokidar], but defaulted to `true` instead of `false`.__ + +Type: `Boolean` + +Default: `true` + +##### `options.delay` + +The delay to wait before triggering the `fn`. Useful for waiting on many changes before doing the work on changed files, e.g. find-and-replace on many files. + +Type: `Number` + +Default: `200` (milliseconds) + +##### `options.queue` + +Whether or not a file change should queue the `fn` execution if the `fn` is already running. Useful for a long running `fn`. + +Type: `Boolean` + +Default: `true` + +##### other + +Options are passed directly to [lodash.debounce][lodash-debounce] and [chokidar][chokidar], so all their options are supported. Any debounce-related options are documented in [lodash.debounce][lodash-debounce]. Any chokidar-related options are documented in [chokidar][chokidar]. + +## License + +MIT + +[downloads-image]: http://img.shields.io/npm/dm/glob-watcher.svg +[npm-url]: https://npmjs.com/package/glob-watcher +[npm-image]: http://img.shields.io/npm/v/glob-watcher.svg -[npm-url]: https://npmjs.org/package/glob-watcher -[npm-image]: https://badge.fury.io/js/glob-watcher.png +[travis-url]: https://travis-ci.org/gulpjs/glob-watcher +[travis-image]: http://img.shields.io/travis/gulpjs/glob-watcher.svg?label=travis-ci -[travis-url]: https://travis-ci.org/wearefractal/glob-watcher -[travis-image]: https://travis-ci.org/wearefractal/glob-watcher.png?branch=master +[appveyor-url]: https://ci.appveyor.com/project/gulpjs/glob-watcher +[appveyor-image]: https://img.shields.io/appveyor/ci/gulpjs/glob-watcher.svg?label=appveyor -[coveralls-url]: https://coveralls.io/r/wearefractal/glob-watcher -[coveralls-image]: https://coveralls.io/repos/wearefractal/glob-watcher/badge.png +[coveralls-url]: https://coveralls.io/r/gulpjs/glob-watcher +[coveralls-image]: http://img.shields.io/coveralls/gulpjs/glob-watcher/master.svg -[depstat-url]: https://david-dm.org/wearefractal/glob-watcher -[depstat-image]: https://david-dm.org/wearefractal/glob-watcher.png +[gitter-url]: https://gitter.im/gulpjs/gulp +[gitter-image]: https://badges.gitter.im/gulpjs/gulp.png -[david-url]: https://david-dm.org/wearefractal/glob-watcher -[david-image]: https://david-dm.org/wearefractal/glob-watcher.png?theme=shields.io +[glob]: https://github.com/isaacs/node-glob +[async-completion]: https://github.com/gulpjs/async-done#completion-and-error-resolution +[chokidar]: https://github.com/paulmillr/chokidar +[lodash-debounce]: https://lodash.com/docs#debounce diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..ebd6fc0 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,24 @@ +# http://www.appveyor.com/docs/appveyor-yml +# http://www.appveyor.com/docs/lang/nodejs-iojs + +environment: + matrix: + # node.js + - nodejs_version: "0.10" + - nodejs_version: "0.12" + - nodejs_version: "4" + - nodejs_version: "5" + +install: + - ps: Install-Product node $env:nodejs_version + - npm install + +test_script: + - node --version + - npm --version + - cmd: npm test + +build: off + +# build version format +version: "{build}" diff --git a/index.js b/index.js index 2b47e55..392ed85 100644 --- a/index.js +++ b/index.js @@ -1,43 +1,68 @@ -var gaze = require('gaze'); -var EventEmitter = require('events').EventEmitter; - -function onWatch(out, cb){ - return function(err, rwatcher){ - if (err) out.emit('error', err); - rwatcher.on('all', function(evt, path, old){ - var outEvt = {type: evt, path: path}; - if(old) outEvt.old = old; - out.emit('change', outEvt); - if(cb) cb(); - }); - } +'use strict'; + +var chokidar = require('chokidar'); +var debounce = require('lodash.debounce'); +var asyncDone = require('async-done'); +var assignWith = require('lodash.assignwith'); + +function assignNullish(objValue, srcValue) { + return (srcValue == null ? objValue : srcValue); } -module.exports = function(glob, opts, cb) { - var out = new EventEmitter(); +var defaults = { + ignoreInitial: true, + delay: 200, + queue: true, +}; - if (typeof opts === 'function') { - cb = opts; - opts = {}; +function watch(glob, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; } - var watcher = gaze(glob, opts, onWatch(out, cb)); - - watcher.on('end', out.emit.bind(out, 'end')); - watcher.on('error', out.emit.bind(out, 'error')); - watcher.on('ready', out.emit.bind(out, 'ready')); - watcher.on('nomatch', out.emit.bind(out, 'nomatch')); - - out.end = function(){ - return watcher.close(); - }; - out.add = function(glob, cb){ - return watcher.add(glob, onWatch(out, cb)); - }; - out.remove = function(glob){ - return watcher.remove(glob); - }; - out._watcher = watcher; - - return out; -}; + var opt = assignWith({}, defaults, options, assignNullish); + + var queued = false; + var running = false; + + var watcher = chokidar.watch(glob, opt); + + function runComplete(err) { + running = false; + + if (err) { + watcher.emit('error', err); + } + + // If we have a run queued, start onChange again + if (queued) { + queued = false; + onChange(); + } + } + + function onChange() { + if (running) { + if (opt.queue) { + queued = true; + } + return; + } + + running = true; + asyncDone(cb, runComplete); + } + + if (typeof cb === 'function') { + var fn = debounce(onChange, opt.delay, opt); + watcher + .on('change', fn) + .on('unlink', fn) + .on('add', fn); + } + + return watcher; +} + +module.exports = watch; diff --git a/package.json b/package.json index 1812fde..abaae33 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,51 @@ { "name": "glob-watcher", - "description": "Watch globs", "version": "2.0.0", - "homepage": "http://github.com/wearefractal/glob-watcher", - "repository": "git://github.com/wearefractal/glob-watcher.git", - "author": "Fractal (http://wearefractal.com/)", - "main": "./index.js", + "description": "Watch globs and execute a function upon change, with intelligent defaults for debouncing and queueing.", + "author": "Gulp Team (http://gulpjs.com/)", + "contributors": [], + "repository": "gulpjs/glob-watcher", + "license": "MIT", + "engines": { + "node": ">= 0.10" + }, + "main": "index.js", "files": [ - "index.js", - "lib" + "index.js" ], + "scripts": { + "lint": "eslint . && jscs index.js test/", + "pretest": "npm run lint", + "test": "mocha --async-only", + "cover": "istanbul cover _mocha --report lcovonly", + "coveralls": "npm run cover && istanbul-coveralls" + }, "dependencies": { - "gaze": "^0.5.1" + "async-done": "^1.2.0", + "chokidar": "^1.4.3", + "lodash.assignwith": "^4.0.6", + "lodash.debounce": "^4.0.6" }, "devDependencies": { - "mocha": "^2.0.1", - "should": "^6.0.0", - "mocha-lcov-reporter": "^0.0.2", - "coveralls": "^2.6.1", - "istanbul": "^0.3.0", - "rimraf": "^2.2.5", - "jshint": "^2.4.1", - "mkdirp": "^0.5.0" - }, - "scripts": { - "test": "mocha --reporter spec && jshint", - "coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage" - }, - "engines": { - "node": ">= 0.10" + "coveralls": "^2.11.2", + "del": "^2.2.0", + "eslint": "^1.10.3", + "eslint-config-gulp": "^2.0.0", + "expect": "^1.16.0", + "istanbul": "^0.4.0", + "istanbul-coveralls": "^1.0.1", + "jscs": "^2.3.5", + "jscs-preset-gulp": "^1.0.0", + "mocha": "^2.0.0", + "mocha-lcov-reporter": "^1.2.0", + "through2": "^2.0.1" }, - "licenses": [ - { - "type": "MIT", - "url": "http://github.com/wearefractal/glob-watcher/raw/master/LICENSE" - } + "keywords": [ + "watch", + "glob", + "async", + "queue", + "debounce", + "callback" ] } diff --git a/test/fixtures/test.coffee b/test/fixtures/test.coffee deleted file mode 100644 index 4be3c45..0000000 --- a/test/fixtures/test.coffee +++ /dev/null @@ -1 +0,0 @@ -test test \ No newline at end of file diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..f87ca08 --- /dev/null +++ b/test/index.js @@ -0,0 +1,251 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +var del = require('del'); +var expect = require('expect'); +var through = require('through2'); + +var watch = require('../'); + +// Default delay on debounce +var timeout = 200; + +describe('glob-watcher', function() { + + var watcher; + + var outDir = path.join(__dirname, './fixtures/'); + var outFile1 = path.join(outDir, 'changed.js'); + var outFile2 = path.join(outDir, 'added.js'); + var outGlob = path.join(outDir, './**/*.js'); + + function changeFile() { + fs.writeFileSync(outFile1, 'hello changed'); + } + + function addFile() { + fs.writeFileSync(outFile2, 'hello added'); + } + + beforeEach(function(cb) { + fs.mkdirSync(outDir); + fs.writeFileSync(outFile1, 'hello world'); + cb(); + }); + + afterEach(function() { + if (watcher) { + watcher.close(); + } + return del(outDir); + }); + + it('only requires a glob and returns watcher', function(done) { + watcher = watch(outGlob); + + watcher.once('change', function(path) { + expect(path).toEqual(outFile1); + done(); + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', changeFile); + }); + + it('picks up added files', function(done) { + watcher = watch(outGlob); + + watcher.once('add', function(path) { + expect(path).toEqual(outFile2); + done(); + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', addFile); + }); + + it('accepts a callback & calls when file is changed', function(done) { + watcher = watch(outGlob, function(cb) { + cb(); + done(); + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', changeFile); + }); + + it('accepts a callback & calls when file is added', function(done) { + watcher = watch(outGlob, function(cb) { + cb(); + done(); + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', addFile); + }); + + it('waits for completion is signaled before running again', function(done) { + var runs = 0; + + watcher = watch(outGlob, function(cb) { + runs++; + if (runs === 1) { + setTimeout(function() { + expect(runs).toEqual(1); + cb(); + }, timeout * 3); + } + if (runs === 2) { + cb(); + done(); + } + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', function() { + changeFile(); + // Fire after double the delay + setTimeout(changeFile, timeout * 2); + }); + }); + + // It can signal completion with anything async-done supports + // Just wanted to have a smoke test for streams + it('can signal completion with a stream', function(done) { + var runs = 0; + + watcher = watch(outGlob, function(cb) { + runs++; + if (runs === 1) { + var stream = through(); + setTimeout(function() { + expect(runs).toEqual(1); + stream.end(); + }, timeout * 3); + return stream; + } + if (runs === 2) { + cb(); + done(); + } + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', function() { + changeFile(); + // Fire after double the delay + setTimeout(changeFile, timeout * 2); + }); + }); + + it('emits an error if one occurs in the callback', function(done) { + var expectedError = new Error('boom'); + + watcher = watch(outGlob, function(cb) { + cb(expectedError); + }); + + watcher.on('error', function(err) { + expect(err).toEqual(expectedError); + done(); + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', changeFile); + }); + + it('allows the user to disable queueing', function(done) { + var runs = 0; + + watcher = watch(outGlob, { queue: false }, function(cb) { + runs++; + setTimeout(function() { + // Expect 1 because run 2 is never queued + expect(runs).toEqual(1); + cb(); + done(); + }, timeout * 3); + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', function() { + changeFile(); + // This will never trigger a call because queueing is disabled + setTimeout(changeFile, timeout * 2); + }); + }); + + it('allows the user to adjust delay', function(done) { + var runs = 0; + + watcher = watch(outGlob, { delay: (timeout / 2) }, function(cb) { + runs++; + if (runs === 1) { + setTimeout(function() { + expect(runs).toEqual(1); + cb(); + }, timeout * 3); + } + if (runs === 2) { + expect(runs).toEqual(2); + cb(); + done(); + } + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', function() { + changeFile(); + // This will queue because delay is halved + setTimeout(changeFile, timeout); + }); + }); + + it('passes options to chokidar', function(done) { + // Callback is called while chokidar is discovering file paths + // if ignoreInitial is explicitly set to false and passed to chokidar + watcher = watch(outGlob, { ignoreInitial: false }, function(cb) { + cb(); + done(); + }); + }); + + it('passes options to lodash.debounce', function(done) { + var runs = 0; + + watcher = watch(outGlob, { leading: true }, function(cb) { + runs++; + if (runs === 1) { + setTimeout(function() { + expect(runs).toEqual(1); + cb(); + }, timeout * 3); + } + if (runs === 2) { + expect(runs).toEqual(2); + cb(); + done(); + } + }); + + // We default `ignoreInitial` to true, so always wait for `on('ready')` + watcher.on('ready', function() { + changeFile(); + // Fires on the leading edge on the debounce + setTimeout(changeFile, timeout); + }); + }); + + it('does not override default values with null values', function(done) { + watcher = watch(outGlob, { ignoreInitial: null }, function(cb) { + cb(); + done(); + }); + + // We default `ignoreInitial` to true and it isn't overwritten by null + // So wait for `on('ready')` + watcher.on('ready', changeFile); + }); +}); diff --git a/test/main.js b/test/main.js deleted file mode 100644 index 4b604f6..0000000 --- a/test/main.js +++ /dev/null @@ -1,131 +0,0 @@ -var watch = require('../'); -var should = require('should'); -var path = require('path'); -var fs = require('fs'); -var rimraf = require('rimraf'); -var mkdirp = require('mkdirp'); - -require('mocha'); - -// timeout is 100ms by default on gaze -// so any file modifications are done on this interval -var timeout = 100; - -describe('glob-watcher', function() { - it('should return a valid file struct via EE', function(done) { - var expectedName = path.join(__dirname, './fixtures/stuff/temp.coffee'); - var fname = path.join(__dirname, './fixtures/**/temp.coffee'); - mkdirp.sync(path.dirname(expectedName)); - fs.writeFileSync(expectedName, 'testing'); - - var watcher = watch(fname); - watcher.once('change', function(evt) { - should.exist(evt); - should.exist(evt.path); - should.exist(evt.type); - evt.type.should.equal('changed'); - evt.path.should.equal(expectedName); - watcher.end(); - }); - watcher.once('end', function(){ - rimraf.sync(expectedName); - done(); - }); - setTimeout(function(){ - fs.writeFileSync(expectedName, 'test test'); - }, timeout); - }); - - it('should emit nomatch via EE', function(done) { - var fname = path.join(__dirname, './doesnt_exist_lol/temp.coffee'); - - var watcher = watch(fname); - watcher.once('nomatch', function() { - done(); - }); - }); - - it('should not pass any args via callback', function(done) { - var expectedName = path.join(__dirname, './fixtures/stuff/test.coffee'); - var fname = path.join(__dirname, './fixtures/**/test.coffee'); - mkdirp.sync(path.dirname(expectedName)); - fs.writeFileSync(expectedName, 'testing'); - - var watcher = watch(fname, function() { - arguments.length.should.equal(0); - watcher.end(); - }); - - watcher.once('end', function(){ - rimraf.sync(expectedName); - done(); - }); - setTimeout(function(){ - fs.writeFileSync(expectedName, 'test test'); - }, timeout); - }); - - it('should call the callback registered with `add`', function(done) { - var expectedName = path.join(__dirname, './fixtures/stuff/test.coffee'); - var fname = path.join(__dirname, './fixtures/**/test.coffee'); - mkdirp.sync(path.dirname(expectedName)); - fs.writeFileSync(expectedName, 'testing'); - - var watcher = watch(); - watcher.add(fname, function() { - arguments.length.should.equal(0); - watcher.end(); - }); - - watcher.once('end', function(){ - rimraf.sync(expectedName); - done(); - }); - setTimeout(function(){ - fs.writeFileSync(expectedName, 'test test'); - }, timeout); - }); - - it('should not call a callback when filepath removed with `remove`', function(done) { - var expectedName = path.join(__dirname, './fixtures/stuff/test.coffee'); - var fname = path.join(__dirname, './fixtures/**/test.coffee'); - mkdirp.sync(path.dirname(expectedName)); - fs.writeFileSync(expectedName, 'testing'); - - var watcher = watch(); - watcher.add(fname, function() { - // if we get here, that's bad and should fail - true.should.equal(false); - watcher.end(); - }); - watcher.remove(expectedName); - - setTimeout(function(){ - fs.writeFileSync(expectedName, 'test test'); - }, timeout); - setTimeout(function(){ - rimraf.sync(expectedName); - done(); - }, timeout * 3); - }); - - it('should not return a non-matching file struct via callback', function(done) { - var expectedName = path.join(__dirname, './fixtures/test123.coffee'); - var fname = path.join(__dirname, './fixtures/**/test.coffee'); - mkdirp.sync(path.dirname(expectedName)); - fs.writeFileSync(expectedName, 'testing'); - - var watcher = watch(fname, function(evt) { - throw new Error('Should not have been called! '+evt.path); - }); - - setTimeout(function(){ - fs.writeFileSync(expectedName, 'test test'); - }, timeout); - - setTimeout(function(){ - rimraf.sync(expectedName); - done(); - }, timeout * 2); - }); -});