From bd40b07968cd7a5255dbd97e0f5a99b51037aa52 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Mon, 11 Feb 2019 20:24:26 -0800 Subject: [PATCH 01/10] Rename comments; for easier search. --- index.js | 10 +++++----- lib/nodefs-handler.js | 34 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index fcf661a7..2795276d 100644 --- a/index.js +++ b/index.js @@ -89,7 +89,7 @@ function FSWatcher(_opts) { if (!FsEventsHandler.canUse()) opts.useFsEvents = false; // Use polling on Mac if not using fsevents. - // Other platforms use non-polling fs.watch. + // Other platforms use non-polling fs_watch. if (undef('usePolling') && !opts.useFsEvents) { opts.usePolling = process.platform === 'darwin'; } @@ -113,7 +113,7 @@ function FSWatcher(_opts) { opts.interval = parseInt(envInterval); } - // Editor atomic write normalization enabled by default with fs.watch + // Editor atomic write normalization enabled by default with fs_watch if (undef('atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents; if (opts.atomic) this._pendingUnlinks = Object.create(null); @@ -231,7 +231,7 @@ FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { ) { var fullPath = this.options.cwd ? sysPath.join(this.options.cwd, path) : path; fs.stat(fullPath, function(error, stats) { - // Suppress event when fs.stat fails, to avoid sending undefined 'stat' + // Suppress event when fs_stat fails, to avoid sending undefined 'stat' if (error || !stats) return; args.push(stats); @@ -350,7 +350,7 @@ FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit // Private method: Determines whether user has asked to ignore this path // // * path - string, path to file or directory -// * stats - object, result of fs.stat +// * stats - object, result of fs_stat // // Returns boolean var dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/; @@ -510,7 +510,7 @@ FSWatcher.prototype._getWatchedDir = function(directory) { // Private method: Check for read permissions // Based on this answer on SO: http://stackoverflow.com/a/11781404/1358405 // -// * stats - object, result of fs.stat +// * stats - object, result of fs_stat // // Returns boolean FSWatcher.prototype._hasReadPermissions = function(stats) { diff --git a/lib/nodefs-handler.js b/lib/nodefs-handler.js index 2a116ee2..23d8ea60 100644 --- a/lib/nodefs-handler.js +++ b/lib/nodefs-handler.js @@ -5,9 +5,9 @@ var sysPath = require('path'); var readdirp = require('readdirp'); var isBinaryPath = require('is-binary-path'); -// fs.watch helpers +// fs_watch helpers -// object to hold per-process fs.watch instances +// object to hold per-process fs_watch instances // (may be shared across chokidar FSWatcher instances) var FsWatchInstances = Object.create(null); @@ -16,10 +16,10 @@ var FsWatchInstances = Object.create(null); // or when only atime is updated (which we want to ignore) var accessTimeDelayThreshold = 1000; -// Private function: Instantiates the fs.watch interface +// Private function: Instantiates the fs_watch interface // * path - string, path to be watched -// * options - object, options to be passed to fs.watch +// * options - object, options to be passed to fs_watch // * listener - function, main event handler // * errHandler - function, handler which emits info about errors // * emitRaw - function, handler which emits raw event data @@ -45,10 +45,10 @@ function createFsWatchInstance(path, options, listener, errHandler, emitRaw) { } } -// Private function: Helper for passing fs.watch event data to a +// Private function: Helper for passing fs_watch event data to a // collection of listeners -// * fullPath - string, absolute path bound to the fs.watch instance +// * fullPath - string, absolute path bound to the fs_watch instance // * type - string, listener type // * val[1..3] - arguments to be passed to listeners @@ -60,12 +60,12 @@ function fsWatchBroadcast(fullPath, type, val1, val2, val3) { }); } -// Private function: Instantiates the fs.watch interface or binds listeners +// Private function: Instantiates the fs_watch interface or binds listeners // to an existing one covering the same file system entry // * path - string, path to be watched // * fullPath - string, absolute path -// * options - object, options to be passed to fs.watch +// * options - object, options to be passed to fs_watch // * handlers - object, container for event listener functions // Returns close function @@ -117,7 +117,7 @@ function setFsWatchListener(path, fullPath, options, handlers) { } var listenerIndex = container.listeners.length - 1; - // removes this instance's listeners and closes the underlying fs.watch + // removes this instance's listeners and closes the underlying fs_watch // instance if there are no more listeners left return function close() { delete container.listeners[listenerIndex]; @@ -132,18 +132,18 @@ function setFsWatchListener(path, fullPath, options, handlers) { }; } -// fs.watchFile helpers +// fs_watchFile helpers -// object to hold per-process fs.watchFile instances +// object to hold per-process fs_watchFile instances // (may be shared across chokidar FSWatcher instances) var FsWatchFileInstances = Object.create(null); -// Private function: Instantiates the fs.watchFile interface or binds listeners +// Private function: Instantiates the fs_watchFile interface or binds listeners // to an existing one covering the same file system entry // * path - string, path to be watched // * fullPath - string, absolute path -// * options - object, options to be passed to fs.watchFile +// * options - object, options to be passed to fs_watchFile // * handlers - object, container for event listener functions // Returns close function @@ -193,7 +193,7 @@ function setFsWatchFileListener(path, fullPath, options, handlers) { } var listenerIndex = container.listeners.length - 1; - // removes this instance's listeners and closes the underlying fs.watchFile + // removes this instance's listeners and closes the underlying fs_watchFile // instance if there are no more listeners left return function close() { delete container.listeners[listenerIndex]; @@ -209,7 +209,7 @@ function setFsWatchFileListener(path, fullPath, options, handlers) { // will be copied to FSWatcher's prototype function NodeFsHandler() {} -// Private method: Watch file for changes with fs.watchFile or fs.watch. +// Private method: Watch file for changes with fs_watchFile or fs_watch. // * path - string, path to file or directory. // * listener - function, to be executed on fs change. @@ -246,7 +246,7 @@ function(path, listener) { // Private method: Watch a file and emit add event if warranted // * file - string, the file's path -// * stats - object, result of fs.stat +// * stats - object, result of fs_stat // * initialAdd - boolean, was the file added at watch instantiation? // * callback - function, called when done processing as a newly seen file @@ -339,7 +339,7 @@ function(entry, directory, path, item) { // and re-read it on change. // * dir - string, fs path. -// * stats - object, result of fs.stat +// * stats - object, result of fs_stat // * initialAdd - boolean, was the file added at watch instantiation? // * depth - int, depth relative to user-supplied path // * target - string, child path actually targeted for watch From 43ed062f3334b56c1882b4be84f893fcd658c271 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Mon, 11 Feb 2019 22:11:45 -0800 Subject: [PATCH 02/10] Switch to const from vars. --- .jshintrc | 1 + index.js | 175 ++++++++++++++++++++-------------------- lib/fsevents-handler.js | 94 ++++++++++----------- lib/nodefs-handler.js | 107 ++++++++++++------------ package.json | 1 + 5 files changed, 190 insertions(+), 188 deletions(-) diff --git a/.jshintrc b/.jshintrc index 6ee10d9b..0fa621d1 100644 --- a/.jshintrc +++ b/.jshintrc @@ -4,5 +4,6 @@ "bitwise": false, "mocha": true, "expr": true, + "esversion": 8, "predef": ["toString"] } diff --git a/index.js b/index.js index 2795276d..882da405 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,26 @@ 'use strict'; -var EventEmitter = require('events').EventEmitter; -var fs = require('fs'); -var sysPath = require('path'); -var asyncEach = require('async-each'); -var anymatch = require('anymatch'); -var globParent = require('glob-parent'); -var isGlob = require('is-glob'); -var isAbsolute = require('path-is-absolute'); -var inherits = require('inherits'); -var braces = require('braces'); -var normalizePath = require('normalize-path'); -var upath = require('upath'); - -var NodeFsHandler = require('./lib/nodefs-handler'); -var FsEventsHandler = require('./lib/fsevents-handler'); - -var arrify = function(value) { +const EventEmitter = require('events').EventEmitter; +const fs = require('fs'); +const sysPath = require('path'); +const asyncEach = require('async-each'); +const anymatch = require('anymatch'); +const globParent = require('glob-parent'); +const isGlob = require('is-glob'); +const isAbsolute = require('path-is-absolute'); +const inherits = require('inherits'); +const braces = require('braces'); +const normalizePath = require('normalize-path'); +const upath = require('upath'); + +const NodeFsHandler = require('./lib/nodefs-handler'); +const FsEventsHandler = require('./lib/fsevents-handler'); + +const arrify = function(value) { if (value == null) return []; return Array.isArray(value) ? value : [value]; }; -var flatten = function(list, result) { +const flatten = function(list, result) { if (result == null) result = []; list.forEach(function(item) { if (Array.isArray(item)) { @@ -33,7 +33,7 @@ var flatten = function(list, result) { }; // Little isString util for use in Array#every. -var isString = function(thing) { +const isString = function(thing) { return typeof thing === 'string'; }; @@ -47,7 +47,7 @@ var isString = function(thing) { // // Examples // -// var watcher = new FSWatcher() +// const watcher = new FSWatcher() // .add(directories) // .on('add', path => console.log('File', path, 'was added')) // .on('change', path => console.log('File', path, 'was changed')) @@ -56,9 +56,9 @@ var isString = function(thing) { // function FSWatcher(_opts) { EventEmitter.call(this); - var opts = {}; + const opts = {}; // in case _opts that is passed in is a frozen object - if (_opts) for (var opt in _opts) opts[opt] = _opts[opt]; + if (_opts) for (const opt in _opts) opts[opt] = _opts[opt]; this._watched = Object.create(null); this._closers = Object.create(null); this._ignoredPaths = Object.create(null); @@ -96,19 +96,19 @@ function FSWatcher(_opts) { // Global override (useful for end-developers that need to force polling for all // instances of chokidar, regardless of usage/dependency depth) - var envPoll = process.env.CHOKIDAR_USEPOLLING; + const envPoll = process.env.CHOKIDAR_USEPOLLING; if (envPoll !== undefined) { - var envLower = envPoll.toLowerCase(); + const envLower = envPoll.toLowerCase(); if (envLower === 'false' || envLower === '0') { opts.usePolling = false; } else if (envLower === 'true' || envLower === '1') { opts.usePolling = true; } else { - opts.usePolling = !!envLower + opts.usePolling = !!envLower; } } - var envInterval = process.env.CHOKIDAR_INTERVAL; + const envInterval = process.env.CHOKIDAR_INTERVAL; if (envInterval) { opts.interval = parseInt(envInterval); } @@ -121,7 +121,7 @@ function FSWatcher(_opts) { if (undef('awaitWriteFinish')) opts.awaitWriteFinish = false; if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {}; - var awf = opts.awaitWriteFinish; + const awf = opts.awaitWriteFinish; if (awf) { if (!awf.stabilityThreshold) awf.stabilityThreshold = 2000; if (!awf.pollInterval) awf.pollInterval = 100; @@ -134,7 +134,7 @@ function FSWatcher(_opts) { return !this._isIgnored(path, stat); }.bind(this); - var readyCalls = 0; + let readyCalls = 0; this._emitReady = function() { if (++readyCalls >= this._readyCount) { this._emitReady = Function.prototype; @@ -165,12 +165,12 @@ inherits(FSWatcher, EventEmitter); // FSWatcher instance's `closed` flag FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { if (this.options.cwd) path = sysPath.relative(this.options.cwd, path); - var args = [event, path]; + const args = [event, path]; if (val3 !== undefined) args.push(val1, val2, val3); else if (val2 !== undefined) args.push(val1, val2); else if (val1 !== undefined) args.push(val1); - var awf = this.options.awaitWriteFinish; + const awf = this.options.awaitWriteFinish; if (awf && this._pendingWrites[path]) { this._pendingWrites[path].lastChange = new Date(); return this; @@ -185,9 +185,8 @@ FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { this.emit.apply(this, ['all'].concat(this._pendingUnlinks[path])); delete this._pendingUnlinks[path]; }.bind(this)); - }.bind(this), typeof this.options.atomic === "number" - ? this.options.atomic - : 100); + }.bind(this), + typeof this.options.atomic === "number" ? this.options.atomic : 100); return this; } else if (event === 'add' && this._pendingUnlinks[path]) { event = args[0] = 'change'; @@ -195,13 +194,13 @@ FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { } } - var emitEvent = function() { + const emitEvent = function() { this.emit.apply(this, args); if (event !== 'error') this.emit.apply(this, ['all'].concat(args)); }.bind(this); if (awf && (event === 'add' || event === 'change') && this._readyEmitted) { - var awfEmit = function(err, stats) { + const awfEmit = function(err, stats) { if (err) { event = args[0] = 'error'; args[1] = err; @@ -229,7 +228,7 @@ FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { this.options.alwaysStat && val1 === undefined && (event === 'add' || event === 'addDir' || event === 'change') ) { - var fullPath = this.options.cwd ? sysPath.join(this.options.cwd, path) : path; + const fullPath = this.options.cwd ? sysPath.join(this.options.cwd, path) : path; fs.stat(fullPath, function(error, stats) { // Suppress event when fs_stat fails, to avoid sending undefined 'stat' if (error || !stats) return; @@ -251,8 +250,8 @@ FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { // Returns the error if defined, otherwise the value of the // FSWatcher instance's `closed` flag FSWatcher.prototype._handleError = function(error) { - var code = error && error.code; - var ipe = this.options.ignorePermissionErrors; + const code = error && error.code; + const ipe = this.options.ignorePermissionErrors; if (error && code !== 'ENOENT' && code !== 'ENOTDIR' && @@ -272,18 +271,18 @@ FSWatcher.prototype._throttle = function(action, path, timeout) { if (!(action in this._throttled)) { this._throttled[action] = Object.create(null); } - var throttled = this._throttled[action]; + const throttled = this._throttled[action]; if (path in throttled) { throttled[path].count++; return false; } function clear() { - var count = throttled[path] ? throttled[path].count : 0; + const count = throttled[path] ? throttled[path].count : 0; delete throttled[path]; clearTimeout(timeoutObject); return count; } - var timeoutObject = setTimeout(clear, timeout); + const timeoutObject = setTimeout(clear, timeout); throttled[path] = {timeoutObject: timeoutObject, clear: clear, count: 0}; return throttled[path]; }; @@ -297,23 +296,23 @@ FSWatcher.prototype._throttle = function(action, path, timeout) { // Polls a newly created file for size variations. When files size does not // change for 'threshold' milliseconds calls callback. FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit) { - var timeoutHandler; + let timeoutHandler; - var fullPath = path; + let fullPath = path; if (this.options.cwd && !isAbsolute(path)) { fullPath = sysPath.join(this.options.cwd, path); } - var now = new Date(); + const now = new Date(); - var awaitWriteFinish = (function (prevStat) { + const awaitWriteFinish = (function (prevStat) { fs.stat(fullPath, function(err, curStat) { if (err || !(path in this._pendingWrites)) { if (err && err.code !== 'ENOENT') awfEmit(err); return; } - var now = new Date(); + const now = new Date(); if (prevStat && curStat.size != prevStat.size) { this._pendingWrites[path].lastChange = now; @@ -353,20 +352,20 @@ FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit // * stats - object, result of fs_stat // // Returns boolean -var dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/; +const dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/; FSWatcher.prototype._isIgnored = function(path, stats) { if (this.options.atomic && dotRe.test(path)) return true; if (!this._userIgnored) { - var cwd = this.options.cwd; - var ignored = this.options.ignored; + const cwd = this.options.cwd; + let ignored = this.options.ignored; if (cwd && ignored) { ignored = ignored.map(function (path) { if (typeof path !== 'string') return path; return upath.normalize(isAbsolute(path) ? path : sysPath.join(cwd, path)); }); } - var paths = arrify(ignored) + const paths = arrify(ignored) .filter(function(path) { return typeof path === 'string' && !isGlob(path); }).map(function(path) { @@ -387,17 +386,17 @@ FSWatcher.prototype._isIgnored = function(path, stats) { // * depth - int, at any depth > 0, this isn't a glob // // Returns object containing helpers for this path -var replacerRe = /^\.[\/\\]/; +const replacerRe = /^\.[\/\\]/; FSWatcher.prototype._getWatchHelpers = function(path, depth) { path = path.replace(replacerRe, ''); - var watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path); - var fullWatchPath = sysPath.resolve(watchPath); - var hasGlob = watchPath !== path; - var globFilter = hasGlob ? anymatch(path) : false; - var follow = this.options.followSymlinks; - var globSymlink = hasGlob && follow ? null : false; - - var checkGlobSymlink = function(entry) { + const watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path); + const fullWatchPath = sysPath.resolve(watchPath); + const hasGlob = watchPath !== path; + const globFilter = hasGlob ? anymatch(path) : false; + const follow = this.options.followSymlinks; + let globSymlink = hasGlob && follow ? null : false; + + const checkGlobSymlink = function(entry) { // only need to resolve once // first entry should always have entry.parentDir === '' if (globSymlink == null) { @@ -414,43 +413,43 @@ FSWatcher.prototype._getWatchHelpers = function(path, depth) { return entry.fullPath; }; - var entryPath = function(entry) { + const entryPath = function(entry) { return sysPath.join(watchPath, sysPath.relative(watchPath, checkGlobSymlink(entry)) ); }; - var filterPath = function(entry) { + const filterPath = function(entry) { if (entry.stat && entry.stat.isSymbolicLink()) return filterDir(entry); - var resolvedPath = entryPath(entry); + const resolvedPath = entryPath(entry); return (!hasGlob || globFilter(resolvedPath)) && this._isntIgnored(resolvedPath, entry.stat) && (this.options.ignorePermissionErrors || this._hasReadPermissions(entry.stat)); }.bind(this); - var getDirParts = function(path) { + const getDirParts = function(path) { if (!hasGlob) return false; - var parts = []; - var expandedPath = braces.expand(path); + const parts = []; + const expandedPath = braces.expand(path); expandedPath.forEach(function(path) { parts.push(sysPath.relative(watchPath, path).split(/[\/\\]/)); }); return parts; }; - var dirParts = getDirParts(path); + const dirParts = getDirParts(path); if (dirParts) { dirParts.forEach(function(parts) { if (parts.length > 1) parts.pop(); }); } - var unmatchedGlob; + let unmatchedGlob; - var filterDir = function(entry) { + const filterDir = function(entry) { if (hasGlob) { - var entryParts = getDirParts(checkGlobSymlink(entry)); - var globstar = false; + const entryParts = getDirParts(checkGlobSymlink(entry)); + let globstar = false; unmatchedGlob = !dirParts.some(function(parts) { return parts.every(function(part, i) { if (part === '**') globstar = true; @@ -483,8 +482,8 @@ FSWatcher.prototype._getWatchHelpers = function(path, depth) { // // Returns the directory's tracking object FSWatcher.prototype._getWatchedDir = function(directory) { - var dir = sysPath.resolve(directory); - var watcherRemove = this._remove.bind(this); + const dir = sysPath.resolve(directory); + const watcherRemove = this._remove.bind(this); if (!(dir in this._watched)) this._watched[dir] = { _items: Object.create(null), add: function(item) { @@ -529,23 +528,23 @@ FSWatcher.prototype._remove = function(directory, item) { // if what is being deleted is a directory, get that directory's paths // for recursive deleting and cleaning of watched object // if it is not a directory, nestedDirectoryChildren will be empty array - var path = sysPath.join(directory, item); - var fullPath = sysPath.resolve(path); - var isDirectory = this._watched[path] || this._watched[fullPath]; + const path = sysPath.join(directory, item); + const fullPath = sysPath.resolve(path); + const isDirectory = this._watched[path] || this._watched[fullPath]; // prevent duplicate handling in case of arriving here nearly simultaneously // via multiple paths (such as _handleFile and _handleDir) if (!this._throttle('remove', path, 100)) return; // if the only watched file is removed, watch for its return - var watchedDirs = Object.keys(this._watched); + const watchedDirs = Object.keys(this._watched); if (!isDirectory && !this.options.useFsEvents && watchedDirs.length === 1) { this.add(directory, item, true); } // This will create a new entry in the watched object in either case // so we got to do the directory check beforehand - var nestedDirectoryChildren = this._getWatchedDir(path).children(); + const nestedDirectoryChildren = this._getWatchedDir(path).children(); // Recursively remove children directories / files. nestedDirectoryChildren.forEach(function(nestedItem) { @@ -553,15 +552,15 @@ FSWatcher.prototype._remove = function(directory, item) { }, this); // Check if item was on the watched list and remove it - var parent = this._getWatchedDir(directory); - var wasTracked = parent.has(item); + const parent = this._getWatchedDir(directory); + const wasTracked = parent.has(item); parent.remove(item); // If we wait for this file to be fully written, cancel the wait. - var relPath = path; + let relPath = path; if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path); if (this.options.awaitWriteFinish && this._pendingWrites[relPath]) { - var event = this._pendingWrites[relPath].cancelWait(); + const event = this._pendingWrites[relPath].cancelWait(); if (event === 'add') return; } @@ -569,7 +568,7 @@ FSWatcher.prototype._remove = function(directory, item) { // or a bogus entry to a file, in either case we have to remove it delete this._watched[path]; delete this._watched[fullPath]; - var eventName = isDirectory ? 'unlinkDir' : 'unlink'; + const eventName = isDirectory ? 'unlinkDir' : 'unlink'; if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path); // Avoid conflicts if we later create another file with the same name @@ -583,7 +582,7 @@ FSWatcher.prototype._closePath = function(path) { this._closers[path](); delete this._closers[path]; this._getWatchedDir(sysPath.dirname(path)).remove(sysPath.basename(path)); -} +}; // Public method: Adds paths to be watched on an existing FSWatcher instance @@ -593,8 +592,8 @@ FSWatcher.prototype._closePath = function(path) { // Returns an instance of FSWatcher for chaining. FSWatcher.prototype.add = function(paths, _origAdd, _internal) { - var disableGlobbing = this.options.disableGlobbing; - var cwd = this.options.cwd; + const disableGlobbing = this.options.disableGlobbing; + const cwd = this.options.cwd; this.closed = false; paths = flatten(arrify(paths)); @@ -603,7 +602,7 @@ FSWatcher.prototype.add = function(paths, _origAdd, _internal) { } if (cwd) paths = paths.map(function(path) { - var absPath; + let absPath; if (isAbsolute(path)) { absPath = path; } else if (path[0] === '!') { @@ -712,9 +711,9 @@ FSWatcher.prototype.close = function() { // Returns object w/ dir paths as keys and arrays of contained paths as values. FSWatcher.prototype.getWatched = function() { - var watchList = {}; + const watchList = {}; Object.keys(this._watched).forEach(function(dir) { - var key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir; + const key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir; watchList[key || '.'] = Object.keys(this._watched[dir]._items).sort(); }.bind(this)); return watchList; diff --git a/lib/fsevents-handler.js b/lib/fsevents-handler.js index a748a1ba..22ee4846 100644 --- a/lib/fsevents-handler.js +++ b/lib/fsevents-handler.js @@ -1,22 +1,22 @@ 'use strict'; -var fs = require('fs'); -var sysPath = require('path'); -var readdirp = require('readdirp'); -var fsevents; +const fs = require('fs'); +const sysPath = require('path'); +const readdirp = require('readdirp'); +let fsevents; try { fsevents = require('fsevents'); } catch (error) { - if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error) + if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error); } // fsevents instance helper functions // object to hold per-process fsevents instances // (may be shared across chokidar FSWatcher instances) -var FSEventsWatchers = Object.create(null); +const FSEventsWatchers = Object.create(null); // Threshold of duplicate path prefixes at which to start // consolidating going forward -var consolidateThreshhold = 10; +const consolidateThreshhold = 10; // Private function: Instantiates the fsevents interface @@ -38,9 +38,9 @@ function createFSEventsInstance(path, callback) { // Returns close function function setFSEventsListener(path, realPath, listener, rawEmitter) { - var watchPath = sysPath.extname(path) ? sysPath.dirname(path) : path; - var watchContainer; - var parentPath = sysPath.dirname(watchPath); + let watchPath = sysPath.extname(path) ? sysPath.dirname(path) : path; + let watchContainer; + const parentPath = sysPath.dirname(watchPath); // If we've accumulated a substantial number of paths that // could have been consolidated by watching one directory @@ -50,8 +50,8 @@ function setFSEventsListener(path, realPath, listener, rawEmitter) { watchPath = parentPath; } - var resolvedPath = sysPath.resolve(path); - var hasSymlink = resolvedPath !== realPath; + const resolvedPath = sysPath.resolve(path); + const hasSymlink = resolvedPath !== realPath; function filteredListener(fullPath, flags, info) { if (hasSymlink) fullPath = fullPath.replace(realPath, resolvedPath); if ( @@ -80,7 +80,7 @@ function setFSEventsListener(path, realPath, listener, rawEmitter) { listeners: [filteredListener], rawEmitters: [rawEmitter], watcher: createFSEventsInstance(watchPath, function(fullPath, flags) { - var info = fsevents.getInfo(fullPath, flags); + const info = fsevents.getInfo(fullPath, flags); watchContainer.listeners.forEach(function(listener) { listener(fullPath, flags, info); }); @@ -90,7 +90,7 @@ function setFSEventsListener(path, realPath, listener, rawEmitter) { }) }; } - var listenerIndex = watchContainer.listeners.length - 1; + const listenerIndex = watchContainer.listeners.length - 1; // removes this instance's listeners and closes the underlying fsevents // instance if there are no more listeners left @@ -107,11 +107,11 @@ function setFSEventsListener(path, realPath, listener, rawEmitter) { // Decide whether or not we should start a new higher-level // parent watcher function couldConsolidate(path) { - var keys = Object.keys(FSEventsWatchers); - var count = 0; + const keys = Object.keys(FSEventsWatchers); + let count = 0; - for (var i = 0, len = keys.length; i < len; ++i) { - var watchPath = keys[i]; + for (let i = 0, len = keys.length; i < len; ++i) { + const watchPath = keys[i]; if (watchPath.indexOf(path) === 0) { count++; if (count >= consolidateThreshhold) { @@ -130,7 +130,7 @@ function canUse() { // determines subdirectory traversal levels from root to path function depth(path, root) { - var i = 0; + let i = 0; while (!path.indexOf(root) && (path = sysPath.dirname(path)) !== root) i++; return i; } @@ -150,22 +150,22 @@ function FsEventsHandler() {} FsEventsHandler.prototype._watchWithFsEvents = function(watchPath, realPath, transform, globFilter) { if (this._isIgnored(watchPath)) return; - var watchCallback = function(fullPath, flags, info) { + const watchCallback = function watchCallback(fullPath, flags, info) { if ( this.options.depth !== undefined && depth(fullPath, realPath) > this.options.depth ) return; - var path = transform(sysPath.join( + const path = transform(sysPath.join( watchPath, sysPath.relative(watchPath, fullPath) )); if (globFilter && !globFilter(path)) return; // ensure directories are tracked - var parent = sysPath.dirname(path); - var item = sysPath.basename(path); - var watchedDir = this._getWatchedDir( + const parent = sysPath.dirname(path); + const item = sysPath.basename(path); + const watchedDir = this._getWatchedDir( info.type === 'directory' ? path : parent ); - var checkIgnored = function(stats) { + const checkIgnored = function checkIgnored(stats) { if (this._isIgnored(path, stats)) { this._ignoredPaths[path] = true; if (stats && stats.isDirectory()) { @@ -178,7 +178,7 @@ function(watchPath, realPath, transform, globFilter) { } }.bind(this); - var handleEvent = function(event) { + const handleEvent = function handleEvent(event) { if (checkIgnored()) return; if (event === 'unlink') { @@ -193,7 +193,7 @@ function(watchPath, realPath, transform, globFilter) { if (info.type === 'symlink' && this.options.followSymlinks) { // push symlinks back to the top of the stack to get handled - var curDepth = this.options.depth === undefined ? + const curDepth = this.options.depth === undefined ? undefined : depth(fullPath, realPath) + 1; return this._addToFsEvents(path, false, true, curDepth); } else { @@ -202,7 +202,7 @@ function(watchPath, realPath, transform, globFilter) { this._getWatchedDir(parent).add(item); } } - var eventName = info.type === 'directory' ? event + 'Dir' : event; + const eventName = info.type === 'directory' ? event + 'Dir' : event; this._emit(eventName, path); if (eventName === 'addDir') this._addToFsEvents(path, false, true); } @@ -212,12 +212,12 @@ function(watchPath, realPath, transform, globFilter) { handleEvent(watchedDir.has(item) ? 'change' : 'add'); } function checkFd() { - fs.open(path, 'r', function(error, fd) { + fs.open(path, 'r', function opened(error, fd) { if (error) { error.code !== 'EACCES' ? handleEvent('unlink') : addOrChange(); } else { - fs.close(fd, function(err) { + fs.close(fd, function closed(err) { err && err.code !== 'EACCES' ? handleEvent('unlink') : addOrChange(); }); @@ -225,7 +225,7 @@ function(watchPath, realPath, transform, globFilter) { }); } // correct for wrong events emitted - var wrongEventFlags = [ + const wrongEventFlags = [ 69888, 70400, 71424, 72704, 73472, 131328, 131840, 262912 ]; if (wrongEventFlags.indexOf(flags) !== -1 || info.event === 'unknown') { @@ -249,7 +249,7 @@ function(watchPath, realPath, transform, globFilter) { } }.bind(this); - var closer = setFSEventsListener( + const closer = setFSEventsListener( watchPath, realPath, watchCallback, @@ -269,7 +269,7 @@ function(watchPath, realPath, transform, globFilter) { // Returns nothing FsEventsHandler.prototype._handleFsEventsSymlink = -function(linkPath, fullPath, transform, curDepth) { +function _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { // don't follow the same symlink more than once if (this._symlinkPaths[fullPath]) return; else this._symlinkPaths[fullPath] = true; @@ -286,8 +286,8 @@ function(linkPath, fullPath, transform, curDepth) { // add the linkTarget for watching with a wrapper for transform // that causes emitted paths to incorporate the link's path this._addToFsEvents(linkTarget || linkPath, function(path) { - var dotSlash = '.' + sysPath.sep; - var aliasedPath = linkPath; + const dotSlash = '.' + sysPath.sep; + let aliasedPath = linkPath; if (linkTarget && linkTarget !== dotSlash) { aliasedPath = path.replace(linkTarget, linkPath); } else if (path !== dotSlash) { @@ -310,14 +310,14 @@ FsEventsHandler.prototype._addToFsEvents = function(path, transform, forceAdd, priorDepth) { // applies transform if provided, otherwise returns same value - var processPath = typeof transform === 'function' ? + const processPath = typeof transform === 'function' ? transform : function(val) { return val; }; - var emitAdd = function(newPath, stats) { - var pp = processPath(newPath); - var isDir = stats.isDirectory(); - var dirObj = this._getWatchedDir(sysPath.dirname(pp)); - var base = sysPath.basename(pp); + const emitAdd = function(newPath, stats) { + const pp = processPath(newPath); + const isDir = stats.isDirectory(); + const dirObj = this._getWatchedDir(sysPath.dirname(pp)); + const base = sysPath.basename(pp); // ensure empty dirs get tracked if (isDir) this._getWatchedDir(pp); @@ -330,7 +330,7 @@ function(path, transform, forceAdd, priorDepth) { } }.bind(this); - var wh = this._getWatchHelpers(path); + const wh = this._getWatchHelpers(path); // evaluate what is at the path we're being asked to watch fs[wh.statMethod](wh.watchPath, function(error, stats) { @@ -358,13 +358,13 @@ function(path, transform, forceAdd, priorDepth) { // need to check filterPath on dirs b/c filterDir is less restrictive if (entry.stat.isDirectory() && !wh.filterPath(entry)) return; - var joinedPath = sysPath.join(wh.watchPath, entry.path); - var fullPath = entry.fullPath; + const joinedPath = sysPath.join(wh.watchPath, entry.path); + const fullPath = entry.fullPath; if (wh.followSymlinks && entry.stat.isSymbolicLink()) { // preserve the current depth here since it can't be derived from // real paths past the symlink - var curDepth = this.options.depth === undefined ? + const curDepth = this.options.depth === undefined ? undefined : depth(joinedPath, sysPath.resolve(wh.watchPath)) + 1; this._handleFsEventsSymlink(joinedPath, fullPath, processPath, curDepth); @@ -381,9 +381,9 @@ function(path, transform, forceAdd, priorDepth) { }.bind(this)); if (this.options.persistent && forceAdd !== true) { - var initWatch = function(error, realPath) { + const initWatch = function(error, realPath) { if (this.closed) return; - var closer = this._watchWithFsEvents( + const closer = this._watchWithFsEvents( wh.watchPath, sysPath.resolve(realPath || wh.watchPath), processPath, diff --git a/lib/nodefs-handler.js b/lib/nodefs-handler.js index 23d8ea60..8edcea27 100644 --- a/lib/nodefs-handler.js +++ b/lib/nodefs-handler.js @@ -1,20 +1,20 @@ 'use strict'; -var fs = require('fs'); -var sysPath = require('path'); -var readdirp = require('readdirp'); -var isBinaryPath = require('is-binary-path'); +const fs = require('fs'); +const sysPath = require('path'); +const readdirp = require('readdirp'); +const isBinaryPath = require('is-binary-path'); // fs_watch helpers // object to hold per-process fs_watch instances // (may be shared across chokidar FSWatcher instances) -var FsWatchInstances = Object.create(null); +const FsWatchInstances = Object.create(null); // atime and mtime are changing simultaneously by OS // so we need threshold (in ms) to understand when file is changed // or when only atime is updated (which we want to ignore) -var accessTimeDelayThreshold = 1000; +const accessTimeDelayThreshold = 1000; // Private function: Instantiates the fs_watch interface @@ -26,7 +26,7 @@ var accessTimeDelayThreshold = 1000; // Returns new fsevents instance function createFsWatchInstance(path, options, listener, errHandler, emitRaw) { - var handleEvent = function(rawEvent, evPath) { + const handleEvent = function(rawEvent, evPath) { listener(path); emitRaw(rawEvent, evPath, {watchedPath: path}); @@ -70,11 +70,11 @@ function fsWatchBroadcast(fullPath, type, val1, val2, val3) { // Returns close function function setFsWatchListener(path, fullPath, options, handlers) { - var listener = handlers.listener; - var errHandler = handlers.errHandler; - var rawEmitter = handlers.rawEmitter; - var container = FsWatchInstances[fullPath]; - var watcher; + const listener = handlers.listener; + const errHandler = handlers.errHandler; + const rawEmitter = handlers.rawEmitter; + let container = FsWatchInstances[fullPath]; + let watcher; if (!options.persistent) { watcher = createFsWatchInstance( path, options, listener, errHandler, rawEmitter @@ -90,7 +90,7 @@ function setFsWatchListener(path, fullPath, options, handlers) { fsWatchBroadcast.bind(null, fullPath, 'rawEmitters') ); if (!watcher) return; - var broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers'); + const broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers'); watcher.on('error', function(error) { container.watcherUnusable = true; // documented since Node 10.4.1 // Workaround for https://github.com/joyent/node/issues/4337 @@ -115,7 +115,7 @@ function setFsWatchListener(path, fullPath, options, handlers) { container.errHandlers.push(errHandler); container.rawEmitters.push(rawEmitter); } - var listenerIndex = container.listeners.length - 1; + const listenerIndex = container.listeners.length - 1; // removes this instance's listeners and closes the underlying fs_watch // instance if there are no more listeners left @@ -136,7 +136,7 @@ function setFsWatchListener(path, fullPath, options, handlers) { // object to hold per-process fs_watchFile instances // (may be shared across chokidar FSWatcher instances) -var FsWatchFileInstances = Object.create(null); +const FsWatchFileInstances = Object.create(null); // Private function: Instantiates the fs_watchFile interface or binds listeners // to an existing one covering the same file system entry @@ -148,11 +148,11 @@ var FsWatchFileInstances = Object.create(null); // Returns close function function setFsWatchFileListener(path, fullPath, options, handlers) { - var listener = handlers.listener; - var rawEmitter = handlers.rawEmitter; - var container = FsWatchFileInstances[fullPath]; - var listeners = []; - var rawEmitters = []; + const listener = handlers.listener; + const rawEmitter = handlers.rawEmitter; + let container = FsWatchFileInstances[fullPath]; + let listeners = []; + let rawEmitters = []; if ( container && ( container.options.persistent < options.persistent || @@ -179,7 +179,7 @@ function setFsWatchFileListener(path, fullPath, options, handlers) { container.rawEmitters.forEach(function(rawEmitter) { rawEmitter('change', fullPath, {curr: curr, prev: prev}); }); - var currmtime = curr.mtime.getTime(); + const currmtime = curr.mtime.getTime(); if (curr.size !== prev.size || currmtime > prev.mtime.getTime() || currmtime === 0) { container.listeners.forEach(function(listener) { listener(path, curr); @@ -191,7 +191,7 @@ function setFsWatchFileListener(path, fullPath, options, handlers) { container.listeners.push(listener); container.rawEmitters.push(rawEmitter); } - var listenerIndex = container.listeners.length - 1; + const listenerIndex = container.listeners.length - 1; // removes this instance's listeners and closes the underlying fs_watchFile // instance if there are no more listeners left @@ -217,15 +217,15 @@ function NodeFsHandler() {} // Returns close function for the watcher instance NodeFsHandler.prototype._watchWithNodeFs = function(path, listener) { - var directory = sysPath.dirname(path); - var basename = sysPath.basename(path); - var parent = this._getWatchedDir(directory); + const directory = sysPath.dirname(path); + const basename = sysPath.basename(path); + const parent = this._getWatchedDir(directory); parent.add(basename); - var absolutePath = sysPath.resolve(path); - var options = {persistent: this.options.persistent}; + const absolutePath = sysPath.resolve(path); + const options = {persistent: this.options.persistent}; if (!listener) listener = Function.prototype; // empty function - var closer; + let closer; if (this.options.usePolling) { options.interval = this.enableBinaryInterval && isBinaryPath(basename) ? this.options.binaryInterval : this.options.interval; @@ -253,15 +253,15 @@ function(path, listener) { // Returns close function for the watcher instance NodeFsHandler.prototype._handleFile = function(file, stats, initialAdd, callback) { - var dirname = sysPath.dirname(file); - var basename = sysPath.basename(file); - var parent = this._getWatchedDir(dirname); + const dirname = sysPath.dirname(file); + const basename = sysPath.basename(file); + const parent = this._getWatchedDir(dirname); // if the file is already being watched, do nothing if (parent.has(basename)) return callback(); // kick off the watcher - var closer = this._watchWithNodeFs(file, function(path, newStats) { + let closer = this._watchWithNodeFs(file, function(path, newStats) { if (!this._throttle('watch', file, 5)) return; if (!newStats || newStats && newStats.mtime.getTime() === 0) { fs.stat(file, function(error, newStats) { @@ -270,8 +270,8 @@ function(file, stats, initialAdd, callback) { this._remove(dirname, basename); } else { // Check that change event was not fired because of changed accessTime. - var at = newStats.atime.getTime(); - var mt = newStats.mtime.getTime(); + const at = newStats.atime.getTime(); + const mt = newStats.mtime.getTime(); if (!at || at <= mt || (at - mt) <= accessTimeDelayThreshold) { this._emit('change', file, newStats); } @@ -280,8 +280,8 @@ function(file, stats, initialAdd, callback) { // add is about to be emitted if file not already tracked in parent } else if (parent.has(basename)) { // Check that change event was not fired because of changed accessTime. - var at = newStats.atime.getTime(); - var mt = newStats.mtime.getTime(); + const at = newStats.atime.getTime(); + const mt = newStats.mtime.getTime(); if (!at || at <= mt || (at - mt) <= accessTimeDelayThreshold) { this._emit('change', file, newStats); } @@ -308,8 +308,8 @@ function(file, stats, initialAdd, callback) { // Returns true if no more processing is needed for this entry. NodeFsHandler.prototype._handleSymlink = function(entry, directory, path, item) { - var full = entry.fullPath; - var dir = this._getWatchedDir(directory); + const full = entry.fullPath; + const dir = this._getWatchedDir(directory); if (!this.options.followSymlinks) { // watch symlink directly (don't follow) and detect changes @@ -349,8 +349,8 @@ function(entry, directory, path, item) { // Returns close function for the watcher instance NodeFsHandler.prototype._handleDir = function(dir, stats, initialAdd, depth, target, wh, callback) { - var parentDir = this._getWatchedDir(sysPath.dirname(dir)); - var tracked = parentDir.has(sysPath.basename(dir)); + const parentDir = this._getWatchedDir(sysPath.dirname(dir)); + const tracked = parentDir.has(sysPath.basename(dir)); if (!(initialAdd && this.options.ignoreInitial) && !target && !tracked) { if (!wh.hasGlob || wh.globFilter(dir)) this._emit('addDir', dir, stats); } @@ -358,18 +358,19 @@ function(dir, stats, initialAdd, depth, target, wh, callback) { // ensure dir is tracked (harmless if redundant) parentDir.add(sysPath.basename(dir)); this._getWatchedDir(dir); + let throttler; - var read = function(directory, initialAdd, done) { + const read = function(directory, initialAdd, done) { // Normalize the directory name on Windows directory = sysPath.join(directory, ''); if (!wh.hasGlob) { - var throttler = this._throttle('readdir', directory, 1000); + throttler = this._throttle('readdir', directory, 1000); if (!throttler) return; } - var previous = this._getWatchedDir(wh.path); - var current = []; + const previous = this._getWatchedDir(wh.path); + const current = []; readdirp({ root: directory, @@ -379,8 +380,8 @@ function(dir, stats, initialAdd, depth, target, wh, callback) { depth: 0, lstat: true }).on('data', function(entry) { - var item = entry.path; - var path = sysPath.join(directory, item); + const item = entry.path; + let path = sysPath.join(directory, item); current.push(item); if (entry.stat.isSymbolicLink() && @@ -398,7 +399,7 @@ function(dir, stats, initialAdd, depth, target, wh, callback) { this._addToNodeFs(path, initialAdd, wh, depth + 1); } }.bind(this)).on('end', function() { - var wasThrottled = throttler ? throttler.clear() : false; + const wasThrottled = throttler ? throttler.clear() : false; if (done) done(); // Files that absent in current directory snapshot @@ -422,7 +423,7 @@ function(dir, stats, initialAdd, depth, target, wh, callback) { }.bind(this)).on('error', this._handleError.bind(this)); }.bind(this); - var closer; + let closer; if (this.options.depth == null || depth <= this.options.depth) { if (!target) read(dir, initialAdd, callback); @@ -451,13 +452,13 @@ function(dir, stats, initialAdd, depth, target, wh, callback) { NodeFsHandler.prototype._addToNodeFs = function(path, initialAdd, priorWh, depth, target, callback) { if (!callback) callback = Function.prototype; - var ready = this._emitReady; + const ready = this._emitReady; if (this._isIgnored(path) || this.closed) { ready(); return callback(null, false); } - var wh = this._getWatchHelpers(path, depth); + const wh = this._getWatchHelpers(path, depth); if (!wh.hasGlob && priorWh) { wh.hasGlob = priorWh.hasGlob; wh.globFilter = priorWh.globFilter; @@ -473,15 +474,15 @@ function(path, initialAdd, priorWh, depth, target, callback) { return callback(null, false); } - var initDir = function(dir, target) { + const initDir = function(dir, target) { return this._handleDir(dir, stats, initialAdd, depth, target, wh, ready); }.bind(this); - var closer; + let closer; if (stats.isDirectory()) { closer = initDir(wh.watchPath, target); } else if (stats.isSymbolicLink()) { - var parent = sysPath.dirname(wh.watchPath); + const parent = sysPath.dirname(wh.watchPath); this._getWatchedDir(parent).add(wh.watchPath); this._emit('add', wh.watchPath, stats); closer = initDir(parent, path); diff --git a/package.json b/package.json index 6218ad14..10581345 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "chai": "^3.2.0", "coveralls": "^3.0.1", "graceful-fs": "4.1.4", + "jshint": "^2.10.1", "mocha": "^5.2.0", "nyc": "^11.8.0", "rimraf": "^2.4.3", From 7aa0b6bec5b1bcfa0f5e8fc5a6325d65df5406ba Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Mon, 11 Feb 2019 22:11:53 -0800 Subject: [PATCH 03/10] Use async-await in tests, part 1. --- test.js | 840 +++++++++++++++++++++++++------------------------------- 1 file changed, 377 insertions(+), 463 deletions(-) diff --git a/test.js b/test.js index 1109d56a..61eac399 100644 --- a/test.js +++ b/test.js @@ -1,19 +1,47 @@ 'use strict'; var chokidar = require('./'); +var promisify = require('util').promisify; var chai = require('chai'); var expect = chai.expect; var should = chai.should(); var sinon = require('sinon'); -var rimraf = require('rimraf'); +var rimraf = promisify(require('rimraf')); var fs = require('graceful-fs'); -var rimraf = require('rimraf'); var sysPath = require('path'); var upath = require("upath"); -var cp = require('child_process'); +var exec = promisify(require('child_process').exec); chai.use(require('sinon-chai')); var os = process.platform; +const fs_promises = require('fs').promises; +const write = fs_promises.writeFile; + +const isTravisMac = process.env.TRAVIS && os === 'darwin'; + +const delay = async (time) => { + const timer = time || slowerDelay || 50; + return new Promise((resolve) => { + // console.log('delay#Promise', timer); + setTimeout(resolve, timer); + }); +}; + +// spyOnReady +const aspy = async (watcher, eventName, spy) => { + if (spy == null) spy = sinon.spy(); + return new Promise((resolve, reject) => { + watcher.on('error', reject); + if (typeof eventName === 'string') { + watcher.on(eventName, spy); + } + // console.log('aspy', eventName); + watcher.on('ready', () => { + resolve(spy); + }); + }); +}; + function getFixturePath (subPath) { return sysPath.join( __dirname, @@ -59,29 +87,25 @@ if (!fs.readFileSync(__filename).toString().match(/\sit\.only\(/)) { } } -before(function(done) { +before(async () => { var writtenCount = 0; - function wrote(err) { - if (err) throw err; + await rimraf(sysPath.join(__dirname, 'test-fixtures')); + await fs.promises.mkdir(fixturesPath, PERM_ARR); + while (subdir < testCount) { + subdir++; + fixturesPath = getFixturePath(''); + await fs.promises.mkdir(fixturesPath, PERM_ARR); + await write(sysPath.join(fixturesPath, 'change.txt'), 'b'); + if (++writtenCount === testCount * 2) { + subdir = 0; + return; + } + await write(sysPath.join(fixturesPath, 'unlink.txt'), 'b'); if (++writtenCount === testCount * 2) { subdir = 0; - done(); + return; } } - rimraf(sysPath.join(__dirname, 'test-fixtures'), function(err) { - if (err) throw err; - fs.mkdir(fixturesPath, PERM_ARR, function(err) { - if (err) throw err; - while (subdir < testCount) { - subdir++; - fixturesPath = getFixturePath(''); - fs.mkdir(fixturesPath, PERM_ARR, function() { - fs.writeFile(sysPath.join(this, 'change.txt'), 'b', wrote); - fs.writeFile(sysPath.join(this, 'unlink.txt'), 'b', wrote); - }.bind(fixturesPath)); - } - }); - }); }); beforeEach(function() { @@ -89,11 +113,14 @@ beforeEach(function() { fixturesPath = getFixturePath(''); }); -function closeWatchers(done) { +const closeWatchers = async () => { var u; while (u = usedWatchers.pop()) u.close(); - if (done) { - process.env.TRAVIS && os === 'darwin' ? setTimeout(done, 500) : done(); + if (isTravisMac) { + await delay(500); + return true; + } else { + return true; } } function disposeWatcher(watcher) { @@ -122,9 +149,6 @@ describe('chokidar', function() { }); function simpleCb(err) { if (err) throw err; } -function w(fn, to) { - return setTimeout.bind(null, fn, to || slowerDelay || 50); -} function runTests(baseopts) { baseopts.persistent = true; @@ -155,26 +179,30 @@ function runTests(baseopts) { }); function stdWatcher() { - return watcher = chokidar.watch(fixturesPath, options); + watcher = chokidar.watch(fixturesPath, options); + return watcher; } - function waitFor(spies, fn) { - function isSpyReady(spy) { - return Array.isArray(spy) ? spy[0].callCount >= spy[1] : spy.callCount; - } - function finish() { - clearInterval(intrvl); - clearTimeout(to); - fn(); - fn = Function.prototype; - } - var intrvl = setInterval(function() { - if (spies.every(isSpyReady)) finish(); - }, 5); - var to = setTimeout(finish, 3500); + const waitFor = async (spies) => { + if (!Array.isArray(spies)) spies = [spies] + return new Promise((resolve, reject) => { + function isSpyReady(spy) { + return Array.isArray(spy) ? spy[0].callCount >= spy[1] : spy.callCount; + } + var intrvl; + function finish() { + clearInterval(intrvl); + clearTimeout(to); + resolve(); + } + intrvl = setInterval(() => { + if (spies.every(isSpyReady)) finish(); + }, 5); + var to = setTimeout(finish, 3500); + }); } - describe('watch a directory', function() { + describe('watch a directory', () => { var readySpy, rawSpy; beforeEach(function() { options.ignoreInitial = true; @@ -183,39 +211,35 @@ function runTests(baseopts) { rawSpy = sinon.spy(function rawSpy(){}); stdWatcher().on('ready', readySpy).on('raw', rawSpy); }); - afterEach(function(done) { - waitFor([readySpy], function() { - readySpy.should.have.been.calledOnce; - rawSpy = undefined; - closeWatchers(done); - }); + afterEach(async () => { + await waitFor(readySpy); + readySpy.should.have.been.calledOnce; + rawSpy = undefined; + await closeWatchers(); }); - it('should produce an instance of chokidar.FSWatcher', function() { - watcher.should.be.an['instanceof'](chokidar.FSWatcher); + it('should produce an instance of chokidar.FSWatcher', () => { + watcher.should.be.an.instanceof(chokidar.FSWatcher); }); - it('should expose public API methods', function() { + it('should expose public API methods', () => { watcher.on.should.be.a('function'); watcher.emit.should.be.a('function'); watcher.add.should.be.a('function'); watcher.close.should.be.a('function'); watcher.getWatched.should.be.a('function'); }); - it('should emit `add` event when file was added', function(done) { - var spy = sinon.spy(); + it('should emit `add` event when file was added', async () => { var testPath = getFixturePath('add.txt'); - watcher.on('add', spy).on('ready', w(function() { - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy], function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith(testPath); - expect(spy.args[0][1]).to.be.ok; // stats - rawSpy.should.have.been.called; - done(); - }); - })); - }); - it('should emit nine `add` events when nine files were added in one directory', function(done) { - var spy = sinon.spy(); + const spy = sinon.spy(); + await aspy(watcher, 'add', spy); + await delay(); + await write(testPath, Date.now()); + await waitFor(spy); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith(testPath); + expect(spy.args[0][1]).to.be.ok; // stats + rawSpy.should.have.been.called; + }); + it('should emit nine `add` events when nine files were added in one directory', async () => { var test1Path = getFixturePath('add1.txt'); var test2Path = getFixturePath('add2.txt'); var test3Path = getFixturePath('add3.txt'); @@ -225,40 +249,29 @@ function runTests(baseopts) { var test7Path = getFixturePath('add7.txt'); var test8Path = getFixturePath('add8.txt'); var test9Path = getFixturePath('add9.txt'); - watcher.on('add', spy).on('ready', w(function() { - fs.writeFile(test1Path, Date.now(), function() { - fs.writeFile(test2Path, Date.now(), function() { - fs.writeFile(test3Path, Date.now(), function() { - fs.writeFile(test4Path, Date.now(), function() { - fs.writeFile(test5Path, Date.now(), w(function() { - fs.writeFile(test6Path, Date.now(), function() { - fs.writeFile(test7Path, Date.now(), function() { - fs.writeFile(test8Path, Date.now(), function() { - fs.writeFile(test9Path, Date.now(), simpleCb); - }); - }); - }); - }, 200)); - }); - }); - }); - }); - waitFor([[spy, 9]], function() { - spy.should.have.been.calledWith(test1Path); - spy.should.have.been.calledWith(test2Path); - spy.should.have.been.calledWith(test3Path); - spy.should.have.been.calledWith(test4Path); - spy.should.have.been.calledWith(test5Path); - spy.should.have.been.calledWith(test6Path); - spy.should.have.been.calledWith(test7Path); - spy.should.have.been.calledWith(test8Path); - spy.should.have.been.calledWith(test9Path); - done(); - }); - })); - }); - it('should emit thirtythree `add` events when thirtythree files were added in nine directories', function(done) { - var spy = sinon.spy(); + var spy = await aspy(watcher, 'add'); + await write(test1Path, Date.now()); + await write(test2Path, Date.now()); + await write(test3Path, Date.now()); + await write(test4Path, Date.now()); + await write(test5Path, Date.now()); + await delay(200); + await write(test6Path, Date.now()); + await write(test7Path, Date.now()); + await write(test8Path, Date.now()); + await write(test9Path, Date.now()); + await waitFor([[spy, 9]]); + spy.should.have.been.calledWith(test1Path); + spy.should.have.been.calledWith(test2Path); + spy.should.have.been.calledWith(test3Path); + spy.should.have.been.calledWith(test4Path); + spy.should.have.been.calledWith(test5Path); + spy.should.have.been.calledWith(test6Path); + spy.should.have.been.calledWith(test7Path); + spy.should.have.been.calledWith(test8Path); + spy.should.have.been.calledWith(test9Path); + }); + it('should emit thirtythree `add` events when thirtythree files were added in nine directories', async () => { var test1Path = getFixturePath('add1.txt'); var testb1Path = getFixturePath('b/add1.txt'); var testc1Path = getFixturePath('c/add1.txt'); @@ -300,378 +313,284 @@ function runTests(baseopts) { fs.mkdirSync(getFixturePath('g'), PERM_ARR); fs.mkdirSync(getFixturePath('h'), PERM_ARR); fs.mkdirSync(getFixturePath('i'), PERM_ARR); - watcher.on('add', spy).on('ready', w(function() { - fs.writeFile(test1Path, Date.now(), function() { - fs.writeFile(test2Path, Date.now(), function() { - fs.writeFile(test3Path, Date.now(), function() { - fs.writeFile(test4Path, Date.now(), function() { - fs.writeFile(test5Path, Date.now(), w(function() { - fs.writeFile(test6Path, Date.now(), function() { - fs.writeFile(test7Path, Date.now(), function() { - fs.writeFile(test8Path, Date.now(), function() { - fs.writeFile(test9Path, Date.now(), function() { - fs.writeFile(testb1Path, Date.now(), function() { - fs.writeFile(testb2Path, Date.now(), function() { - fs.writeFile(testb3Path, Date.now(), function() { - fs.writeFile(testb4Path, Date.now(), function() { - fs.writeFile(testb5Path, Date.now(), w(function() { - fs.writeFile(testb6Path, Date.now(), function() { - fs.writeFile(testb7Path, Date.now(), function() { - fs.writeFile(testb8Path, Date.now(), function() { - fs.writeFile(testb9Path, Date.now(), function() { - fs.writeFile(testc1Path, Date.now(), function() { - fs.writeFile(testc2Path, Date.now(), function() { - fs.writeFile(testc3Path, Date.now(), function() { - fs.writeFile(testc4Path, Date.now(), function() { - fs.writeFile(testc5Path, Date.now(), w(function() { - fs.writeFile(testc6Path, Date.now(), function() { - fs.writeFile(testc7Path, Date.now(), function() { - fs.writeFile(testc8Path, Date.now(), function() { - fs.writeFile(testc9Path, Date.now(), function() { - fs.writeFile(testd1Path, Date.now(), function() { - fs.writeFile(teste1Path, Date.now(), function() { - fs.writeFile(testf1Path, Date.now(), w(function() { - fs.writeFile(testg1Path, Date.now(), function() { - fs.writeFile(testh1Path, Date.now(), function() { - fs.writeFile(testi1Path, Date.now(), function() { - simpleCb(); - }); - }); - }); - }, 100)); - }); - }); - }); - }); - }); - }); - }, 150)); - }); - }); - }); - }); - }); - }); - }); - }); - }, 200)); - }); - }); - }); - }); - }); - }); - }); - }); - }, 200)); - }); - }); - }); - }); - waitFor([[spy, 33]], function() { - spy.should.have.been.calledWith(test1Path); - spy.should.have.been.calledWith(test2Path); - spy.should.have.been.calledWith(test3Path); - spy.should.have.been.calledWith(test4Path); - spy.should.have.been.calledWith(test5Path); - spy.should.have.been.calledWith(test6Path); - spy.should.have.been.calledWith(test7Path); - spy.should.have.been.calledWith(test8Path); - spy.should.have.been.calledWith(test9Path); - spy.should.have.been.calledWith(testb1Path); - spy.should.have.been.calledWith(testb2Path); - spy.should.have.been.calledWith(testb3Path); - spy.should.have.been.calledWith(testb4Path); - spy.should.have.been.calledWith(testb5Path); - spy.should.have.been.calledWith(testb6Path); - spy.should.have.been.calledWith(testb7Path); - spy.should.have.been.calledWith(testb8Path); - spy.should.have.been.calledWith(testb9Path); - spy.should.have.been.calledWith(testc1Path); - spy.should.have.been.calledWith(testc2Path); - spy.should.have.been.calledWith(testc3Path); - spy.should.have.been.calledWith(testc4Path); - spy.should.have.been.calledWith(testc5Path); - spy.should.have.been.calledWith(testc6Path); - spy.should.have.been.calledWith(testc7Path); - spy.should.have.been.calledWith(testc8Path); - spy.should.have.been.calledWith(testc9Path); - spy.should.have.been.calledWith(testd1Path); - spy.should.have.been.calledWith(teste1Path); - spy.should.have.been.calledWith(testf1Path); - spy.should.have.been.calledWith(testg1Path); - spy.should.have.been.calledWith(testh1Path); - spy.should.have.been.calledWith(testi1Path); - done(); - }); - })); - }); - it('should emit `addDir` event when directory was added', function(done) { - var spy = sinon.spy(); + + const spy = await aspy(watcher, 'add'); + + await write(test1Path, Date.now()); + await write(test2Path, Date.now()); + await write(test3Path, Date.now()); + await write(test4Path, Date.now()); + await write(test5Path, Date.now()); + await write(test6Path, Date.now()); + await write(test7Path, Date.now()); + await write(test8Path, Date.now()); + await write(test9Path, Date.now()); + await write(testb1Path, Date.now()); + await write(testb2Path, Date.now()); + await write(testb3Path, Date.now()); + await write(testb4Path, Date.now()); + await write(testb5Path, Date.now()); + await write(testb6Path, Date.now()); + await write(testb7Path, Date.now()); + await write(testb8Path, Date.now()); + await write(testb9Path, Date.now()); + await write(testc1Path, Date.now()); + await write(testc2Path, Date.now()); + await write(testc3Path, Date.now()); + await write(testc4Path, Date.now()); + await write(testc5Path, Date.now()); + await write(testc6Path, Date.now()); + await write(testc7Path, Date.now()); + await write(testc8Path, Date.now()); + await write(testc9Path, Date.now()); + await write(testd1Path, Date.now()); + await write(teste1Path, Date.now()); + await write(testf1Path, Date.now()); + await write(testg1Path, Date.now()); + await write(testh1Path, Date.now()); + await write(testi1Path, Date.now()); + await waitFor([[spy, 33]]); + + spy.should.have.been.calledWith(test1Path); + spy.should.have.been.calledWith(test2Path); + spy.should.have.been.calledWith(test3Path); + spy.should.have.been.calledWith(test4Path); + spy.should.have.been.calledWith(test5Path); + spy.should.have.been.calledWith(test6Path); + spy.should.have.been.calledWith(test7Path); + spy.should.have.been.calledWith(test8Path); + spy.should.have.been.calledWith(test9Path); + spy.should.have.been.calledWith(testb1Path); + spy.should.have.been.calledWith(testb2Path); + spy.should.have.been.calledWith(testb3Path); + spy.should.have.been.calledWith(testb4Path); + spy.should.have.been.calledWith(testb5Path); + spy.should.have.been.calledWith(testb6Path); + spy.should.have.been.calledWith(testb7Path); + spy.should.have.been.calledWith(testb8Path); + spy.should.have.been.calledWith(testb9Path); + spy.should.have.been.calledWith(testc1Path); + spy.should.have.been.calledWith(testc2Path); + spy.should.have.been.calledWith(testc3Path); + spy.should.have.been.calledWith(testc4Path); + spy.should.have.been.calledWith(testc5Path); + spy.should.have.been.calledWith(testc6Path); + spy.should.have.been.calledWith(testc7Path); + spy.should.have.been.calledWith(testc8Path); + spy.should.have.been.calledWith(testc9Path); + spy.should.have.been.calledWith(testd1Path); + spy.should.have.been.calledWith(teste1Path); + spy.should.have.been.calledWith(testf1Path); + spy.should.have.been.calledWith(testg1Path); + spy.should.have.been.calledWith(testh1Path); + spy.should.have.been.calledWith(testi1Path); + }); + it('should emit `addDir` event when directory was added', async () => { var testDir = getFixturePath('subdir'); - watcher.on('addDir', spy).on('ready', w(function() { - spy.should.not.have.been.called; - fs.mkdir(testDir, PERM_ARR, simpleCb); - waitFor([spy], function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith(testDir); - expect(spy.args[0][1]).to.be.ok; // stats - rawSpy.should.have.been.called; - done(); - }); - })); - }); - it('should emit `change` event when file was changed', function(done) { - var spy = sinon.spy(); + const spy = await aspy(watcher, 'addDir'); + spy.should.not.have.been.called; + await fs.promises.mkdir(testDir, PERM_ARR); + await waitFor([spy]); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith(testDir); + expect(spy.args[0][1]).to.be.ok; // stats + rawSpy.should.have.been.called; + }); + it('should emit `change` event when file was changed', async () => { var testPath = getFixturePath('change.txt'); - watcher.on('change', spy).on('ready', function() { - spy.should.not.have.been.called; - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy], function() { - spy.should.have.been.calledWith(testPath); - expect(spy.args[0][1]).to.be.ok; // stats - rawSpy.should.have.been.called; - if (!osXFsWatch010) spy.should.have.been.calledOnce; - done(); - }); - }); - }); - it('should emit `unlink` event when file was removed', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('unlink.txt'); - watcher.on('unlink', spy).on('ready', function() { - spy.should.not.have.been.called; - fs.unlink(testPath, simpleCb); - waitFor([spy], function() { - spy.should.have.been.calledWith(testPath); - expect(spy.args[0][1]).to.not.be.ok; // no stats - rawSpy.should.have.been.called; - if (!osXFsWatch010) spy.should.have.been.calledOnce; - done(); - }); - }); - }); - it('should emit `unlinkDir` event when a directory was removed', function(done) { - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); + const spy = await aspy(watcher, 'change'); + spy.should.not.have.been.called; + fs.writeFile(testPath, Date.now(), simpleCb); + await waitFor([spy]); + spy.should.have.been.calledWith(testPath); + expect(spy.args[0][1]).to.be.ok; // stats + rawSpy.should.have.been.called; + if (!osXFsWatch010) spy.should.have.been.calledOnce; + }); + it('should emit `unlink` event when file was removed', async () => { + const testPath = getFixturePath('unlink.txt'); + const spy = await aspy(watcher, 'unlink'); + spy.should.not.have.been.called; + fs.unlink(testPath, simpleCb); + await waitFor([spy]); + spy.should.have.been.calledWith(testPath); + expect(spy.args[0][1]).to.not.be.ok; // no stats + rawSpy.should.have.been.called; + if (!osXFsWatch010) spy.should.have.been.calledOnce; + }); + it('should emit `unlinkDir` event when a directory was removed', async () => { + const testDir = getFixturePath('subdir'); fs.mkdirSync(testDir, PERM_ARR); - watcher.on('unlinkDir', spy).on('ready', function() { - w(fs.rmdir.bind(fs, testDir, simpleCb))(); - waitFor([spy], function() { - spy.should.have.been.calledWith(testDir); - expect(spy.args[0][1]).to.not.be.ok; // no stats - rawSpy.should.have.been.called; - if (!osXFsWatch010) spy.should.have.been.calledOnce; - done(); - }); - }); - }); - it('should emit two `unlinkDir` event when two nested directories were removed', function(done) { - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); - var testDir2 = getFixturePath('subdir/subdir2'); - var testDir3 = getFixturePath('subdir/subdir2/subdir3'); + const spy = await aspy(watcher, 'unlinkDir'); + await delay(); + await fs.promises.rmdir(testDir); + await waitFor([spy]); + spy.should.have.been.calledWith(testDir); + expect(spy.args[0][1]).to.not.be.ok; // no stats + rawSpy.should.have.been.called; + if (!osXFsWatch010) spy.should.have.been.calledOnce; + }); + it('should emit two `unlinkDir` event when two nested directories were removed', async () => { + const testDir = getFixturePath('subdir'); + const testDir2 = getFixturePath('subdir/subdir2'); + const testDir3 = getFixturePath('subdir/subdir2/subdir3'); fs.mkdirSync(testDir, PERM_ARR); fs.mkdirSync(testDir2, PERM_ARR); fs.mkdirSync(testDir3, PERM_ARR); - watcher.on('unlinkDir', spy).on('ready', function() { - rimraf(testDir2, simpleCb); // test removing in one - waitFor([spy], function() { - spy.should.have.been.calledWith(testDir2); - spy.should.have.been.calledWith(testDir3); - expect(spy.args[0][1]).to.not.be.ok; // no stats - rawSpy.should.have.been.called; - if (!osXFsWatch010) spy.should.have.been.calledTwice; - done(); - }); - }); - }); - it('should emit `unlink` and `add` events when a file is renamed', function(done) { - var unlinkSpy = sinon.spy(function unlink(){}); - var addSpy = sinon.spy(function add(){}); - var testPath = getFixturePath('change.txt'); - var newPath = getFixturePath('moved.txt'); - watcher - .on('unlink', unlinkSpy) - .on('add', addSpy) - .on('ready', function() { - unlinkSpy.should.not.have.been.called; - addSpy.should.not.have.been.called; - w(fs.rename.bind(fs, testPath, newPath, simpleCb))(); - waitFor([unlinkSpy, addSpy], function() { - unlinkSpy.should.have.been.calledWith(testPath); - expect(unlinkSpy.args[0][1]).to.not.be.ok; // no stats - addSpy.should.have.been.calledOnce; - addSpy.should.have.been.calledWith(newPath); - expect(addSpy.args[0][1]).to.be.ok; // stats - rawSpy.should.have.been.called; - if (!osXFsWatch) unlinkSpy.should.have.been.calledOnce; - done(); - }); - }); - }); - it('should emit `add`, not `change`, when previously deleted file is re-added', function(done) { - if (win32Polling010) return done(); - var unlinkSpy = sinon.spy(function unlink(){}); - var addSpy = sinon.spy(function add(){}); - var changeSpy = sinon.spy(function change(){}); - var testPath = getFixturePath('add.txt'); + const spy = await aspy(watcher, 'unlinkDir'); + rimraf(testDir2, simpleCb); // test removing in one + await waitFor([spy]); + spy.should.have.been.calledWith(testDir2); + spy.should.have.been.calledWith(testDir3); + expect(spy.args[0][1]).to.not.be.ok; // no stats + rawSpy.should.have.been.called; + if (!osXFsWatch010) spy.should.have.been.calledTwice; + }); + it('should emit `unlink` and `add` events when a file is renamed', async () => { + const unlinkSpy = sinon.spy(function unlink(){}); + const addSpy = sinon.spy(function add(){}); + const testPath = getFixturePath('change.txt'); + const newPath = getFixturePath('moved.txt'); + watcher.on('unlink', unlinkSpy).on('add', addSpy); + await aspy(watcher); + unlinkSpy.should.not.have.been.called; + addSpy.should.not.have.been.called; + await delay(); + await fs.promises.rename(testPath, newPath); + await waitFor([unlinkSpy, addSpy]); + unlinkSpy.should.have.been.calledWith(testPath); + expect(unlinkSpy.args[0][1]).to.not.be.ok; // no stats + addSpy.should.have.been.calledOnce; + addSpy.should.have.been.calledWith(newPath); + expect(addSpy.args[0][1]).to.be.ok; // stats + rawSpy.should.have.been.called; + if (!osXFsWatch) unlinkSpy.should.have.been.calledOnce; + }); + it('should emit `add`, not `change`, when previously deleted file is re-added', async () => { + if (win32Polling010) return true; + const unlinkSpy = sinon.spy(function unlink(){}); + const addSpy = sinon.spy(function add(){}); + const changeSpy = sinon.spy(function change(){}); + const testPath = getFixturePath('add.txt'); fs.writeFileSync(testPath, 'hello'); watcher .on('unlink', unlinkSpy) .on('add', addSpy) - .on('change', changeSpy) - .on('ready', function() { - unlinkSpy.should.not.have.been.called; - addSpy.should.not.have.been.called; - changeSpy.should.not.have.been.called; - fs.unlink(testPath, simpleCb); - waitFor([unlinkSpy.withArgs(testPath)], function() { - unlinkSpy.should.have.been.calledWith(testPath); - w(fs.writeFile.bind(fs, testPath, Date.now(), simpleCb))(); - waitFor([addSpy.withArgs(testPath)], function() { - addSpy.should.have.been.calledWith(testPath); - changeSpy.should.not.have.been.called; - done(); - }); - }); - }); - }); - it('should not emit `unlink` for previously moved files', function(done) { - var unlinkSpy = sinon.spy(function unlink(){}); - var testPath = getFixturePath('change.txt'); - var newPath1 = getFixturePath('moved.txt'); - var newPath2 = getFixturePath('moved-again.txt'); - watcher - .on('unlink', unlinkSpy) - .on('ready', function() { - fs.rename(testPath, newPath1, w(function() { - fs.rename(newPath1, newPath2, simpleCb); - }, 300)); - waitFor([unlinkSpy.withArgs(newPath1)], function() { - unlinkSpy.withArgs(testPath).should.have.been.calledOnce; - unlinkSpy.withArgs(newPath1).should.have.been.calledOnce; - unlinkSpy.withArgs(newPath2).should.not.have.been.called; - done(); - }); - }); - }); - it('should survive ENOENT for missing subdirectories', function(done) { - var testDir; - testDir = getFixturePath('notadir'); - watcher.on('ready', function() { - watcher.add(testDir); - done(); - }); - }); - it('should notice when a file appears in a new directory', function(done) { - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); - var testPath = getFixturePath('subdir/add.txt'); - watcher.on('add', spy).on('ready', function() { - spy.should.not.have.been.called; - fs.mkdir(testDir, PERM_ARR, function() { - fs.writeFile(testPath, Date.now(), simpleCb); - }); - waitFor([spy], function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith(testPath); - expect(spy.args[0][1]).to.be.ok; // stats - rawSpy.should.have.been.called; - done(); - }); - }); - }); - it('should watch removed and re-added directories', function(done) { - if (win32Polling010) return done(); - var unlinkSpy = sinon.spy(function unlinkSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); - var parentPath = getFixturePath('subdir2'); - var subPath = getFixturePath('subdir2/subsub'); - watcher - .on('unlinkDir', unlinkSpy) - .on('addDir', addSpy) - .on('ready', function() { - fs.mkdir(parentPath, PERM_ARR, w(function() { - fs.rmdir(parentPath, simpleCb); - }, win32Polling ? 900 : 300)); - waitFor([unlinkSpy.withArgs(parentPath)], function() { - unlinkSpy.should.have.been.calledWith(parentPath); - fs.mkdir(parentPath, PERM_ARR, w(function() { - fs.mkdir(subPath, PERM_ARR, simpleCb); - }, win32Polling ? 2200 : 1200)); - waitFor([[addSpy, 3]], function() { - addSpy.should.have.been.calledWith(parentPath); - addSpy.should.have.been.calledWith(subPath); - done(); - }); - }); - }); + .on('change', changeSpy); + await aspy(watcher); + unlinkSpy.should.not.have.been.called; + addSpy.should.not.have.been.called; + changeSpy.should.not.have.been.called; + await fs.promises.unlink(testPath, simpleCb); + await waitFor([unlinkSpy.withArgs(testPath)]); + unlinkSpy.should.have.been.calledWith(testPath); + await delay(); + await write(testPath, Date.now()); + await waitFor([addSpy.withArgs(testPath)]); + addSpy.should.have.been.calledWith(testPath); + changeSpy.should.not.have.been.called; + }); + it('should not emit `unlink` for previously moved files', async () => { + const unlinkSpy = sinon.spy(function unlink(){}); + const testPath = getFixturePath('change.txt'); + const newPath1 = getFixturePath('moved.txt'); + const newPath2 = getFixturePath('moved-again.txt'); + await aspy(watcher, 'unlink', unlinkSpy); + await fs.promises.rename(testPath, newPath1); + await delay(300); + await fs.promises.rename(newPath1, newPath2); + await waitFor([unlinkSpy.withArgs(newPath1)]) + unlinkSpy.withArgs(testPath).should.have.been.calledOnce; + unlinkSpy.withArgs(newPath1).should.have.been.calledOnce; + unlinkSpy.withArgs(newPath2).should.not.have.been.called; + }); + it('should survive ENOENT for missing subdirectories', async () => { + const testDir = getFixturePath('notadir'); + await aspy(watcher); + watcher.add(testDir); + }); + it('should notice when a file appears in a new directory', async () => { + const testDir = getFixturePath('subdir'); + const testPath = getFixturePath('subdir/add.txt'); + const spy = await aspy(watcher, 'add'); + spy.should.not.have.been.called; + await fs.promises.mkdir(testDir, PERM_ARR); + await write(testPath, Date.now()); + await waitFor([spy]); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith(testPath); + expect(spy.args[0][1]).to.be.ok; // stats + rawSpy.should.have.been.called; + }); + it('should watch removed and re-added directories', async () => { + if (win32Polling010) return; + const unlinkSpy = sinon.spy(function unlinkSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); + const parentPath = getFixturePath('subdir2'); + const subPath = getFixturePath('subdir2/subsub'); + watcher.on('unlinkDir', unlinkSpy).on('addDir', addSpy) + await aspy(watcher); + await fs.promises.mkdir(parentPath, PERM_ARR) + await delay(win32Polling ? 900 : 300); + await fs.promises.rmdir(parentPath); + await waitFor([unlinkSpy.withArgs(parentPath)]); + unlinkSpy.should.have.been.calledWith(parentPath); + await fs.promises.mkdir(parentPath, PERM_ARR); + await delay(win32Polling ? 2200 : 1200); + await fs.promises.mkdir(subPath, PERM_ARR); + await waitFor([[addSpy, 3]]) + addSpy.should.have.been.calledWith(parentPath); + addSpy.should.have.been.calledWith(subPath); }); }); - describe('watch individual files', function() { + describe('watch individual files', () => { before(closeWatchers); - it('should detect changes', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('change.txt'); - watcher = chokidar.watch(testPath, options) - .on('change', spy) - .on('ready', function() { - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy], function() { - spy.should.have.always.been.calledWith(testPath); - done(); - }); - }); - }); - it('should detect unlinks', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('unlink.txt'); - watcher = chokidar.watch(testPath, options) - .on('unlink', spy) - .on('ready', function() { - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([spy], function() { - spy.should.have.been.calledWith(testPath); - done(); - }); - }); - }); - it('should detect unlink and re-add', function(done) { + it('should detect changes', async () => { + const testPath = getFixturePath('change.txt'); + watcher = chokidar.watch(testPath, options); + const spy = await aspy(watcher, 'change'); + await write(testPath, Date.now()); + await waitFor([spy]); + spy.should.have.always.been.calledWith(testPath); + }); + it('should detect unlinks', async () => { + const testPath = getFixturePath('unlink.txt'); + watcher = chokidar.watch(testPath, options); + const spy = await aspy(watcher, 'unlink'); + await delay(); + await fs.promises.unlink(testPath); + await waitFor([spy]); + spy.should.have.been.calledWith(testPath); + }); + it('should detect unlink and re-add', async () => { options.ignoreInitial = true; - var unlinkSpy = sinon.spy(function unlinkSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); - var testPath = getFixturePath('unlink.txt'); + const unlinkSpy = sinon.spy(function unlinkSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); + const testPath = getFixturePath('unlink.txt'); watcher = chokidar.watch([testPath], options) .on('unlink', unlinkSpy) .on('add', addSpy) - .on('ready', function() { - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([unlinkSpy], w(function() { - unlinkSpy.should.have.been.calledWith(testPath); - w(fs.writeFile.bind(fs, testPath, 're-added', simpleCb))(); - waitFor([addSpy], function() { - addSpy.should.have.been.calledWith(testPath); - done(); - }); - })); - }); + await aspy(watcher); + await delay(); + await fs.promises.unlink(testPath); + await waitFor([unlinkSpy]); + unlinkSpy.should.have.been.calledWith(testPath); + await delay(); + await write(testPath, 're-added'); + await waitFor([addSpy]); + addSpy.should.have.been.calledWith(testPath); }); - it('should ignore unwatched siblings', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - var siblingPath = getFixturePath('change.txt'); - watcher = chokidar.watch(testPath, options) - .on('all', spy) - .on('ready', w(function() { - fs.writeFile(siblingPath, Date.now(), simpleCb); - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy], function() { - spy.should.have.always.been.calledWith('add', testPath); - done(); - }); - })); + it('should ignore unwatched siblings', async () => { + const testPath = getFixturePath('add.txt'); + const siblingPath = getFixturePath('change.txt'); + watcher = chokidar.watch(testPath, options); + const spy = await aspy(watcher, 'all'); + await delay(); + await write(siblingPath, Date.now()); + await write(testPath, Date.now()); + await waitFor([spy]); + spy.should.have.always.been.calledWith('add', testPath); }); // PR 682 is failing. @@ -2262,21 +2181,16 @@ function runTests(baseopts) { }); }); }); - it('should not prevent the process from exiting', function(done) { + it('should not prevent the process from exiting', async function(done) { var scriptFile = getFixturePath('script.js'); var scriptContent = '\ var chokidar = require("' + __dirname.replace(/\\/g, '\\\\') + '");\n\ var watcher = chokidar.watch("' + scriptFile.replace(/\\/g, '\\\\') + '");\n\ watcher.close();\n\ process.stdout.write("closed");\n'; - fs.writeFile(scriptFile, scriptContent, function (err) { - if (err) throw err; - cp.exec('node ' + scriptFile, function (err, stdout) { - if (err) throw err; - expect(stdout.toString()).to.equal('closed'); - done(); - }); - }); + await write(scriptFile, scriptContent); + const stdout = await exec('node ' + scriptFile); + expect(stdout.toString()).to.equal('closed'); }); }); describe('env variable option override', function() { From 17acaaeb1f8f09ff0098479a9b14806dd81cb7c7 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 12 Feb 2019 01:07:33 -0800 Subject: [PATCH 04/10] Move all tests to async syntax. --- test.js | 2758 ++++++++++++++++++++++++++----------------------------- 1 file changed, 1292 insertions(+), 1466 deletions(-) diff --git a/test.js b/test.js index 61eac399..8e857c3f 100644 --- a/test.js +++ b/test.js @@ -1,32 +1,25 @@ 'use strict'; -var chokidar = require('./'); -var promisify = require('util').promisify; -var chai = require('chai'); -var expect = chai.expect; -var should = chai.should(); -var sinon = require('sinon'); -var rimraf = promisify(require('rimraf')); -var fs = require('graceful-fs'); -var sysPath = require('path'); -var upath = require("upath"); -var exec = promisify(require('child_process').exec); +const chokidar = require('./'); +const promisify = require('util').promisify; +const chai = require('chai'); +const expect = chai.expect; +const should = chai.should(); +const sinon = require('sinon'); +const rimraf = promisify(require('rimraf')); +const fs = require('graceful-fs'); +const sysPath = require('path'); +const upath = require("upath"); +const exec = promisify(require('child_process').exec); chai.use(require('sinon-chai')); -var os = process.platform; +const os = process.platform; const fs_promises = require('fs').promises; const write = fs_promises.writeFile; +const fsunlink = fs_promises.unlink; const isTravisMac = process.env.TRAVIS && os === 'darwin'; -const delay = async (time) => { - const timer = time || slowerDelay || 50; - return new Promise((resolve) => { - // console.log('delay#Promise', timer); - setTimeout(resolve, timer); - }); -}; - // spyOnReady const aspy = async (watcher, eventName, spy) => { if (spy == null) spy = sinon.spy(); @@ -35,34 +28,16 @@ const aspy = async (watcher, eventName, spy) => { if (typeof eventName === 'string') { watcher.on(eventName, spy); } - // console.log('aspy', eventName); watcher.on('ready', () => { resolve(spy); }); }); }; -function getFixturePath (subPath) { - return sysPath.join( - __dirname, - 'test-fixtures', - subdir && subdir.toString() || '', - subPath - ); -} -function getGlobPath (subPath) { - return upath.join( - __dirname, - 'test-fixtures', - subdir && subdir.toString() || '', - subPath - ); -} - -var watcher, +let watcher, watcher2, usedWatchers = [], - fixturesPath = getFixturePath(''), + fixturesPath, subdir = 0, options, node010 = process.version.slice(0, 5) === 'v0.10', @@ -75,16 +50,32 @@ var watcher, mochaIt = it, PERM_ARR = 0x1ed; // rwe, r+e, r+e; 755 +const delay = async (time) => { + return new Promise((resolve) => { + const timer = time || slowerDelay || 50; + setTimeout(resolve, timer); + }); +}; + +const getFixturePath = (subPath) => { + const subd = subdir && subdir.toString() || ''; + return sysPath.join(__dirname, 'test-fixtures', subd, subPath); +}; +const getGlobPath = (subPath) => { + const subd = subdir && subdir.toString() || ''; + return upath.join(__dirname, 'test-fixtures', subd, subPath); +}; +fixturesPath = getFixturePath(''); if (!fs.readFileSync(__filename).toString().match(/\sit\.only\(/)) { it = function() { testCount++; mochaIt.apply(this, arguments); - } + }; it.skip = function() { testCount--; - mochaIt.skip.apply(this, arguments) - } + mochaIt.skip.apply(this, arguments); + }; } before(async () => { @@ -99,11 +90,12 @@ before(async () => { if (++writtenCount === testCount * 2) { subdir = 0; return; - } - await write(sysPath.join(fixturesPath, 'unlink.txt'), 'b'); - if (++writtenCount === testCount * 2) { - subdir = 0; - return; + } else { + await write(sysPath.join(fixturesPath, 'unlink.txt'), 'b'); + if (++writtenCount === testCount * 2) { + subdir = 0; + return; + } } } }); @@ -114,7 +106,7 @@ beforeEach(function() { }); const closeWatchers = async () => { - var u; + let u; while (u = usedWatchers.pop()) u.close(); if (isTravisMac) { await delay(500); @@ -122,7 +114,8 @@ const closeWatchers = async () => { } else { return true; } -} +}; + function disposeWatcher(watcher) { if (!watcher || !watcher.close) return; os === 'darwin' ? usedWatchers.push(watcher) : watcher.close(); @@ -132,25 +125,7 @@ afterEach(function() { disposeWatcher(watcher2); }); -describe('chokidar', function() { - this.timeout(6000); - it('should expose public API methods', function() { - chokidar.FSWatcher.should.be.a('function'); - chokidar.watch.should.be.a('function'); - }); - - if (os === 'darwin') { - describe('fsevents (native extension)', runTests.bind(this, {useFsEvents: true})); - } - if (os !== 'darwin' || !node010) { - describe('fs.watch (non-polling)', runTests.bind(this, {usePolling: false, useFsEvents: false})); - } - describe('fs.watchFile (polling)', runTests.bind(this, {usePolling: true, interval: 10})); -}); - -function simpleCb(err) { if (err) throw err; } - -function runTests(baseopts) { +const runTests = function(baseopts) { baseopts.persistent = true; before(function() { @@ -163,7 +138,7 @@ function runTests(baseopts) { if (win32Polling010) { slowerDelay = 900; } else if (node010 || osXFsWatch) { - slowerDelay = 200; + slowerDelay = 900; } else { slowerDelay = undefined; } @@ -174,7 +149,7 @@ function runTests(baseopts) { beforeEach(function clean() { options = {}; Object.keys(baseopts).forEach(function(key) { - options[key] = baseopts[key] + options[key] = baseopts[key]; }); }); @@ -184,25 +159,29 @@ function runTests(baseopts) { } const waitFor = async (spies) => { - if (!Array.isArray(spies)) spies = [spies] + if (spies.length === 0) throw new TypeError('SPies zero'); return new Promise((resolve, reject) => { - function isSpyReady(spy) { - return Array.isArray(spy) ? spy[0].callCount >= spy[1] : spy.callCount; - } - var intrvl; + const isSpyReady = (spy) => { + if (Array.isArray(spy)) { + return spy[0].callCount >= spy[1]; + } else { + return spy.callCount >= 1; + } + }; + let intrvl, timeo; function finish() { clearInterval(intrvl); - clearTimeout(to); + clearTimeout(timeo); resolve(); } intrvl = setInterval(() => { if (spies.every(isSpyReady)) finish(); - }, 5); - var to = setTimeout(finish, 3500); + }, 20); + timeo = setTimeout(finish, 3500); }); - } + }; - describe('watch a directory', () => { + describe('watch a directory', function() { var readySpy, rawSpy; beforeEach(function() { options.ignoreInitial = true; @@ -212,8 +191,9 @@ function runTests(baseopts) { stdWatcher().on('ready', readySpy).on('raw', rawSpy); }); afterEach(async () => { - await waitFor(readySpy); + await waitFor([readySpy]); readySpy.should.have.been.calledOnce; + readySpy = undefined; rawSpy = undefined; await closeWatchers(); }); @@ -228,38 +208,50 @@ function runTests(baseopts) { watcher.getWatched.should.be.a('function'); }); it('should emit `add` event when file was added', async () => { - var testPath = getFixturePath('add.txt'); - const spy = sinon.spy(); - await aspy(watcher, 'add', spy); + const testPath = getFixturePath('add.txt'); + const spy = await aspy(watcher, 'add'); await delay(); await write(testPath, Date.now()); - await waitFor(spy); + await waitFor([spy]); spy.should.have.been.calledOnce; spy.should.have.been.calledWith(testPath); expect(spy.args[0][1]).to.be.ok; // stats rawSpy.should.have.been.called; }); it('should emit nine `add` events when nine files were added in one directory', async () => { - var test1Path = getFixturePath('add1.txt'); - var test2Path = getFixturePath('add2.txt'); - var test3Path = getFixturePath('add3.txt'); - var test4Path = getFixturePath('add4.txt'); - var test5Path = getFixturePath('add5.txt'); - var test6Path = getFixturePath('add6.txt'); - var test7Path = getFixturePath('add7.txt'); - var test8Path = getFixturePath('add8.txt'); - var test9Path = getFixturePath('add9.txt'); - var spy = await aspy(watcher, 'add'); - await write(test1Path, Date.now()); - await write(test2Path, Date.now()); - await write(test3Path, Date.now()); - await write(test4Path, Date.now()); - await write(test5Path, Date.now()); - await delay(200); - await write(test6Path, Date.now()); - await write(test7Path, Date.now()); - await write(test8Path, Date.now()); - await write(test9Path, Date.now()); + const test1Path = getFixturePath('add1.txt'); + const test2Path = getFixturePath('add2.txt'); + const test3Path = getFixturePath('add3.txt'); + const test4Path = getFixturePath('add4.txt'); + const test5Path = getFixturePath('add5.txt'); + const test6Path = getFixturePath('add6.txt'); + const test7Path = getFixturePath('add7.txt'); + const test8Path = getFixturePath('add8.txt'); + const test9Path = getFixturePath('add9.txt'); + + const spy = sinon.spy(); + watcher.on('add', (path) => { + spy(path); + }); + await aspy(watcher); + + await delay(); + + (async () => { + await write(test1Path, Date.now()); + await write(test2Path, Date.now()); + await write(test3Path, Date.now()); + await write(test4Path, Date.now()); + await write(test5Path, Date.now()); + + await delay(100); + write(test6Path, Date.now()); + write(test7Path, Date.now()); + write(test8Path, Date.now()); + write(test9Path, Date.now()); + delay(); + })(); + await waitFor([[spy, 9]]); spy.should.have.been.calledWith(test1Path); spy.should.have.been.calledWith(test2Path); @@ -272,39 +264,39 @@ function runTests(baseopts) { spy.should.have.been.calledWith(test9Path); }); it('should emit thirtythree `add` events when thirtythree files were added in nine directories', async () => { - var test1Path = getFixturePath('add1.txt'); - var testb1Path = getFixturePath('b/add1.txt'); - var testc1Path = getFixturePath('c/add1.txt'); - var testd1Path = getFixturePath('d/add1.txt'); - var teste1Path = getFixturePath('e/add1.txt'); - var testf1Path = getFixturePath('f/add1.txt'); - var testg1Path = getFixturePath('g/add1.txt'); - var testh1Path = getFixturePath('h/add1.txt'); - var testi1Path = getFixturePath('i/add1.txt'); - var test2Path = getFixturePath('add2.txt'); - var testb2Path = getFixturePath('b/add2.txt'); - var testc2Path = getFixturePath('c/add2.txt'); - var test3Path = getFixturePath('add3.txt'); - var testb3Path = getFixturePath('b/add3.txt'); - var testc3Path = getFixturePath('c/add3.txt'); - var test4Path = getFixturePath('add4.txt'); - var testb4Path = getFixturePath('b/add4.txt'); - var testc4Path = getFixturePath('c/add4.txt'); - var test5Path = getFixturePath('add5.txt'); - var testb5Path = getFixturePath('b/add5.txt'); - var testc5Path = getFixturePath('c/add5.txt'); - var test6Path = getFixturePath('add6.txt'); - var testb6Path = getFixturePath('b/add6.txt'); - var testc6Path = getFixturePath('c/add6.txt'); - var test7Path = getFixturePath('add7.txt'); - var testb7Path = getFixturePath('b/add7.txt'); - var testc7Path = getFixturePath('c/add7.txt'); - var test8Path = getFixturePath('add8.txt'); - var testb8Path = getFixturePath('b/add8.txt'); - var testc8Path = getFixturePath('c/add8.txt'); - var test9Path = getFixturePath('add9.txt'); - var testb9Path = getFixturePath('b/add9.txt'); - var testc9Path = getFixturePath('c/add9.txt'); + const test1Path = getFixturePath('add1.txt'); + const testb1Path = getFixturePath('b/add1.txt'); + const testc1Path = getFixturePath('c/add1.txt'); + const testd1Path = getFixturePath('d/add1.txt'); + const teste1Path = getFixturePath('e/add1.txt'); + const testf1Path = getFixturePath('f/add1.txt'); + const testg1Path = getFixturePath('g/add1.txt'); + const testh1Path = getFixturePath('h/add1.txt'); + const testi1Path = getFixturePath('i/add1.txt'); + const test2Path = getFixturePath('add2.txt'); + const testb2Path = getFixturePath('b/add2.txt'); + const testc2Path = getFixturePath('c/add2.txt'); + const test3Path = getFixturePath('add3.txt'); + const testb3Path = getFixturePath('b/add3.txt'); + const testc3Path = getFixturePath('c/add3.txt'); + const test4Path = getFixturePath('add4.txt'); + const testb4Path = getFixturePath('b/add4.txt'); + const testc4Path = getFixturePath('c/add4.txt'); + const test5Path = getFixturePath('add5.txt'); + const testb5Path = getFixturePath('b/add5.txt'); + const testc5Path = getFixturePath('c/add5.txt'); + const test6Path = getFixturePath('add6.txt'); + const testb6Path = getFixturePath('b/add6.txt'); + const testc6Path = getFixturePath('c/add6.txt'); + const test7Path = getFixturePath('add7.txt'); + const testb7Path = getFixturePath('b/add7.txt'); + const testc7Path = getFixturePath('c/add7.txt'); + const test8Path = getFixturePath('add8.txt'); + const testb8Path = getFixturePath('b/add8.txt'); + const testc8Path = getFixturePath('c/add8.txt'); + const test9Path = getFixturePath('add9.txt'); + const testb9Path = getFixturePath('b/add9.txt'); + const testc9Path = getFixturePath('c/add9.txt'); fs.mkdirSync(getFixturePath('b'), PERM_ARR); fs.mkdirSync(getFixturePath('c'), PERM_ARR); fs.mkdirSync(getFixturePath('d'), PERM_ARR); @@ -321,6 +313,8 @@ function runTests(baseopts) { await write(test3Path, Date.now()); await write(test4Path, Date.now()); await write(test5Path, Date.now()); + + await delay(200); await write(test6Path, Date.now()); await write(test7Path, Date.now()); await write(test8Path, Date.now()); @@ -330,6 +324,8 @@ function runTests(baseopts) { await write(testb3Path, Date.now()); await write(testb4Path, Date.now()); await write(testb5Path, Date.now()); + + await delay(200); await write(testb6Path, Date.now()); await write(testb7Path, Date.now()); await write(testb8Path, Date.now()); @@ -339,6 +335,8 @@ function runTests(baseopts) { await write(testc3Path, Date.now()); await write(testc4Path, Date.now()); await write(testc5Path, Date.now()); + + await delay(150); await write(testc6Path, Date.now()); await write(testc7Path, Date.now()); await write(testc8Path, Date.now()); @@ -346,6 +344,8 @@ function runTests(baseopts) { await write(testd1Path, Date.now()); await write(teste1Path, Date.now()); await write(testf1Path, Date.now()); + + await delay(100); await write(testg1Path, Date.now()); await write(testh1Path, Date.now()); await write(testi1Path, Date.now()); @@ -386,7 +386,7 @@ function runTests(baseopts) { spy.should.have.been.calledWith(testi1Path); }); it('should emit `addDir` event when directory was added', async () => { - var testDir = getFixturePath('subdir'); + const testDir = getFixturePath('subdir'); const spy = await aspy(watcher, 'addDir'); spy.should.not.have.been.called; await fs.promises.mkdir(testDir, PERM_ARR); @@ -397,10 +397,10 @@ function runTests(baseopts) { rawSpy.should.have.been.called; }); it('should emit `change` event when file was changed', async () => { - var testPath = getFixturePath('change.txt'); + const testPath = getFixturePath('change.txt'); const spy = await aspy(watcher, 'change'); spy.should.not.have.been.called; - fs.writeFile(testPath, Date.now(), simpleCb); + await write(testPath, Date.now()); await waitFor([spy]); spy.should.have.been.calledWith(testPath); expect(spy.args[0][1]).to.be.ok; // stats @@ -411,7 +411,7 @@ function runTests(baseopts) { const testPath = getFixturePath('unlink.txt'); const spy = await aspy(watcher, 'unlink'); spy.should.not.have.been.called; - fs.unlink(testPath, simpleCb); + await fsunlink(testPath); await waitFor([spy]); spy.should.have.been.calledWith(testPath); expect(spy.args[0][1]).to.not.be.ok; // no stats @@ -422,6 +422,7 @@ function runTests(baseopts) { const testDir = getFixturePath('subdir'); fs.mkdirSync(testDir, PERM_ARR); const spy = await aspy(watcher, 'unlinkDir'); + await delay(); await fs.promises.rmdir(testDir); await waitFor([spy]); @@ -438,7 +439,7 @@ function runTests(baseopts) { fs.mkdirSync(testDir2, PERM_ARR); fs.mkdirSync(testDir3, PERM_ARR); const spy = await aspy(watcher, 'unlinkDir'); - rimraf(testDir2, simpleCb); // test removing in one + await rimraf(testDir2); // test removing in one await waitFor([spy]); spy.should.have.been.calledWith(testDir2); spy.should.have.been.calledWith(testDir3); @@ -455,6 +456,7 @@ function runTests(baseopts) { await aspy(watcher); unlinkSpy.should.not.have.been.called; addSpy.should.not.have.been.called; + await delay(); await fs.promises.rename(testPath, newPath); await waitFor([unlinkSpy, addSpy]); @@ -481,9 +483,10 @@ function runTests(baseopts) { unlinkSpy.should.not.have.been.called; addSpy.should.not.have.been.called; changeSpy.should.not.have.been.called; - await fs.promises.unlink(testPath, simpleCb); + await fs.promises.unlink(testPath); await waitFor([unlinkSpy.withArgs(testPath)]); unlinkSpy.should.have.been.calledWith(testPath); + await delay(); await write(testPath, Date.now()); await waitFor([addSpy.withArgs(testPath)]); @@ -497,9 +500,10 @@ function runTests(baseopts) { const newPath2 = getFixturePath('moved-again.txt'); await aspy(watcher, 'unlink', unlinkSpy); await fs.promises.rename(testPath, newPath1); + await delay(300); await fs.promises.rename(newPath1, newPath2); - await waitFor([unlinkSpy.withArgs(newPath1)]) + await waitFor([unlinkSpy.withArgs(newPath1)]); unlinkSpy.withArgs(testPath).should.have.been.calledOnce; unlinkSpy.withArgs(newPath1).should.have.been.calledOnce; unlinkSpy.withArgs(newPath2).should.not.have.been.called; @@ -528,22 +532,24 @@ function runTests(baseopts) { const addSpy = sinon.spy(function addSpy(){}); const parentPath = getFixturePath('subdir2'); const subPath = getFixturePath('subdir2/subsub'); - watcher.on('unlinkDir', unlinkSpy).on('addDir', addSpy) + watcher.on('unlinkDir', unlinkSpy).on('addDir', addSpy); await aspy(watcher); - await fs.promises.mkdir(parentPath, PERM_ARR) + await fs.promises.mkdir(parentPath, PERM_ARR); + await delay(win32Polling ? 900 : 300); await fs.promises.rmdir(parentPath); await waitFor([unlinkSpy.withArgs(parentPath)]); unlinkSpy.should.have.been.calledWith(parentPath); await fs.promises.mkdir(parentPath, PERM_ARR); + await delay(win32Polling ? 2200 : 1200); await fs.promises.mkdir(subPath, PERM_ARR); - await waitFor([[addSpy, 3]]) + await waitFor([[addSpy, 3]]); addSpy.should.have.been.calledWith(parentPath); addSpy.should.have.been.calledWith(subPath); }); }); - describe('watch individual files', () => { + describe('watch individual files', function() { before(closeWatchers); it('should detect changes', async () => { const testPath = getFixturePath('change.txt'); @@ -557,6 +563,7 @@ function runTests(baseopts) { const testPath = getFixturePath('unlink.txt'); watcher = chokidar.watch(testPath, options); const spy = await aspy(watcher, 'unlink'); + await delay(); await fs.promises.unlink(testPath); await waitFor([spy]); @@ -569,12 +576,14 @@ function runTests(baseopts) { const testPath = getFixturePath('unlink.txt'); watcher = chokidar.watch([testPath], options) .on('unlink', unlinkSpy) - .on('add', addSpy) + .on('add', addSpy); await aspy(watcher); + await delay(); await fs.promises.unlink(testPath); await waitFor([unlinkSpy]); unlinkSpy.should.have.been.calledWith(testPath); + await delay(); await write(testPath, 're-added'); await waitFor([addSpy]); @@ -586,6 +595,7 @@ function runTests(baseopts) { const siblingPath = getFixturePath('change.txt'); watcher = chokidar.watch(testPath, options); const spy = await aspy(watcher, 'all'); + await delay(); await write(siblingPath, Date.now()); await write(testPath, Date.now()); @@ -595,465 +605,422 @@ function runTests(baseopts) { // PR 682 is failing. describe.skip('Skipping gh-682: should detect unlink', function() { - it('should detect unlink while watching a non-existent second file in another directory', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('unlink.txt'); - var otherDirPath = getFixturePath('other-dir'); - var otherPath = getFixturePath('other-dir/other.txt'); + it('should detect unlink while watching a non-existent second file in another directory', async () => { + const testPath = getFixturePath('unlink.txt'); + const otherDirPath = getFixturePath('other-dir'); + const otherPath = getFixturePath('other-dir/other.txt'); fs.mkdirSync(otherDirPath, PERM_ARR); + watcher = chokidar.watch([testPath, otherPath], options); // intentionally for this test don't write fs.writeFileSync(otherPath, 'other'); - watcher = chokidar.watch([testPath, otherPath], options) - .on('unlink', spy) - .on('ready', function() { - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([spy], function() { - spy.should.have.been.calledWith(testPath); - done(); - }); - }); + const spy = await aspy(watcher, 'unlink'); + + await delay(); + await fs.promises.unlink(testPath); + await waitFor([spy]); + spy.should.have.been.calledWith(testPath); }); - it('should detect unlink and re-add while watching a second file', function(done) { + it('should detect unlink and re-add while watching a second file', async () => { options.ignoreInitial = true; - var unlinkSpy = sinon.spy(function unlinkSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); - var testPath = getFixturePath('unlink.txt'); - var otherPath = getFixturePath('other.txt'); + const unlinkSpy = sinon.spy(function unlinkSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); + const testPath = getFixturePath('unlink.txt'); + const otherPath = getFixturePath('other.txt'); fs.writeFileSync(otherPath, 'other'); watcher = chokidar.watch([testPath, otherPath], options) .on('unlink', unlinkSpy) - .on('add', addSpy) - .on('ready', function() { - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([unlinkSpy], w(function() { - unlinkSpy.should.have.been.calledWith(testPath); - w(fs.writeFile.bind(fs, testPath, 're-added', simpleCb))(); - waitFor([addSpy], function() { - addSpy.should.have.been.calledWith(testPath); - done(); - }); - })); - }); + .on('add', addSpy); + await aspy(watcher); + + await delay(); + await fs.promises.unlink(testPath); + await waitFor([unlinkSpy]); + + await delay(); + unlinkSpy.should.have.been.calledWith(testPath); + + await delay(); + write(testPath, 're-added'); + await waitFor([addSpy]); + addSpy.should.have.been.calledWith(testPath); }); - it('should detect unlink and re-add while watching a non-existent second file in another directory', function(done) { + it('should detect unlink and re-add while watching a non-existent second file in another directory', async () => { options.ignoreInitial = true; - var unlinkSpy = sinon.spy(function unlinkSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); - var testPath = getFixturePath('unlink.txt'); - var otherDirPath = getFixturePath('other-dir'); - var otherPath = getFixturePath('other-dir/other.txt'); + const unlinkSpy = sinon.spy(function unlinkSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); + const testPath = getFixturePath('unlink.txt'); + const otherDirPath = getFixturePath('other-dir'); + const otherPath = getFixturePath('other-dir/other.txt'); fs.mkdirSync(otherDirPath, PERM_ARR); // intentionally for this test don't write fs.writeFileSync(otherPath, 'other'); watcher = chokidar.watch([testPath, otherPath], options) .on('unlink', unlinkSpy) - .on('add', addSpy) - .on('ready', function() { - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([unlinkSpy], w(function() { - unlinkSpy.should.have.been.calledWith(testPath); - w(fs.writeFile.bind(fs, testPath, 're-added', simpleCb))(); - waitFor([addSpy], function() { - addSpy.should.have.been.calledWith(testPath); - done(); - }); - })); - }); + .on('add', addSpy); + await aspy(watcher); + + await delay(); + await fsunlink(testPath); + await waitFor([unlinkSpy]); + + await delay(); + unlinkSpy.should.have.been.calledWith(testPath); + + await delay(); + await write(testPath, 're-added'); + await waitFor([addSpy]); + addSpy.should.have.been.calledWith(testPath); }); - it('should detect unlink and re-add while watching a non-existent second file in the same directory', function(done) { + it('should detect unlink and re-add while watching a non-existent second file in the same directory', async () => { options.ignoreInitial = true; - var unlinkSpy = sinon.spy(function unlinkSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); - var testPath = getFixturePath('unlink.txt'); - var otherPath = getFixturePath('other.txt'); + const unlinkSpy = sinon.spy(function unlinkSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); + const testPath = getFixturePath('unlink.txt'); + const otherPath = getFixturePath('other.txt'); // intentionally for this test don't write fs.writeFileSync(otherPath, 'other'); watcher = chokidar.watch([testPath, otherPath], options) .on('unlink', unlinkSpy) - .on('add', addSpy) - .on('ready', function() { - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([unlinkSpy], w(function() { - unlinkSpy.should.have.been.calledWith(testPath); - w(fs.writeFile.bind(fs, testPath, 're-added', simpleCb))(); - waitFor([addSpy], function() { - addSpy.should.have.been.calledWith(testPath); - done(); - }); - })); - }); + .on('add', addSpy); + await aspy(watcher); + + await delay(); + await fsunlink(testPath); + await waitFor([unlinkSpy]); + + await delay(); + unlinkSpy.should.have.been.calledWith(testPath); + + await delay(); + await write(testPath, 're-added'); + await waitFor([addSpy]); + addSpy.should.have.been.calledWith(testPath); }); - it('should detect two unlinks and one re-add', function(done) { + it('should detect two unlinks and one re-add', async () => { options.ignoreInitial = true; - var unlinkSpy = sinon.spy(function unlinkSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); - var testPath = getFixturePath('unlink.txt'); - var otherPath = getFixturePath('other.txt'); + const unlinkSpy = sinon.spy(function unlinkSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); + const testPath = getFixturePath('unlink.txt'); + const otherPath = getFixturePath('other.txt'); fs.writeFileSync(otherPath, 'other'); watcher = chokidar.watch([testPath, otherPath], options) .on('unlink', unlinkSpy) - .on('add', addSpy) - .on('ready', function() { - w(fs.unlink.bind(fs, otherPath, simpleCb))(); - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([[unlinkSpy, 2]], w(function() { - unlinkSpy.should.have.been.calledWith(otherPath); - unlinkSpy.should.have.been.calledWith(testPath); - w(fs.writeFile.bind(fs, testPath, 're-added', simpleCb))(); - waitFor([addSpy], function() { - addSpy.should.have.been.calledWith(testPath); - done(); - }); - })); - }); + .on('add', addSpy); + await aspy(watcher); + + await delay(); + await fsunlink(otherPath); + + await delay(); + await fsunlink(testPath); + await waitFor([[unlinkSpy, 2]]); + + await delay(); + unlinkSpy.should.have.been.calledWith(otherPath); + unlinkSpy.should.have.been.calledWith(testPath); + + await delay(); + await write(testPath, 're-added'); + await waitFor([addSpy]); + addSpy.should.have.been.calledWith(testPath); }); - it('should detect unlink and re-add while watching a second file and a non-existent third file', function(done) { + it('should detect unlink and re-add while watching a second file and a non-existent third file', async () => { options.ignoreInitial = true; - var unlinkSpy = sinon.spy(function unlinkSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); - var testPath = getFixturePath('unlink.txt'); - var otherPath = getFixturePath('other.txt'); - var other2Path = getFixturePath('other2.txt'); + const unlinkSpy = sinon.spy(function unlinkSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); + const testPath = getFixturePath('unlink.txt'); + const otherPath = getFixturePath('other.txt'); + const other2Path = getFixturePath('other2.txt'); fs.writeFileSync(otherPath, 'other'); // intentionally for this test don't write fs.writeFileSync(other2Path, 'other2'); watcher = chokidar.watch([testPath, otherPath, other2Path], options) .on('unlink', unlinkSpy) - .on('add', addSpy) - .on('ready', function() { - w(fs.unlink.bind(fs, testPath, simpleCb))(); - waitFor([unlinkSpy], w(function() { - unlinkSpy.should.have.been.calledWith(testPath); - w(fs.writeFile.bind(fs, testPath, 're-added', simpleCb))(); - waitFor([addSpy], function() { - addSpy.should.have.been.calledWith(testPath); - done(); - }); - })); - }); + .on('add', addSpy); + await aspy(watcher, 'ready'); + await delay(); + await fsunlink(testPath); + + await waitFor([unlinkSpy]); + await delay(); + unlinkSpy.should.have.been.calledWith(testPath); + + await delay(); + await write(testPath, 're-added'); + await waitFor([addSpy]); + addSpy.should.have.been.calledWith(testPath); }); }); }); describe('renamed directory', function() { - it('should emit `add` for a file in a renamed directory', function(done) { + it('should emit `add` for a file in a renamed directory', async () => { options.ignoreInitial = true; - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); - var testPath = getFixturePath('subdir/add.txt'); - var renamedDir = getFixturePath('subdir-renamed'); - var expectedPath = sysPath.join(renamedDir, 'add.txt') - fs.mkdir(testDir, PERM_ARR, function() { - fs.writeFile(testPath, Date.now(), function() { - watcher = chokidar.watch(fixturesPath, options) - .on('add', spy) - .on('ready', function() { - w(function() { - fs.rename(testDir, renamedDir, simpleCb); - }, 1000)(); - waitFor([spy], function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith(expectedPath); - done(); - }); - }); - }); - }); + const testDir = getFixturePath('subdir'); + const testPath = getFixturePath('subdir/add.txt'); + const renamedDir = getFixturePath('subdir-renamed'); + const expectedPath = sysPath.join(renamedDir, 'add.txt'); + await fs.promises.mkdir(testDir, PERM_ARR); + await write(testPath, Date.now()); + watcher = chokidar.watch(fixturesPath, options); + const spy = await aspy(watcher, 'add'); + + await delay(1000); + await fs.promises.rename(testDir, renamedDir); + await waitFor([spy]); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith(expectedPath); }); }); describe('watch non-existent paths', function() { - it('should watch non-existent file and detect add', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - watcher = chokidar.watch(testPath, options) - .on('add', spy) - .on('ready', function() { - w(fs.writeFile.bind(fs, testPath, Date.now(), simpleCb))(); - waitFor([spy], function() { - spy.should.have.been.calledWith(testPath); - done(); - }); - }); + it('should watch non-existent file and detect add', async () => { + const testPath = getFixturePath('add.txt'); + watcher = chokidar.watch(testPath, options); + const spy = await aspy(watcher, 'add'); + + await delay(); + await write(testPath, Date.now()); + await waitFor([spy]); + spy.should.have.been.calledWith(testPath); }); - it('should watch non-existent dir and detect addDir/add', function(done) { - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); - var testPath = getFixturePath('subdir/add.txt'); - watcher = chokidar.watch(testDir, options) - .on('all', spy) - .on('ready', function() { - spy.should.not.have.been.called; - w(function() { - fs.mkdir(testDir, PERM_ARR, w(function() { - fs.writeFile(testPath, 'hello', simpleCb); - }, win32Polling010 ? 900 : undefined)); - }, win32Polling010 ? 900 : undefined)(); - waitFor([spy.withArgs('add')], function() { - spy.should.have.been.calledWith('addDir', testDir); - spy.should.have.been.calledWith('add', testPath); - done(); - }); - }); + it('should watch non-existent dir and detect addDir/add', async () => { + const testDir = getFixturePath('subdir'); + const testPath = getFixturePath('subdir/add.txt'); + watcher = chokidar.watch(testDir, options); + const spy = await aspy(watcher, 'all'); + spy.should.not.have.been.called; + + await delay(win32Polling010 ? 900 : undefined); + await fs.promises.mkdir(testDir, PERM_ARR); + + await delay(win32Polling010 ? 900 : undefined); + await write(testPath, 'hello'); + await waitFor([spy.withArgs('add')]); + spy.should.have.been.calledWith('addDir', testDir); + spy.should.have.been.calledWith('add', testPath); }); }); describe('watch glob patterns', function() { before(closeWatchers); - it('should correctly watch and emit based on glob input', function(done) { - var spy = sinon.spy(); - var watchPath = getGlobPath('*a*.txt'); - var addPath = getFixturePath('add.txt'); - var changePath = getFixturePath('change.txt'); - watcher = chokidar.watch(watchPath, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', changePath); - w(function() { - fs.writeFile(addPath, Date.now(), simpleCb); - fs.writeFile(changePath, Date.now(), simpleCb); - })(); - waitFor([[spy, 3], spy.withArgs('add', addPath)], function() { - spy.should.have.been.calledWith('add', addPath); - spy.should.have.been.calledWith('change', changePath); - spy.should.not.have.been.calledWith('add', getFixturePath('unlink.txt')); - spy.should.not.have.been.calledWith('addDir'); - done(); - }); - }); - }); - it('should respect negated glob patterns', function(done) { - var spy = sinon.spy(); - var watchPath = getGlobPath('*'); - var negatedWatchPath = '!' + getGlobPath('*a*.txt'); - var unlinkPath = getFixturePath('unlink.txt'); - watcher = chokidar.watch([watchPath, negatedWatchPath], options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith('add', unlinkPath); - w(fs.unlink.bind(fs, unlinkPath, simpleCb))(); - waitFor([[spy, 2], spy.withArgs('unlink')], function() { - if (!osXFsWatch010) spy.should.have.been.calledTwice; - spy.should.have.been.calledWith('unlink', unlinkPath); - done(); - }); - }); + it('should correctly watch and emit based on glob input', async () => { + const watchPath = getGlobPath('*a*.txt'); + const addPath = getFixturePath('add.txt'); + const changePath = getFixturePath('change.txt'); + watcher = chokidar.watch(watchPath, options); + const spy = await aspy(watcher, 'all'); + spy.should.have.been.calledWith('add', changePath); + + await write(addPath, Date.now()); + await write(changePath, Date.now()); + + await delay(); + await waitFor([[spy, 3], spy.withArgs('add', addPath)]); + spy.should.have.been.calledWith('add', addPath); + spy.should.have.been.calledWith('change', changePath); + spy.should.not.have.been.calledWith('add', getFixturePath('unlink.txt')); + spy.should.not.have.been.calledWith('addDir'); + }); + it('should respect negated glob patterns', async () => { + const watchPath = getGlobPath('*'); + const negatedWatchPath = '!' + getGlobPath('*a*.txt'); + const unlinkPath = getFixturePath('unlink.txt'); + watcher = chokidar.watch([watchPath, negatedWatchPath], options); + const spy = await aspy(watcher, 'all'); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith('add', unlinkPath); + + await delay(); + await fsunlink(unlinkPath); + await waitFor([[spy, 2], spy.withArgs('unlink')]); + if (!osXFsWatch010) spy.should.have.been.calledTwice; + spy.should.have.been.calledWith('unlink', unlinkPath); }); - it('should traverse subdirs to match globstar patterns', function(done) { - var spy = sinon.spy(); - var watchPath = getGlobPath('../../test-*/' + subdir + '/**/a*.txt'); + it('should traverse subdirs to match globstar patterns', async () => { + const watchPath = getGlobPath('../../test-*/' + subdir + '/**/a*.txt'); fs.mkdirSync(getFixturePath('subdir'), PERM_ARR); fs.mkdirSync(getFixturePath('subdir/subsub'), PERM_ARR); fs.writeFileSync(getFixturePath('subdir/a.txt'), 'b'); fs.writeFileSync(getFixturePath('subdir/b.txt'), 'b'); fs.writeFileSync(getFixturePath('subdir/subsub/ab.txt'), 'b'); - w(function() { - watcher = chokidar.watch(watchPath, options) - .on('all', spy) - .on('ready', function() { - w(function() { - fs.writeFile(getFixturePath('add.txt'), Date.now(), simpleCb); - fs.writeFile(getFixturePath('subdir/subsub/ab.txt'), Date.now(), simpleCb); - fs.unlink(getFixturePath('subdir/a.txt'), simpleCb); - fs.unlink(getFixturePath('subdir/b.txt'), simpleCb); - })(); - waitFor([[spy.withArgs('add'), 3], spy.withArgs('unlink'), spy.withArgs('change')], function() { - spy.withArgs('add').should.have.been.calledThrice; - spy.should.have.been.calledWith('unlink', getFixturePath('subdir/a.txt')); - spy.should.have.been.calledWith('change', getFixturePath('subdir/subsub/ab.txt')); - if (!node010) { - spy.withArgs('unlink').should.have.been.calledOnce; - spy.withArgs('change').should.have.been.calledOnce; - } - done(); - }); - }); - }, win32Polling010 ? 300 : undefined)(); + + await delay(win32Polling010 ? 300 : undefined); + watcher = chokidar.watch(watchPath, options); + const spy = await aspy(watcher, 'all'); + setTimeout(() => { + write(getFixturePath('add.txt'), Date.now()); + write(getFixturePath('subdir/subsub/ab.txt'), Date.now()); + fsunlink(getFixturePath('subdir/a.txt')); + fsunlink(getFixturePath('subdir/b.txt')); + }, 50); + await waitFor([[spy.withArgs('add'), 3], spy.withArgs('unlink'), spy.withArgs('change')]); + spy.withArgs('add').should.have.been.calledThrice; + spy.should.have.been.calledWith('unlink', getFixturePath('subdir/a.txt')); + spy.should.have.been.calledWith('change', getFixturePath('subdir/subsub/ab.txt')); + if (!node010) { + spy.withArgs('unlink').should.have.been.calledOnce; + spy.withArgs('change').should.have.been.calledOnce; + } }); - it('should resolve relative paths with glob patterns', function(done) { - var spy = sinon.spy(); - var watchPath = 'test-*/' + subdir + '/*a*.txt'; + it('should resolve relative paths with glob patterns', async () => { + const watchPath = 'test-*/' + subdir + '/*a*.txt'; // getFixturePath() returns absolute paths, so use sysPath.join() instead - var addPath = sysPath.join('test-fixtures', subdir.toString(), 'add.txt'); - var changePath = sysPath.join('test-fixtures', subdir.toString(), 'change.txt'); - var unlinkPath = sysPath.join('test-fixtures', subdir.toString(), 'unlink.txt'); - watcher = chokidar.watch(watchPath, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', changePath); - w(function() { - fs.writeFile(addPath, Date.now(), simpleCb); - fs.writeFile(changePath, Date.now(), simpleCb); - })(); - waitFor([[spy, 3], spy.withArgs('add', addPath)], function() { - spy.should.have.been.calledWith('add', addPath); - spy.should.have.been.calledWith('change', changePath); - spy.should.not.have.been.calledWith('add', unlinkPath); - spy.should.not.have.been.calledWith('addDir'); - if (!osXFsWatch) spy.should.have.been.calledThrice; - done(); - }); - }); - }); - it('should correctly handle conflicting glob patterns', function(done) { - var spy = sinon.spy(); - var changePath = getFixturePath('change.txt'); - var unlinkPath = getFixturePath('unlink.txt'); - var addPath = getFixturePath('add.txt'); - var watchPaths = [getGlobPath('change*'), getGlobPath('unlink*')]; - watcher = chokidar.watch(watchPaths, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', changePath); - spy.should.have.been.calledWith('add', unlinkPath); - spy.should.have.been.calledTwice; - w(function() { - fs.writeFile(addPath, Date.now(), simpleCb); - fs.writeFile(changePath, Date.now(), simpleCb); - fs.unlink(unlinkPath, simpleCb); - })(); - waitFor([[spy, 4], spy.withArgs('unlink', unlinkPath)], function() { - spy.should.have.been.calledWith('change', changePath); - spy.should.have.been.calledWith('unlink', unlinkPath); - spy.should.not.have.been.calledWith('add', addPath); - if (!osXFsWatch010) spy.callCount.should.equal(4); - done(); - }); - }); - }); - it('should correctly handle intersecting glob patterns', function(done) { - var spy = sinon.spy(); - var changePath = getFixturePath('change.txt'); - var watchPaths = [getGlobPath('cha*'), getGlobPath('*nge.*')]; - watcher = chokidar.watch(watchPaths, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', changePath); - if (!osXFsWatch010) spy.should.have.been.calledOnce; - w(fs.writeFile.bind(fs, changePath, Date.now(), simpleCb))(); - waitFor([[spy, 2]], function() { - spy.should.have.been.calledWith('change', changePath); - if (!osXFsWatch010) spy.should.have.been.calledTwice; - done(); - }); - }); + const addPath = sysPath.join('test-fixtures', subdir.toString(), 'add.txt'); + const changePath = sysPath.join('test-fixtures', subdir.toString(), 'change.txt'); + const unlinkPath = sysPath.join('test-fixtures', subdir.toString(), 'unlink.txt'); + watcher = chokidar.watch(watchPath, options); + const spy = await aspy(watcher, 'all'); + + spy.should.have.been.calledWith('add', changePath); + setTimeout(async () => { + await write(addPath, Date.now()); + await write(changePath, Date.now()); + }, 50); + await waitFor([[spy, 3], spy.withArgs('add', addPath)]); + spy.should.have.been.calledWith('add', addPath); + spy.should.have.been.calledWith('change', changePath); + spy.should.not.have.been.calledWith('add', unlinkPath); + spy.should.not.have.been.calledWith('addDir'); + if (!osXFsWatch) spy.should.have.been.calledThrice; + }); + it('should correctly handle conflicting glob patterns', async () => { + const changePath = getFixturePath('change.txt'); + const unlinkPath = getFixturePath('unlink.txt'); + const addPath = getFixturePath('add.txt'); + const watchPaths = [getGlobPath('change*'), getGlobPath('unlink*')]; + watcher = chokidar.watch(watchPaths, options); + const spy = await aspy(watcher, 'all'); + spy.should.have.been.calledWith('add', changePath); + spy.should.have.been.calledWith('add', unlinkPath); + spy.should.have.been.calledTwice; + + await delay(); + await write(addPath, Date.now()); + await write(changePath, Date.now()); + await fsunlink(unlinkPath); + + await waitFor([[spy, 4], spy.withArgs('unlink', unlinkPath)]); + spy.should.have.been.calledWith('change', changePath); + spy.should.have.been.calledWith('unlink', unlinkPath); + spy.should.not.have.been.calledWith('add', addPath); + if (!osXFsWatch010) spy.callCount.should.equal(4); + }); + it('should correctly handle intersecting glob patterns', async () => { + const changePath = getFixturePath('change.txt'); + const watchPaths = [getGlobPath('cha*'), getGlobPath('*nge.*')]; + watcher = chokidar.watch(watchPaths, options); + const spy = await aspy(watcher, 'all'); + spy.should.have.been.calledWith('add', changePath); + if (!osXFsWatch010) spy.should.have.been.calledOnce; + + await delay(); + await write(changePath, Date.now()); + await waitFor([[spy, 2]]); + spy.should.have.been.calledWith('change', changePath); + if (!osXFsWatch010) spy.should.have.been.calledTwice; }); - it('should not confuse glob-like filenames with globs', function(done) { - var spy = sinon.spy(); - var filePath = getFixturePath('nota[glob].txt'); - fs.writeFile(filePath, 'b', w(function() { - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', filePath); - w(fs.writeFile.bind(fs, filePath, Date.now(), simpleCb))(); - waitFor([spy.withArgs('change', filePath)], function() { - spy.should.have.been.calledWith('change', filePath); - done(); - }); - }); - })); + it('should not confuse glob-like filenames with globs', async () => { + const filePath = getFixturePath('nota[glob].txt'); + await write(filePath, 'b'); + await delay(); + const spy = await aspy(stdWatcher(), 'all'); + spy.should.have.been.calledWith('add', filePath); + + await delay(); + await write(filePath, Date.now()); + await waitFor([spy.withArgs('change', filePath)]); + spy.should.have.been.calledWith('change', filePath); }); - it('should treat glob-like directory names as literal directory names when globbing is disabled', function(done) { + it('should treat glob-like directory names as literal directory names when globbing is disabled', async () => { options.disableGlobbing = true; - var spy = sinon.spy(); - var filePath = getFixturePath('nota[glob]/a.txt'); - var watchPath = getFixturePath('nota[glob]'); - var testDir = getFixturePath('nota[glob]'); - var matchingDir = getFixturePath('notag'); - var matchingFile = getFixturePath('notag/b.txt'); - var matchingFile2 = getFixturePath('notal'); + const filePath = getFixturePath('nota[glob]/a.txt'); + const watchPath = getFixturePath('nota[glob]'); + const testDir = getFixturePath('nota[glob]'); + const matchingDir = getFixturePath('notag'); + const matchingFile = getFixturePath('notag/b.txt'); + const matchingFile2 = getFixturePath('notal'); fs.mkdirSync(testDir, PERM_ARR); fs.writeFileSync(filePath, 'b'); fs.mkdirSync(matchingDir, PERM_ARR); fs.writeFileSync(matchingFile, 'c'); fs.writeFileSync(matchingFile2, 'd'); - watcher = chokidar.watch(watchPath, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', filePath); - spy.should.not.have.been.calledWith('addDir', matchingDir); - spy.should.not.have.been.calledWith('add', matchingFile); - spy.should.not.have.been.calledWith('add', matchingFile2); - w(fs.writeFile.bind(fs, filePath, Date.now(), simpleCb))(); - waitFor([spy.withArgs('change', filePath)], function() { - spy.should.have.been.calledWith('change', filePath); - done(); - }); - }); + watcher = chokidar.watch(watchPath, options); + const spy = await aspy(watcher, 'all'); + + spy.should.have.been.calledWith('add', filePath); + spy.should.not.have.been.calledWith('addDir', matchingDir); + spy.should.not.have.been.calledWith('add', matchingFile); + spy.should.not.have.been.calledWith('add', matchingFile2); + await delay(); + await write(filePath, Date.now()); + + await waitFor([spy.withArgs('change', filePath)]); + spy.should.have.been.calledWith('change', filePath); }); - it('should treat glob-like filenames as literal filenames when globbing is disabled', function(done) { + it('should treat glob-like filenames as literal filenames when globbing is disabled', async () => { options.disableGlobbing = true; - var spy = sinon.spy(); - var filePath = getFixturePath('nota[glob]'); + const filePath = getFixturePath('nota[glob]'); // This isn't using getGlobPath because it isn't treated as a glob - var watchPath = getFixturePath('nota[glob]'); - var matchingDir = getFixturePath('notag'); - var matchingFile = getFixturePath('notag/a.txt'); - var matchingFile2 = getFixturePath('notal'); + const watchPath = getFixturePath('nota[glob]'); + const matchingDir = getFixturePath('notag'); + const matchingFile = getFixturePath('notag/a.txt'); + const matchingFile2 = getFixturePath('notal'); fs.writeFileSync(filePath, 'b'); fs.mkdirSync(matchingDir, PERM_ARR); fs.writeFileSync(matchingFile, 'c'); fs.writeFileSync(matchingFile2, 'd'); - watcher = chokidar.watch(watchPath, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', filePath); - spy.should.not.have.been.calledWith('addDir', matchingDir); - spy.should.not.have.been.calledWith('add', matchingFile); - spy.should.not.have.been.calledWith('add', matchingFile2); - w(fs.writeFile.bind(fs, filePath, Date.now(), simpleCb))(); - waitFor([spy.withArgs('change', filePath)], function() { - spy.should.have.been.calledWith('change', filePath); - done(); - }); - }); + watcher = chokidar.watch(watchPath, options); + const spy = await aspy(watcher, 'all'); + + spy.should.have.been.calledWith('add', filePath); + spy.should.not.have.been.calledWith('addDir', matchingDir); + spy.should.not.have.been.calledWith('add', matchingFile); + spy.should.not.have.been.calledWith('add', matchingFile2); + await delay(); + await write(filePath, Date.now()); + + await waitFor([spy.withArgs('change', filePath)]); + spy.should.have.been.calledWith('change', filePath); }); - it('should not prematurely filter dirs against complex globstar patterns', function(done) { - var spy = sinon.spy(); - var deepFile = getFixturePath('subdir/subsub/subsubsub/a.txt'); - var watchPath = getGlobPath('../../test-*/' + subdir + '/**/subsubsub/*.txt'); + it('should not prematurely filter dirs against complex globstar patterns', async () => { + const deepFile = getFixturePath('subdir/subsub/subsubsub/a.txt'); + const watchPath = getGlobPath('../../test-*/' + subdir + '/**/subsubsub/*.txt'); fs.mkdirSync(getFixturePath('subdir'), PERM_ARR); fs.mkdirSync(getFixturePath('subdir/subsub'), PERM_ARR); fs.mkdirSync(getFixturePath('subdir/subsub/subsubsub'), PERM_ARR); fs.writeFileSync(deepFile, 'b'); - watcher = chokidar.watch(watchPath, options) - .on('all', spy) - .on('ready', function() { - w(fs.writeFile.bind(fs, deepFile, Date.now(), simpleCb))(); - waitFor([[spy, 2]], function() { - spy.should.have.been.calledWith('add', deepFile); - spy.should.have.been.calledWith('change', deepFile); - done(); - }); - }); + watcher = chokidar.watch(watchPath, options); + const spy = await aspy(watcher, 'all'); + + await delay(); + await write(deepFile, Date.now()); + await waitFor([[spy, 2]]); + spy.should.have.been.calledWith('add', deepFile); + spy.should.have.been.calledWith('change', deepFile); }); - it('should emit matching dir events', function(done) { - var spy = sinon.spy(); + it('should emit matching dir events', async () => { // test with and without globstar matches - var watchPaths = [getGlobPath('*'), getGlobPath('subdir/subsub/**/*')]; - var deepDir = getFixturePath('subdir/subsub/subsubsub'); - var deepFile = sysPath.join(deepDir, 'a.txt'); + const watchPaths = [getGlobPath('*'), getGlobPath('subdir/subsub/**/*')]; + const deepDir = getFixturePath('subdir/subsub/subsubsub'); + const deepFile = sysPath.join(deepDir, 'a.txt'); fs.mkdirSync(getFixturePath('subdir'), PERM_ARR); fs.mkdirSync(getFixturePath('subdir/subsub'), PERM_ARR); - watcher = chokidar.watch(watchPaths, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('addDir', getFixturePath('subdir')); - spy.withArgs('addDir').should.have.been.calledOnce; - fs.mkdirSync(deepDir, PERM_ARR); - fs.writeFileSync(deepFile, Date.now()); - waitFor([[spy.withArgs('addDir'), 2], spy.withArgs('add', deepFile)], function() { - if (win32Polling) return done(); - spy.should.have.been.calledWith('addDir', deepDir); - fs.unlinkSync(deepFile); - fs.rmdirSync(deepDir); - waitFor([spy.withArgs('unlinkDir')], function() { - spy.should.have.been.calledWith('unlinkDir', deepDir); - done(); - }); - }); - }); + watcher = chokidar.watch(watchPaths, options); + const spy = await aspy(watcher, 'all'); + + spy.should.have.been.calledWith('addDir', getFixturePath('subdir')); + spy.withArgs('addDir').should.have.been.calledOnce; + fs.mkdirSync(deepDir, PERM_ARR); + fs.writeFileSync(deepFile, Date.now()); + + await waitFor([[spy.withArgs('addDir'), 2], spy.withArgs('add', deepFile)]); + if (win32Polling) return true; + + spy.should.have.been.calledWith('addDir', deepDir); + fs.unlinkSync(deepFile); + fs.rmdirSync(deepDir); + + await waitFor([spy.withArgs('unlinkDir')]); + spy.should.have.been.calledWith('unlinkDir', deepDir); }); - it('should correctly handle glob with braces', function(done) { - var spy = sinon.spy(); - var watchPath = upath.normalizeSafe(getGlobPath('{subdir/*,subdir1/subsub1}/subsubsub/*.txt')); - var deepFileA = getFixturePath('subdir/subsub/subsubsub/a.txt'); - var deepFileB = getFixturePath('subdir1/subsub1/subsubsub/a.txt'); + it('should correctly handle glob with braces', async () => { + const watchPath = upath.normalizeSafe(getGlobPath('{subdir/*,subdir1/subsub1}/subsubsub/*.txt')); + const deepFileA = getFixturePath('subdir/subsub/subsubsub/a.txt'); + const deepFileB = getFixturePath('subdir1/subsub1/subsubsub/a.txt'); fs.mkdirSync(getFixturePath('subdir'), PERM_ARR); fs.mkdirSync(getFixturePath('subdir/subsub'), PERM_ARR); fs.mkdirSync(getFixturePath('subdir/subsub/subsubsub'), PERM_ARR); @@ -1062,248 +1029,207 @@ function runTests(baseopts) { fs.mkdirSync(getFixturePath('subdir1/subsub1/subsubsub'), PERM_ARR); fs.writeFileSync(deepFileA, Date.now()); fs.writeFileSync(deepFileB, Date.now()); - watcher = chokidar.watch(watchPath, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', deepFileA); - spy.should.have.been.calledWith('add', deepFileB); - fs.appendFileSync(deepFileA, Date.now()); - fs.appendFileSync(deepFileB, Date.now()); - waitFor([[spy, 4]], function() { - spy.should.have.been.calledWith('change', deepFileA); - spy.should.have.been.calledWith('change', deepFileB); - done(); - }); - }); + watcher = chokidar.watch(watchPath, options); + const spy = await aspy(watcher, 'all'); + + spy.should.have.been.calledWith('add', deepFileA); + spy.should.have.been.calledWith('add', deepFileB); + fs.appendFileSync(deepFileA, Date.now()); + fs.appendFileSync(deepFileB, Date.now()); + + await waitFor([[spy, 4]]); + spy.should.have.been.calledWith('change', deepFileA); + spy.should.have.been.calledWith('change', deepFileB); }); }); describe('watch symlinks', function() { - if (os === 'win32') return; + if (os === 'win32') return true; before(closeWatchers); - var linkedDir; - beforeEach(function(done) { + + let linkedDir; + beforeEach(async () => { linkedDir = sysPath.resolve(fixturesPath, '..', subdir + '-link'); - fs.symlink(fixturesPath, linkedDir, function() { - fs.mkdir(getFixturePath('subdir'), PERM_ARR, function() { - fs.writeFile(getFixturePath('subdir/add.txt'), 'b', done); - }); - }); + await fs.promises.symlink(fixturesPath, linkedDir); + await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await write(getFixturePath('subdir/add.txt'), 'b'); + return true; }); - afterEach(function(done) { - fs.unlink(linkedDir, done); + afterEach(async () => { + await fs.promises.unlink(linkedDir); + return true; }); - it('should watch symlinked dirs', function(done) { - var dirSpy = sinon.spy(function dirSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); + + it('should watch symlinked dirs', async () => { + const dirSpy = sinon.spy(function dirSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); watcher = chokidar.watch(linkedDir, options) .on('addDir', dirSpy) - .on('add', addSpy) - .on('ready', function() { - dirSpy.should.have.been.calledWith(linkedDir); - addSpy.should.have.been.calledWith(sysPath.join(linkedDir, 'change.txt')); - addSpy.should.have.been.calledWith(sysPath.join(linkedDir, 'unlink.txt')); - done(); - }); + .on('add', addSpy); + await aspy(watcher); + + dirSpy.should.have.been.calledWith(linkedDir); + addSpy.should.have.been.calledWith(sysPath.join(linkedDir, 'change.txt')); + addSpy.should.have.been.calledWith(sysPath.join(linkedDir, 'unlink.txt')); }); - it('should watch symlinked files', function(done) { - var spy = sinon.spy(); - var changePath = getFixturePath('change.txt'); - var linkPath = getFixturePath('link.txt'); + it('should watch symlinked files', async () => { + const changePath = getFixturePath('change.txt'); + const linkPath = getFixturePath('link.txt'); fs.symlinkSync(changePath, linkPath); - watcher = chokidar.watch(linkPath, options) - .on('all', spy) - .on('ready', function() { - fs.writeFile(changePath, Date.now(), simpleCb); - waitFor([spy.withArgs('change')], function() { - spy.should.have.been.calledWith('add', linkPath); - spy.should.have.been.calledWith('change', linkPath); - done(); - }); - }); - }); - it('should follow symlinked files within a normal dir', function(done) { - var spy = sinon.spy(); - var changePath = getFixturePath('change.txt'); - var linkPath = getFixturePath('subdir/link.txt'); + watcher = chokidar.watch(linkPath, options); + const spy = await aspy(watcher, 'all'); + + await write(changePath, Date.now()); + await waitFor([spy.withArgs('change')]); + spy.should.have.been.calledWith('add', linkPath); + spy.should.have.been.calledWith('change', linkPath); + }); + it('should follow symlinked files within a normal dir', async () => { + const changePath = getFixturePath('change.txt'); + const linkPath = getFixturePath('subdir/link.txt'); fs.symlinkSync(changePath, linkPath); - watcher = chokidar.watch(getFixturePath('subdir'), options) - .on('all', spy) - .on('ready', function() { - fs.writeFile(changePath, Date.now(), simpleCb); - waitFor([spy.withArgs('change', linkPath)], function() { - spy.should.have.been.calledWith('add', linkPath); - spy.should.have.been.calledWith('change', linkPath); - done(); - }); - }); - }); - it('should watch paths with a symlinked parent', function(done) { - var spy = sinon.spy(); - var testDir = sysPath.join(linkedDir, 'subdir'); - var testFile = sysPath.join(testDir, 'add.txt'); - watcher = chokidar.watch(testDir, options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('addDir', testDir); - spy.should.have.been.calledWith('add', testFile); - fs.writeFile(getFixturePath('subdir/add.txt'), Date.now(), simpleCb); - waitFor([spy.withArgs('change')], function() { - spy.should.have.been.calledWith('change', testFile); - done(); - }); - }); - }); - it('should not recurse indefinitely on circular symlinks', function(done) { - fs.symlinkSync(fixturesPath, getFixturePath('subdir/circular')); - stdWatcher().on('ready', done); + watcher = chokidar.watch(getFixturePath('subdir'), options); + const spy = await aspy(watcher, 'all'); + + await write(changePath, Date.now()); + await waitFor([spy.withArgs('change', linkPath)]); + spy.should.have.been.calledWith('add', linkPath); + spy.should.have.been.calledWith('change', linkPath); + }); + it('should watch paths with a symlinked parent', async () => { + const testDir = sysPath.join(linkedDir, 'subdir'); + const testFile = sysPath.join(testDir, 'add.txt'); + watcher = chokidar.watch(testDir, options); + const spy = await aspy(watcher, 'all'); + + spy.should.have.been.calledWith('addDir', testDir); + spy.should.have.been.calledWith('add', testFile); + await write(getFixturePath('subdir/add.txt'), Date.now()); + await waitFor([spy.withArgs('change')]); + spy.should.have.been.calledWith('change', testFile); + }); + it('should not recurse indefinitely on circular symlinks', async () => { + await fs.promises.symlink(fixturesPath, getFixturePath('subdir/circular')); + watcher = stdWatcher(); + await aspy(watcher); + // return true; }); - it('should recognize changes following symlinked dirs', function(done) { - var spy = sinon.spy(function changeSpy(){}); - watcher = chokidar.watch(linkedDir, options) - .on('change', spy) - .on('ready', function() { - var linkedFilePath = sysPath.join(linkedDir, 'change.txt'); - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - waitFor([spy.withArgs(linkedFilePath)], function() { - spy.should.have.been.calledWith(linkedFilePath); - done(); - }); - }); + it('should recognize changes following symlinked dirs', async () => { + const linkedFilePath = sysPath.join(linkedDir, 'change.txt'); + watcher = chokidar.watch(linkedDir, options); + const spy = sinon.spy(); + const wa = spy.withArgs(linkedFilePath); + await aspy(watcher, 'change', spy); + await write(getFixturePath('change.txt'), Date.now()); + await waitFor([wa]); + spy.should.have.been.calledWith(linkedFilePath); }); - it('should follow newly created symlinks', function(done) { + it('should follow newly created symlinks', async () => { options.ignoreInitial = true; - var spy = sinon.spy(); - stdWatcher() - .on('all', spy) - .on('ready', function() { - w(fs.symlink.bind(fs, getFixturePath('subdir'), getFixturePath('link'), simpleCb))(); - waitFor([ - spy.withArgs('add', getFixturePath('link/add.txt')), - spy.withArgs('addDir', getFixturePath('link')) - ], function() { - spy.should.have.been.calledWith('addDir', getFixturePath('link')); - spy.should.have.been.calledWith('add', getFixturePath('link/add.txt')); - done(); - }); - }); - }); - it('should watch symlinks as files when followSymlinks:false', function(done) { + stdWatcher(); + const spy = await aspy(watcher, 'all'); + await delay(); + await fs.promises.symlink(getFixturePath('subdir'), getFixturePath('link')); + await waitFor([ + spy.withArgs('add', getFixturePath('link/add.txt')), + spy.withArgs('addDir', getFixturePath('link')) + ]); + spy.should.have.been.calledWith('addDir', getFixturePath('link')); + spy.should.have.been.calledWith('add', getFixturePath('link/add.txt')); + }); + it('should watch symlinks as files when followSymlinks:false', async () => { options.followSymlinks = false; - var spy = sinon.spy(); - watcher = chokidar.watch(linkedDir, options) - .on('all', spy) - .on('ready', function() { - spy.should.not.have.been.calledWith('addDir'); - spy.should.have.been.calledWith('add', linkedDir); - spy.should.have.been.calledOnce; - done(); - }); + watcher = chokidar.watch(linkedDir, options); + const spy = await aspy(watcher, 'all'); + spy.should.not.have.been.calledWith('addDir'); + spy.should.have.been.calledWith('add', linkedDir); + spy.should.have.been.calledOnce; }); - it('should watch symlinks within a watched dir as files when followSymlinks:false', function(done) { + it('should watch symlinks within a watched dir as files when followSymlinks:false', async () => { options.followSymlinks = false; - var spy = sinon.spy(); - var linkPath = getFixturePath('link'); + const linkPath = getFixturePath('link'); fs.symlinkSync(getFixturePath('subdir'), linkPath); - stdWatcher() - .on('all', spy) - .on('ready', function() { - w(function() { - fs.writeFileSync(getFixturePath('subdir/add.txt'), Date.now()); - fs.unlinkSync(linkPath); - fs.symlinkSync(getFixturePath('subdir/add.txt'), linkPath); - }, options.usePolling ? 1200 : 300)(); - waitFor([spy.withArgs('change', linkPath)], function() { - spy.should.not.have.been.calledWith('addDir', linkPath); - spy.should.not.have.been.calledWith('add', getFixturePath('link/add.txt')); - spy.should.have.been.calledWith('add', linkPath); - spy.should.have.been.calledWith('change', linkPath); - done(); - }); - }); - }); - it('should not reuse watcher when following a symlink to elsewhere', function(done) { - var spy = sinon.spy(); - var linkedPath = getFixturePath('outside'); - var linkedFilePath = sysPath.join(linkedPath, 'text.txt'); - var linkPath = getFixturePath('subdir/subsub'); + const spy = await aspy(stdWatcher(), 'all'); + + // await delay(); + setTimeout(() => { + fs.writeFileSync(getFixturePath('subdir/add.txt'), Date.now()); + fs.unlinkSync(linkPath); + fs.symlinkSync(getFixturePath('subdir/add.txt'), linkPath); + }, options.usePolling ? 1200 : 300); + + await waitFor([spy.withArgs('change', linkPath)]); + spy.should.not.have.been.calledWith('addDir', linkPath); + spy.should.not.have.been.calledWith('add', getFixturePath('link/add.txt')); + spy.should.have.been.calledWith('add', linkPath); + spy.should.have.been.calledWith('change', linkPath); + }); + it('should not reuse watcher when following a symlink to elsewhere', async () => { + const linkedPath = getFixturePath('outside'); + const linkedFilePath = sysPath.join(linkedPath, 'text.txt'); + const linkPath = getFixturePath('subdir/subsub'); fs.mkdirSync(linkedPath, PERM_ARR); fs.writeFileSync(linkedFilePath, 'b'); fs.symlinkSync(linkedPath, linkPath); - watcher2 = chokidar.watch(getFixturePath('subdir'), options) - .on('ready', w(function() { - var watchedPath = getFixturePath('subdir/subsub/text.txt'); - watcher = chokidar.watch(watchedPath, options) - .on('all', spy) - .on('ready', w(function() { - fs.writeFile(linkedFilePath, Date.now(), simpleCb); - waitFor([spy.withArgs('change')], function() { - spy.should.have.been.calledWith('change', watchedPath); - done(); - }); - })); - }, options.usePolling ? 900 : undefined)); - }); - it('should properly match glob patterns that include a symlinked dir', function(done) { - var dirSpy = sinon.spy(function dirSpy(){}); - var addSpy = sinon.spy(function addSpy(){}); + watcher2 = chokidar.watch(getFixturePath('subdir'), options); + await aspy(watcher2); + + await delay(options.usePolling ? 900 : undefined); + const watchedPath = getFixturePath('subdir/subsub/text.txt'); + watcher = chokidar.watch(watchedPath, options); + const spy = await aspy(watcher, 'all'); + + await delay(); + await write(linkedFilePath, Date.now()); + await waitFor([spy.withArgs('change')]); + spy.should.have.been.calledWith('change', watchedPath); + }); + it('should properly match glob patterns that include a symlinked dir', async () => { + const dirSpy = sinon.spy(function dirSpy(){}); + const addSpy = sinon.spy(function addSpy(){}); // test with relative path to ensure proper resolution - var watchDir = upath.relative(process.cwd(), linkedDir); + const watchDir = upath.relative(process.cwd(), linkedDir); watcher = chokidar.watch(upath.join(watchDir, '**/*'), options) .on('addDir', dirSpy) - .on('add', addSpy) - .on('ready', function() { - // only the children are matched by the glob pattern, not the link itself - addSpy.should.have.been.calledWith(sysPath.join(watchDir, 'change.txt')); - addSpy.should.have.been.calledThrice; // also unlink.txt & subdir/add.txt - dirSpy.should.have.been.calledWith(sysPath.join(watchDir, 'subdir')); - fs.writeFile(sysPath.join(watchDir, 'add.txt'), simpleCb); - waitFor([[addSpy, 4]], function() { - addSpy.should.have.been.calledWith(sysPath.join(watchDir, 'add.txt')); - done(); - }); - }); + .on('add', addSpy); + await aspy(watcher); + // only the children are matched by the glob pattern, not the link itself + addSpy.should.have.been.calledThrice; // also unlink.txt & subdir/add.txt + addSpy.should.have.been.calledWith(sysPath.join(watchDir, 'change.txt')); + dirSpy.should.have.been.calledWith(sysPath.join(watchDir, 'subdir')); + await write(sysPath.join(watchDir, 'add.txt')); + await waitFor([[addSpy, 4]]); + addSpy.should.have.been.calledWith(sysPath.join(watchDir, 'add.txt')); }); }); describe('watch arrays of paths/globs', function() { before(closeWatchers); - it('should watch all paths in an array', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('change.txt'); - var testDir = getFixturePath('subdir'); + it('should watch all paths in an array', async () => { + const testPath = getFixturePath('change.txt'); + const testDir = getFixturePath('subdir'); fs.mkdirSync(testDir); - watcher = chokidar.watch([testDir, testPath], options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', testPath); - spy.should.have.been.calledWith('addDir', testDir); - spy.should.not.have.been.calledWith('add', getFixturePath('unlink.txt')); - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy.withArgs('change')], function() { - spy.should.have.been.calledWith('change', testPath); - done(); - }); - }); + watcher = chokidar.watch([testDir, testPath], options); + const spy = await aspy(watcher, 'all'); + spy.should.have.been.calledWith('add', testPath); + spy.should.have.been.calledWith('addDir', testDir); + spy.should.not.have.been.calledWith('add', getFixturePath('unlink.txt')); + await write(testPath, Date.now()); + await waitFor([spy.withArgs('change')]); + spy.should.have.been.calledWith('change', testPath); }); - it('should accommodate nested arrays in input', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('change.txt'); - var testDir = getFixturePath('subdir'); - fs.mkdir(testDir, function() { - watcher = chokidar.watch([[testDir], [testPath]], options) - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', testPath); - spy.should.have.been.calledWith('addDir', testDir); - spy.should.not.have.been.calledWith('add', getFixturePath('unlink.txt')); - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy.withArgs('change')], function() { - spy.should.have.been.calledWith('change', testPath); - done(); - }); - }); - }); + it('should accommodate nested arrays in input', async () => { + const testPath = getFixturePath('change.txt'); + const testDir = getFixturePath('subdir'); + await fs.promises.mkdir(testDir); + watcher = chokidar.watch([[testDir], [testPath]], options); + const spy = await aspy(watcher, 'all'); + spy.should.have.been.calledWith('add', testPath); + spy.should.have.been.calledWith('addDir', testDir); + spy.should.not.have.been.calledWith('add', getFixturePath('unlink.txt')); + await write(testPath, Date.now()); + await waitFor([spy.withArgs('change')]); + spy.should.have.been.calledWith('change', testPath); }); - it('should throw if provided any non-string paths', function() { + it('should throw if provided any non-string paths', () => { expect(chokidar.watch.bind(null, [[fixturesPath], /notastring/])) .to.throw(TypeError, /non-string/i); }); @@ -1313,293 +1239,221 @@ function runTests(baseopts) { describe('ignoreInitial', function() { describe('false', function() { beforeEach(function() { options.ignoreInitial = false; }); - it('should emit `add` events for preexisting files', function(done) { - var spy = sinon.spy(); - watcher = chokidar.watch(fixturesPath, options) - .on('add', spy) - .on('ready', function() { - spy.should.have.been.calledTwice; - done(); - }); + it('should emit `add` events for preexisting files', async () => { + watcher = chokidar.watch(fixturesPath, options); + const spy = await aspy(watcher, 'add'); + spy.should.have.been.calledTwice; }); - it('should emit `addDir` event for watched dir', function(done) { - var spy = sinon.spy(); - watcher = chokidar.watch(fixturesPath, options) - .on('addDir', spy) - .on('ready', function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith(fixturesPath); - done(); - }); + it('should emit `addDir` event for watched dir', async () => { + watcher = chokidar.watch(fixturesPath, options); + const spy = await aspy(watcher, 'addDir'); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith(fixturesPath); }); - it('should emit `addDir` events for preexisting dirs', function(done) { - var spy = sinon.spy(); - fs.mkdir(getFixturePath('subdir'), PERM_ARR, function() { - fs.mkdir(getFixturePath('subdir/subsub'), PERM_ARR, function() { - watcher = chokidar.watch(fixturesPath, options) - .on('addDir', spy) - .on('ready', function() { - spy.should.have.been.calledWith(fixturesPath); - spy.should.have.been.calledWith(getFixturePath('subdir')); - spy.should.have.been.calledWith(getFixturePath('subdir/subsub')); - spy.should.have.been.calledThrice; - done(); - }); - }); - }); + it('should emit `addDir` events for preexisting dirs', async () => { + await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await fs.promises.mkdir(getFixturePath('subdir/subsub'), PERM_ARR); + watcher = chokidar.watch(fixturesPath, options); + const spy = await aspy(watcher, 'addDir'); + spy.should.have.been.calledWith(fixturesPath); + spy.should.have.been.calledWith(getFixturePath('subdir')); + spy.should.have.been.calledWith(getFixturePath('subdir/subsub')); + spy.should.have.been.calledThrice; }); }); describe('true', function() { beforeEach(function() { options.ignoreInitial = true; }); - it('should ignore inital add events', function(done) { - var spy = sinon.spy(); - stdWatcher() - .on('add', spy) - .on('ready', w(function() { - spy.should.not.have.been.called; - done(); - })); + it('should ignore inital add events', async () => { + stdWatcher(); + const spy = await aspy(watcher, 'add'); + await delay(); + spy.should.not.have.been.called; }); - it('should ignore add events on a subsequent .add()', function(done) { - var spy = sinon.spy(); - watcher = chokidar.watch(getFixturePath('subdir'), options) - .on('add', spy) - .on('ready', function() { - watcher.add(fixturesPath); - w(function() { - spy.should.not.have.been.called; - done(); - }, 1000)(); - }); + it('should ignore add events on a subsequent .add()', async () => { + watcher = chokidar.watch(getFixturePath('subdir'), options); + const spy = await aspy(watcher, 'add'); + watcher.add(fixturesPath); + await delay(1000); + spy.should.not.have.been.called; }); - it('should notice when a file appears in an empty directory', function(done) { - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); - var testPath = getFixturePath('subdir/add.txt'); - stdWatcher() - .on('add', spy) - .on('ready', function() { - spy.should.not.have.been.called; - fs.mkdir(testDir, PERM_ARR, function() { - fs.writeFile(testPath, Date.now(), simpleCb); - }); - waitFor([spy], function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith(testPath); - done(); - }); - }); + it('should notice when a file appears in an empty directory', async () => { + const testDir = getFixturePath('subdir'); + const testPath = getFixturePath('subdir/add.txt'); + const spy = await aspy(stdWatcher(), 'add'); + spy.should.not.have.been.called; + await fs.promises.mkdir(testDir, PERM_ARR); + await write(testPath, Date.now()); + await waitFor([spy]); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith(testPath); }); - it('should emit a change on a preexisting file as a change', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('change.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.not.have.been.called; - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy.withArgs('change', testPath)], function() { - spy.should.have.been.calledWith('change', testPath); - spy.should.not.have.been.calledWith('add'); - done(); - }); - }); + it('should emit a change on a preexisting file as a change', async () => { + const testPath = getFixturePath('change.txt'); + const spy = await aspy(stdWatcher(), 'all'); + spy.should.not.have.been.called; + await write(testPath, Date.now()); + await waitFor([spy.withArgs('change', testPath)]); + spy.should.have.been.calledWith('change', testPath); + spy.should.not.have.been.calledWith('add'); }); - it('should not emit for preexisting dirs when depth is 0', function(done) { - options.depth = 0 - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - fs.mkdir(getFixturePath('subdir'), PERM_ARR, w(function() { - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, Date.now(), simpleCb); - waitFor([spy], w(function() { - spy.should.have.been.calledWith('add', testPath); - spy.should.not.have.been.calledWith('addDir'); - done(); - }, win32Polling010 ? 1000 : 200)); - }); - }, win32Polling010 ? 1000 : 200)); + it('should not emit for preexisting dirs when depth is 0', async () => { + options.depth = 0; + const testPath = getFixturePath('add.txt'); + await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + + await delay(win32Polling010 ? 1000 : 200); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, Date.now()); + await waitFor([spy]); + + await delay(win32Polling010 ? 1000 : 200); + spy.should.have.been.calledWith('add', testPath); + spy.should.not.have.been.calledWith('addDir'); }); }); }); describe('ignored', function() { - it('should check ignore after stating', function(done) { + it('should check ignore after stating', async () => { options.ignored = function(path, stats) { if (upath.normalizeSafe(path) === upath.normalizeSafe(testDir) || !stats) return false; return stats.isDirectory(); }; - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); + const testDir = getFixturePath('subdir'); fs.mkdirSync(testDir, PERM_ARR); fs.writeFileSync(sysPath.join(testDir, 'add.txt'), ''); fs.mkdirSync(sysPath.join(testDir, 'subsub'), PERM_ARR); fs.writeFileSync(sysPath.join(testDir, 'subsub', 'ab.txt'), ''); - watcher = chokidar.watch(testDir, options) - .on('add', spy) - .on('ready', function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith(sysPath.join(testDir, 'add.txt')); - done(); - }); + watcher = chokidar.watch(testDir, options); + const spy = await aspy(watcher, 'add'); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith(sysPath.join(testDir, 'add.txt')); }); - it('should not choke on an ignored watch path', function(done) { + it('should not choke on an ignored watch path', async () => { options.ignored = function() { return true; }; - stdWatcher().on('ready', done); + await aspy(stdWatcher()); }); - it('should ignore the contents of ignored dirs', function(done) { - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); - var testFile = sysPath.join(testDir, 'add.txt'); + it('should ignore the contents of ignored dirs', async () => { + const testDir = getFixturePath('subdir'); + const testFile = sysPath.join(testDir, 'add.txt'); options.ignored = testDir; fs.mkdirSync(testDir, PERM_ARR); fs.writeFileSync(testFile, 'b'); - watcher = chokidar.watch(fixturesPath, options) - .on('all', spy) - .on('ready', w(function() { - fs.writeFile(testFile, Date.now(), w(function() { - spy.should.not.have.been.calledWith('addDir', testDir); - spy.should.not.have.been.calledWith('add', testFile); - spy.should.not.have.been.calledWith('change', testFile); - done(); - }, 300)); - })); + watcher = chokidar.watch(fixturesPath, options); + const spy = await aspy(watcher, 'all'); + + await delay(); + await write(testFile, Date.now()); + + await delay(300); + spy.should.not.have.been.calledWith('addDir', testDir); + spy.should.not.have.been.calledWith('add', testFile); + spy.should.not.have.been.calledWith('change', testFile); }); - it('should allow regex/fn ignores', function(done) { + it('should allow regex/fn ignores', async () => { options.cwd = fixturesPath; options.ignored = /add/; - var spy = sinon.spy(); + fs.writeFileSync(getFixturePath('add.txt'), 'b'); - watcher = chokidar.watch(fixturesPath, options) - .on('all', spy) - .on('ready', function() { - w(function() { - fs.writeFile(getFixturePath('add.txt'), Date.now(), simpleCb); - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - })(); - waitFor([spy.withArgs('change', 'change.txt')], function() { - spy.should.not.have.been.calledWith('add', 'add.txt'); - spy.should.not.have.been.calledWith('change', 'add.txt'); - spy.should.have.been.calledWith('add', 'change.txt'); - spy.should.have.been.calledWith('change', 'change.txt'); - done(); - }); - }); + watcher = chokidar.watch(fixturesPath, options); + const spy = await aspy(watcher, 'all'); + + await delay(); + await write(getFixturePath('add.txt'), Date.now()); + await write(getFixturePath('change.txt'), Date.now()); + + await waitFor([spy.withArgs('change', 'change.txt')]); + spy.should.not.have.been.calledWith('add', 'add.txt'); + spy.should.not.have.been.calledWith('change', 'add.txt'); + spy.should.have.been.calledWith('add', 'change.txt'); + spy.should.have.been.calledWith('change', 'change.txt'); }); }); describe('depth', function() { - beforeEach(function(done) { - var i = 0, r = function() { i++ && w(done, options.useFsEvents && 200)(); }; - fs.mkdir(getFixturePath('subdir'), PERM_ARR, function() { - fs.writeFile(getFixturePath('subdir/add.txt'), 'b', r); - fs.mkdir(getFixturePath('subdir/subsub'), PERM_ARR, function() { - fs.writeFile(getFixturePath('subdir/subsub/ab.txt'), 'b', r); - }); - }); + beforeEach(async () => { + await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await fs.promises.writeFile(getFixturePath('subdir/add.txt'), 'b'); + await delay(options.useFsEvents && 200); + await fs.promises.mkdir(getFixturePath('subdir/subsub'), PERM_ARR); + await fs.promises.writeFile(getFixturePath('subdir/subsub/ab.txt'), 'b'); + await delay(options.useFsEvents && 200); }); - it('should not recurse if depth is 0', function(done) { + it('should not recurse if depth is 0', async () => { options.depth = 0; - var spy = sinon.spy(); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(getFixturePath('subdir/add.txt'), Date.now(), simpleCb); - waitFor([[spy, 4]], function() { - spy.should.have.been.calledWith('addDir', fixturesPath); - spy.should.have.been.calledWith('addDir', getFixturePath('subdir')); - spy.should.have.been.calledWith('add', getFixturePath('change.txt')); - spy.should.have.been.calledWith('add', getFixturePath('unlink.txt')); - spy.should.not.have.been.calledWith('change'); - if (!osXFsWatch) spy.callCount.should.equal(4); - done(); - }); - }); + stdWatcher(); + const spy = await aspy(watcher, 'all'); + await write(getFixturePath('subdir/add.txt'), Date.now()); + await waitFor([[spy, 4]]); + spy.should.have.been.calledWith('addDir', fixturesPath); + spy.should.have.been.calledWith('addDir', getFixturePath('subdir')); + spy.should.have.been.calledWith('add', getFixturePath('change.txt')); + spy.should.have.been.calledWith('add', getFixturePath('unlink.txt')); + spy.should.not.have.been.calledWith('change'); + if (!osXFsWatch) spy.callCount.should.equal(4); }); - it('should recurse to specified depth', function(done) { + it('should recurse to specified depth', async () => { options.depth = 1; - var spy = sinon.spy(); - var addPath = getFixturePath('subdir/add.txt'); - var changePath = getFixturePath('change.txt'); - var ignoredPath = getFixturePath('subdir/subsub/ab.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - w(function() { - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - fs.writeFile(addPath, Date.now(), simpleCb); - fs.writeFile(ignoredPath, Date.now(), simpleCb); - })(); - waitFor([spy.withArgs('change', addPath), spy.withArgs('change', changePath)], function() { - spy.should.have.been.calledWith('addDir', getFixturePath('subdir/subsub')); - spy.should.have.been.calledWith('change', changePath); - spy.should.have.been.calledWith('change', addPath); - spy.should.not.have.been.calledWith('add', ignoredPath); - spy.should.not.have.been.calledWith('change', ignoredPath); - if (!osXFsWatch) spy.callCount.should.equal(8); - done(); - }); - }); + const addPath = getFixturePath('subdir/add.txt'); + const changePath = getFixturePath('change.txt'); + const ignoredPath = getFixturePath('subdir/subsub/ab.txt'); + stdWatcher(); + const spy = await aspy(watcher, 'all'); + await delay(); + await write(getFixturePath('change.txt'), Date.now()); + await write(addPath, Date.now()); + await write(ignoredPath, Date.now()); + await waitFor([spy.withArgs('change', addPath), spy.withArgs('change', changePath)]); + spy.should.have.been.calledWith('addDir', getFixturePath('subdir/subsub')); + spy.should.have.been.calledWith('change', changePath); + spy.should.have.been.calledWith('change', addPath); + spy.should.not.have.been.calledWith('add', ignoredPath); + spy.should.not.have.been.calledWith('change', ignoredPath); + if (!osXFsWatch) spy.callCount.should.equal(8); }); - it('should respect depth setting when following symlinks', function(done) { - if (os === 'win32') return done(); // skip on windows + it('should respect depth setting when following symlinks', async () => { + if (os === 'win32') return true; // skip on windows options.depth = 1; - var spy = sinon.spy(); - fs.symlink(getFixturePath('subdir'), getFixturePath('link'), w(function() { - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('addDir', getFixturePath('link')); - spy.should.have.been.calledWith('addDir', getFixturePath('link/subsub')); - spy.should.have.been.calledWith('add', getFixturePath('link/add.txt')); - spy.should.not.have.been.calledWith('add', getFixturePath('link/subsub/ab.txt')); - done(); - }); - })); + await fs.promises.symlink(getFixturePath('subdir'), getFixturePath('link')); + await delay(); + stdWatcher(); + const spy = await aspy(watcher, 'all'); + spy.should.have.been.calledWith('addDir', getFixturePath('link')); + spy.should.have.been.calledWith('addDir', getFixturePath('link/subsub')); + spy.should.have.been.calledWith('add', getFixturePath('link/add.txt')); + spy.should.not.have.been.calledWith('add', getFixturePath('link/subsub/ab.txt')); }); - it('should respect depth setting when following a new symlink', function(done) { - if (os === 'win32') return done(); // skip on windows + it('should respect depth setting when following a new symlink', async () => { + if (os === 'win32') return true; // skip on windows options.depth = 1; options.ignoreInitial = true; - var spy = sinon.spy(); - var linkPath = getFixturePath('link'); - var dirPath = getFixturePath('link/subsub'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.symlink(getFixturePath('subdir'), linkPath, simpleCb); - waitFor([[spy, 3], spy.withArgs('addDir', dirPath)], function() { - spy.should.have.been.calledWith('addDir', linkPath); - spy.should.have.been.calledWith('addDir', dirPath); - spy.should.have.been.calledWith('add', getFixturePath('link/add.txt')); - if (!osXFsWatch010) spy.should.have.been.calledThrice; - done(); - }); - }); + const linkPath = getFixturePath('link'); + const dirPath = getFixturePath('link/subsub'); + stdWatcher(); + const spy = await aspy(watcher, 'all'); + await fs.promises.symlink(getFixturePath('subdir'), linkPath); + await waitFor([[spy, 3], spy.withArgs('addDir', dirPath)]); + spy.should.have.been.calledWith('addDir', linkPath); + spy.should.have.been.calledWith('addDir', dirPath); + spy.should.have.been.calledWith('add', getFixturePath('link/add.txt')); + if (!osXFsWatch010) spy.should.have.been.calledThrice; }); - it('should correctly handle dir events when depth is 0', function(done) { + it('should correctly handle dir events when depth is 0', async () => { options.depth = 0; - var spy = sinon.spy(); - var addSpy = spy.withArgs('addDir'); - var unlinkSpy = spy.withArgs('unlinkDir'); - var subdir2 = getFixturePath('subdir2'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('addDir', fixturesPath); - spy.should.have.been.calledWith('addDir', getFixturePath('subdir')); - fs.mkdir(subdir2, PERM_ARR, simpleCb); - waitFor([[addSpy, 3]], function() { - addSpy.should.have.been.calledThrice; - if (win32Polling010) return done(); - fs.rmdir(subdir2, simpleCb); - waitFor([unlinkSpy], w(function() { - unlinkSpy.should.have.been.calledWith('unlinkDir', subdir2); - unlinkSpy.should.have.been.calledOnce; - done(); - })); - }); - }); + const subdir2 = getFixturePath('subdir2'); + const spy = await aspy(stdWatcher(), 'all'); + const addSpy = spy.withArgs('addDir'); + const unlinkSpy = spy.withArgs('unlinkDir'); + spy.should.have.been.calledWith('addDir', fixturesPath); + spy.should.have.been.calledWith('addDir', getFixturePath('subdir')); + await fs.promises.mkdir(subdir2, PERM_ARR); + await waitFor([[addSpy, 3]]); + addSpy.should.have.been.calledThrice; + if (win32Polling010) return true; + + await fs.promises.rmdir(subdir2); + await waitFor([unlinkSpy]); + await delay(); + unlinkSpy.should.have.been.calledWith('unlinkDir', subdir2); + unlinkSpy.should.have.been.calledOnce; }); }); describe('atomic', function() { @@ -1607,189 +1461,151 @@ function runTests(baseopts) { options.atomic = true; options.ignoreInitial = true; }); - it('should ignore vim/emacs/Sublime swapfiles', function(done) { - var spy = sinon.spy(); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(getFixturePath('.change.txt.swp'), 'a', simpleCb); // vim - fs.writeFile(getFixturePath('add.txt\~'), 'a', simpleCb); // vim/emacs - fs.writeFile(getFixturePath('.subl5f4.tmp'), 'a', simpleCb); // sublime - w(function() { - fs.writeFile(getFixturePath('.change.txt.swp'), 'c', simpleCb); - fs.writeFile(getFixturePath('add.txt\~'), 'c', simpleCb); - fs.writeFile(getFixturePath('.subl5f4.tmp'), 'c', simpleCb); - w(function() { - fs.unlink(getFixturePath('.change.txt.swp'), simpleCb); - fs.unlink(getFixturePath('add.txt\~'), simpleCb); - fs.unlink(getFixturePath('.subl5f4.tmp'), simpleCb); - w(function() { - spy.should.not.have.been.called; - done(); - }, 300)(); - }, 300)(); - }, 300)(); - }); + it('should ignore vim/emacs/Sublime swapfiles', async () => { + const spy = await aspy(stdWatcher(), 'all'); + await write(getFixturePath('.change.txt.swp'), 'a'); // vim + await write(getFixturePath('add.txt\~'), 'a'); // vim/emacs + await write(getFixturePath('.subl5f4.tmp'), 'a'); // sublime + await delay(300); + await write(getFixturePath('.change.txt.swp'), 'c'); + await write(getFixturePath('add.txt\~'), 'c'); + await write(getFixturePath('.subl5f4.tmp'), 'c'); + await delay(300); + await fsunlink(getFixturePath('.change.txt.swp')); + await fsunlink(getFixturePath('add.txt\~')); + await fsunlink(getFixturePath('.subl5f4.tmp')); + await delay(300); + spy.should.not.have.been.called; }); - it('should ignore stale tilde files', function(done) { + it('should ignore stale tilde files', async () => { options.ignoreInitial = false; - var spy = sinon.spy(); - fs.writeFile(getFixturePath('old.txt~'), 'a', w(function() { - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.not.have.been.calledWith(getFixturePath('old.txt')); - spy.should.not.have.been.calledWith(getFixturePath('old.txt~')); - done(); - }); - })); + await fs.promises.writeFile(getFixturePath('old.txt~'), 'a'); + await delay(); + const spy = await aspy(stdWatcher(), 'all'); + spy.should.not.have.been.calledWith(getFixturePath('old.txt')); + spy.should.not.have.been.calledWith(getFixturePath('old.txt~')); }); }); describe('cwd', function() { - it('should emit relative paths based on cwd', function(done) { + it('should emit relative paths based on cwd', async () => { options.cwd = fixturesPath; - var spy = sinon.spy(); - watcher = chokidar.watch('**', options) - .on('all', spy) - .on('ready', function() { - fs.writeFile(getFixturePath('change.txt'), Date.now(), function() { - fs.unlink(getFixturePath('unlink.txt'), simpleCb); - }); - waitFor([spy.withArgs('unlink')], function() { - spy.should.have.been.calledWith('add', 'change.txt'); - spy.should.have.been.calledWith('add', 'unlink.txt'); - spy.should.have.been.calledWith('change', 'change.txt'); - spy.should.have.been.calledWith('unlink', 'unlink.txt'); - done(); - }); - }); + watcher = chokidar.watch('**', options); + const spy = await aspy(watcher, 'all'); + await write(getFixturePath('change.txt'), Date.now()); + await fsunlink(getFixturePath('unlink.txt')); + await waitFor([spy.withArgs('unlink')]); + spy.should.have.been.calledWith('add', 'change.txt'); + spy.should.have.been.calledWith('add', 'unlink.txt'); + spy.should.have.been.calledWith('change', 'change.txt'); + spy.should.have.been.calledWith('unlink', 'unlink.txt'); }); - it('should emit `addDir` with alwaysStat for renamed directory', function(done) { + it('should emit `addDir` with alwaysStat for renamed directory', async () => { options.cwd = fixturesPath; options.alwaysStat = true; options.ignoreInitial = true; - var spy = sinon.spy(); - var testDir = getFixturePath('subdir'); - var renamedDir = getFixturePath('subdir-renamed'); - fs.mkdir(testDir, PERM_ARR, function() { - watcher = chokidar.watch('.', options) - .on('ready', function() { - w(function() { - watcher.on('addDir', spy) - fs.rename(testDir, renamedDir, simpleCb); - }, 1000)(); - waitFor([spy], function() { - spy.should.have.been.calledOnce; - spy.should.have.been.calledWith('subdir-renamed'); - expect(spy.args[0][1]).to.be.ok; // stats - done(); - }); - }); - }); + const spy = sinon.spy(); + const testDir = getFixturePath('subdir'); + const renamedDir = getFixturePath('subdir-renamed'); + + await fs.promises.mkdir(testDir, PERM_ARR); + watcher = chokidar.watch('.', options); + + setTimeout(() => { + watcher.on('addDir', spy) + fs.promises.rename(testDir, renamedDir); + }, 1000); + + await waitFor([spy]); + spy.should.have.been.calledOnce; + spy.should.have.been.calledWith('subdir-renamed'); + expect(spy.args[0][1]).to.be.ok; // stats }); - it('should allow separate watchers to have different cwds', function(done) { + it('should allow separate watchers to have different cwds', async () => { options.cwd = fixturesPath; - var spy1 = sinon.spy(); - var spy2 = sinon.spy(); - var options2 = {}; - Object.keys(options).forEach(function(key) { options2[key] = options[key] }); + const options2 = {}; + Object.keys(options).forEach((key) => { + options2[key] = options[key]; + }); options2.cwd = getFixturePath('subdir'); - watcher = chokidar.watch(getGlobPath('**'), options) - .on('all', spy1) - .on('ready', w(function() { - watcher2 = chokidar.watch(fixturesPath, options2) - .on('all', spy2) - .on('ready', function() { - fs.writeFile(getFixturePath('change.txt'), Date.now(), function() { - fs.unlink(getFixturePath('unlink.txt'), simpleCb); - }); - waitFor([spy1.withArgs('unlink'), spy2.withArgs('unlink')], function() { - spy1.should.have.been.calledWith('change', 'change.txt'); - spy1.should.have.been.calledWith('unlink', 'unlink.txt'); - spy2.should.have.been.calledWith('add', sysPath.join('..', 'change.txt')); - spy2.should.have.been.calledWith('add', sysPath.join('..', 'unlink.txt')); - spy2.should.have.been.calledWith('change', sysPath.join('..', 'change.txt')); - spy2.should.have.been.calledWith('unlink', sysPath.join('..', 'unlink.txt')); - done(); - }); - }); - })); + watcher = chokidar.watch(getGlobPath('**'), options); + const spy1 = await aspy(watcher, 'all'); + + await delay(); + watcher2 = chokidar.watch(fixturesPath, options2); + const spy2 = await aspy(watcher2, 'all'); + + await fs.promises.writeFile(getFixturePath('change.txt'), Date.now()); + await fsunlink(getFixturePath('unlink.txt')); + await waitFor([spy1.withArgs('unlink'), spy2.withArgs('unlink')]); + spy1.should.have.been.calledWith('change', 'change.txt'); + spy1.should.have.been.calledWith('unlink', 'unlink.txt'); + spy2.should.have.been.calledWith('add', sysPath.join('..', 'change.txt')); + spy2.should.have.been.calledWith('add', sysPath.join('..', 'unlink.txt')); + spy2.should.have.been.calledWith('change', sysPath.join('..', 'change.txt')); + spy2.should.have.been.calledWith('unlink', sysPath.join('..', 'unlink.txt')); }); - it('should ignore files even with cwd', function(done) { + it('should ignore files even with cwd', async () => { options.cwd = fixturesPath; options.ignored = 'ignored-option.txt'; - var spy = sinon.spy(); - var files = [ + const files = [ '*.txt', '!ignored.txt' ]; fs.writeFileSync(getFixturePath('change.txt'), 'hello'); fs.writeFileSync(getFixturePath('ignored.txt'), 'ignored'); fs.writeFileSync(getFixturePath('ignored-option.txt'), 'ignored option'); - watcher = chokidar.watch(files, options) - .on('all', spy) - .on('ready', function() { - fs.writeFileSync(getFixturePath('ignored.txt'), Date.now()); - fs.writeFileSync(getFixturePath('ignored-option.txt'), Date.now()); - fs.unlink(getFixturePath('ignored.txt'), simpleCb); - fs.unlink(getFixturePath('ignored-option.txt'), simpleCb); - w(function() { - fs.writeFile(getFixturePath('change.txt'), 'change', simpleCb); - }, (win32Polling010) ? 1000 : undefined)(); - waitFor([spy.withArgs('change', 'change.txt')], function() { - spy.should.have.been.calledWith('add', 'change.txt'); - spy.should.not.have.been.calledWith('add', 'ignored.txt'); - spy.should.not.have.been.calledWith('add', 'ignored-option.txt'); - spy.should.not.have.been.calledWith('change', 'ignored.txt'); - spy.should.not.have.been.calledWith('change', 'ignored-option.txt'); - spy.should.not.have.been.calledWith('unlink', 'ignored.txt'); - spy.should.not.have.been.calledWith('unlink', 'ignored-option.txt'); - spy.should.have.been.calledWith('change', 'change.txt'); - done(); - }); - }); + watcher = chokidar.watch(files, options); + + const spy = await aspy(watcher, 'all'); + fs.writeFileSync(getFixturePath('ignored.txt'), Date.now()); + fs.writeFileSync(getFixturePath('ignored-option.txt'), Date.now()); + await fsunlink(getFixturePath('ignored.txt')); + await fsunlink(getFixturePath('ignored-option.txt')); + await delay(win32Polling010 ? 1000 : undefined); + await write(getFixturePath('change.txt'), 'change'); + await waitFor([spy.withArgs('change', 'change.txt')]); + spy.should.have.been.calledWith('add', 'change.txt'); + spy.should.not.have.been.calledWith('add', 'ignored.txt'); + spy.should.not.have.been.calledWith('add', 'ignored-option.txt'); + spy.should.not.have.been.calledWith('change', 'ignored.txt'); + spy.should.not.have.been.calledWith('change', 'ignored-option.txt'); + spy.should.not.have.been.calledWith('unlink', 'ignored.txt'); + spy.should.not.have.been.calledWith('unlink', 'ignored-option.txt'); + spy.should.have.been.calledWith('change', 'change.txt'); }); }); describe('ignorePermissionErrors', function() { - var filePath; - beforeEach(function(done) { + let filePath; + beforeEach(async () => { filePath = getFixturePath('add.txt'); - fs.writeFile(filePath, 'b', {mode: 128}, w(done)); + await write(filePath, 'b', {mode: 128}); + await delay(); }); describe('false', function() { beforeEach(function() { options.ignorePermissionErrors = false; }); - it('should not watch files without read permissions', function(done) { - if (os === 'win32') return done(); - var spy = sinon.spy(); - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.not.have.been.calledWith('add', filePath); - fs.writeFile(filePath, Date.now(), w(function() { - spy.should.not.have.been.calledWith('change', filePath); - done(); - }, 500)); - }); + it('should not watch files without read permissions', async () => { + if (os === 'win32') return true; + const spy = await aspy(stdWatcher(), 'all'); + spy.should.not.have.been.calledWith('add', filePath); + await write(filePath, Date.now()); + + await delay(500); + spy.should.not.have.been.calledWith('change', filePath); }); }); describe('true', function() { beforeEach(function() { options.ignorePermissionErrors = true; }); - it('should watch unreadable files if possible', function(done) { - var spy = sinon.spy(); - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add', filePath); - if (!options.useFsEvents) return done(); - fs.writeFile(filePath, Date.now(), simpleCb); - waitFor([spy.withArgs('change')], function() { - spy.should.have.been.calledWith('change', filePath); - done(); - }); - }); + it('should watch unreadable files if possible', async () => { + const spy = await aspy(stdWatcher(), 'all'); + spy.should.have.been.calledWith('add', filePath); + if (!options.useFsEvents) return true; + await write(filePath, Date.now()); + await waitFor([spy.withArgs('change')]); + spy.should.have.been.calledWith('change', filePath); }); - it('should not choke on non-existent files', function(done) { - chokidar.watch(getFixturePath('nope.txt'), options).on('ready', done); + it('should not choke on non-existent files', async () => { + watcher = chokidar.watch(getFixturePath('nope.txt'), options); + await aspy(watcher); }); }); }); @@ -1804,179 +1620,174 @@ function runTests(baseopts) { expect(watcher.options.awaitWriteFinish.pollInterval).to.equal(100); expect(watcher.options.awaitWriteFinish.stabilityThreshold).to.equal(2000); }); - it('should not emit add event before a file is fully written', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'hello', simpleCb); - w(function() { - spy.should.not.have.been.calledWith('add'); - done(); - }, 200)(); - }); + it('should not emit add event before a file is fully written', async () => { + const testPath = getFixturePath('add.txt'); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, 'hello'); + await delay(200); + spy.should.not.have.been.calledWith('add'); }); - it('should wait for the file to be fully written before emitting the add event', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'hello', w(function() { - spy.should.not.have.been.called; - }, 300)); - waitFor([spy], function() { - spy.should.have.been.calledWith('add', testPath); - done(); - }); - }); + it('should wait for the file to be fully written before emitting the add event', async () => { + const testPath = getFixturePath('add.txt'); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, 'hello'); + + await delay(300); + spy.should.not.have.been.called; + await waitFor([spy]); + spy.should.have.been.calledWith('add', testPath); }); - it('should emit with the final stats', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'hello ', w(function() { - fs.appendFileSync(testPath, 'world!'); - }, 300)); - waitFor([spy], function() { - spy.should.have.been.calledWith('add', testPath); - expect(spy.args[0][2].size).to.equal(12); - done(); - }); - }); + it('should emit with the final stats', async () => { + const testPath = getFixturePath('add.txt'); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, 'hello '); + + await delay(300); + fs.appendFileSync(testPath, 'world!'); + + await waitFor([spy]); + spy.should.have.been.calledWith('add', testPath); + expect(spy.args[0][2].size).to.equal(12); }); - it('should not emit change event while a file has not been fully written', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'hello', simpleCb); - w(function() { - fs.writeFile(testPath, 'edit', simpleCb); - w(function() { - spy.should.not.have.been.calledWith('change', testPath); - done(); - }, 200)(); - }, 100)(); - }); + it('should not emit change event while a file has not been fully written', async () => { + const testPath = getFixturePath('add.txt'); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, 'hello'); + await delay(100); + await write(testPath, 'edit'); + await delay(200); + spy.should.not.have.been.calledWith('change', testPath); }); - it('should not emit change event before an existing file is fully updated', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('change.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'hello', simpleCb); - w(function() { - spy.should.not.have.been.calledWith('change', testPath); - done(); - }, 300)(); - }); + it('should not emit change event before an existing file is fully updated', async () => { + const testPath = getFixturePath('change.txt'); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, 'hello'); + await delay(300); + spy.should.not.have.been.calledWith('change', testPath); }); - it('should wait for an existing file to be fully updated before emitting the change event', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('change.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'hello', w(function() { - spy.should.not.have.been.called; - }, 300)); - waitFor([spy], function() { - spy.should.have.been.calledWith('change', testPath); - done(); - }); - }); + it('should wait for an existing file to be fully updated before emitting the change event', async () => { + const testPath = getFixturePath('change.txt'); + const spy = await aspy(stdWatcher(), 'all'); + fs.writeFile(testPath, 'hello'); + + await delay(300); + spy.should.not.have.been.called; + await waitFor([spy]); + spy.should.have.been.calledWith('change', testPath); }); - it('should emit change event after the file is fully written', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - w(fs.writeFile.bind(fs, testPath, 'hello', simpleCb))(); - waitFor([spy], function() { - spy.should.have.been.calledWith('add', testPath); - fs.writeFile(testPath, 'edit', simpleCb); - waitFor([spy.withArgs('change')], function() { - spy.should.have.been.calledWith('change', testPath); - done(); - }); - }); - }); + it('should emit change event after the file is fully written', async () => { + const testPath = getFixturePath('add.txt'); + const spy = await aspy(stdWatcher(), 'all'); + await delay(); + await write(testPath, 'hello'); + + await waitFor([spy]); + spy.should.have.been.calledWith('add', testPath); + await write(testPath, 'edit'); + await waitFor([spy.withArgs('change')]); + spy.should.have.been.calledWith('change', testPath); }); - it('should not raise any event for a file that was deleted before fully written', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('add.txt'); - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'hello', simpleCb); - w(function() { - fs.unlink(testPath, simpleCb); - w(function() { - spy.should.not.have.been.calledWith(sinon.match.string, testPath); - done(); - }, 400)(); - }, 400)(); - }); + it('should not raise any event for a file that was deleted before fully written', async () => { + const testPath = getFixturePath('add.txt'); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, 'hello'); + await delay(400); + await fsunlink(testPath); + await delay(400); + spy.should.not.have.been.calledWith(sinon.match.string, testPath); }); - it('should be compatible with the cwd option', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('subdir/add.txt'); - var filename = sysPath.basename(testPath); + it('should be compatible with the cwd option', async () => { + const testPath = getFixturePath('subdir/add.txt'); + const filename = sysPath.basename(testPath); options.cwd = sysPath.dirname(testPath); - fs.mkdir(options.cwd, w(function() { - stdWatcher() - .on('all', spy) - .on('ready', function() { - w(fs.writeFile.bind(fs, testPath, 'hello', simpleCb), 400)(); - waitFor([spy.withArgs('add')], function() { - spy.should.have.been.calledWith('add', filename); - done(); - }); - }); - }, win32Polling010 ? 900 : 200)); + await fs.promises.mkdir(options.cwd); + + await delay(win32Polling010 ? 900 : 200); + const spy = await aspy(stdWatcher(), 'all'); + + await delay(400); + await write(testPath, 'hello'); + + await waitFor([spy.withArgs('add')]); + spy.should.have.been.calledWith('add', filename); }); - it('should still emit initial add events', function(done) { + it('should still emit initial add events', async () => { options.ignoreInitial = false; - var spy = sinon.spy(); - stdWatcher() - .on('all', spy) - .on('ready', function() { - spy.should.have.been.calledWith('add'); - spy.should.have.been.calledWith('addDir'); - done(); - }); + const spy = await aspy(stdWatcher(), 'all'); + spy.should.have.been.calledWith('add'); + spy.should.have.been.calledWith('addDir'); }); - it('should emit an unlink event when a file is updated and deleted just after that', function(done) { - var spy = sinon.spy(); - var testPath = getFixturePath('subdir/add.txt'); - var filename = sysPath.basename(testPath); + it('should emit an unlink event when a file is updated and deleted just after that', async () => { + const testPath = getFixturePath('subdir/add.txt'); + const filename = sysPath.basename(testPath); options.cwd = sysPath.dirname(testPath); - fs.mkdir(options.cwd, w(function() { - fs.writeFile(testPath, 'hello', w(function() { - stdWatcher() - .on('all', spy) - .on('ready', function() { - fs.writeFile(testPath, 'edit', w(function() { - fs.unlink(testPath, simpleCb); - waitFor([spy.withArgs('unlink')], function() { - spy.should.have.been.calledWith('unlink', filename); - if (win32Polling010) return done(); - spy.should.not.have.been.calledWith('change', filename); - done(); - }); - })); - }); - })); - })); + await fs.promises.mkdir(options.cwd); + await delay(); + await write(testPath, 'hello'); + await delay(); + const spy = await aspy(stdWatcher(), 'all'); + await write(testPath, 'edit'); + await delay(); + await fsunlink(testPath); + await waitFor([spy.withArgs('unlink')]); + spy.should.have.been.calledWith('unlink', filename); + if (win32Polling010) return true; + spy.should.not.have.been.calledWith('change', filename); }); + // describe('race2 condition', function() { + // // Reproduces bug https://github.com/paulmillr/chokidar/issues/546, which was causing an + // // uncaught exception. The race condition is likelier to happen when stat() is slow. + // const _fs = require('fs'); + // const _realStat = _fs.stat; + + // beforeEach(function() { + // options.awaitWriteFinish = {pollInterval: 50, stabilityThreshold: 50}; + // options.ignoreInitial = true; + + // // Stub fs.stat() to take a while to return. + // sinon.stub(_fs, 'stat', function(path, cb) { + // _realStat(path, w(cb, 250)); + // }); + // }); + + // afterEach(() => { + // // Restore fs.stat() back to normal. + // sinon.restore(_fs.stat); + // }); + + // it('should handle unlink that happens while waiting for stat to return', async () => { + // const testPath = getFixturePath('add.txt'); + // const spy = await aspy(stdWatcher(), 'all'); + // await write(testPath, 'hello'); + // await waitFor([spy]); + // spy.should.have.been.calledWith('add', testPath); + // _fs.stat.reset(); + // await write(testPath, 'edit'); + + // await delay(40); + // // There will be a stat() call after we notice the change, plus pollInterval. + // // After waiting a bit less, wait specifically for that stat() call. + // _fs.stat.reset(); + // await waitFor([_fs.stat]); + // // Once stat call is made, it will take some time to return. Meanwhile, unlink + // // the file and wait for that to be noticed. + // await fsunlink(testPath); + // await waitFor([spy.withArgs('unlink')]); + + // await delay(400); + // // Wait a while after unlink to ensure stat() had time to return. That's where + // // an uncaught exception used to happen. + // spy.should.have.been.calledWith('unlink', testPath); + // if (win32Polling010) return true; + // spy.should.not.have.been.calledWith('change'); + // }); + // }); describe('race condition', function() { + function w(fn, to) { + return setTimeout.bind(null, fn, to || slowerDelay || 50); + } + function simpleCb(err) { if (err) throw err; } + // Reproduces bug https://github.com/paulmillr/chokidar/issues/546, which was causing an // uncaught exception. The race condition is likelier to happen when stat() is slow. var _fs = require('fs'); @@ -1994,6 +1805,22 @@ function runTests(baseopts) { sinon.restore(_fs.stat); }); + function _waitFor(spies, fn) { + function isSpyReady(spy) { + return Array.isArray(spy) ? spy[0].callCount >= spy[1] : spy.callCount; + } + function finish() { + clearInterval(intrvl); + clearTimeout(to); + fn(); + fn = Function.prototype; + } + var intrvl = setInterval(function() { + if (spies.every(isSpyReady)) finish(); + }, 5); + var to = setTimeout(finish, 3500); + } + it('should handle unlink that happens while waiting for stat to return', function(done) { var spy = sinon.spy(); var testPath = getFixturePath('add.txt'); @@ -2001,7 +1828,7 @@ function runTests(baseopts) { .on('all', spy) .on('ready', function() { fs.writeFile(testPath, 'hello', simpleCb); - waitFor([spy], function() { + _waitFor([spy], function() { spy.should.have.been.calledWith('add', testPath); _fs.stat.reset(); fs.writeFile(testPath, 'edit', simpleCb); @@ -2009,11 +1836,11 @@ function runTests(baseopts) { // There will be a stat() call after we notice the change, plus pollInterval. // After waiting a bit less, wait specifically for that stat() call. _fs.stat.reset(); - waitFor([_fs.stat], function() { + _waitFor([_fs.stat], function() { // Once stat call is made, it will take some time to return. Meanwhile, unlink // the file and wait for that to be noticed. fs.unlink(testPath, simpleCb); - waitFor([spy.withArgs('unlink')], w(function() { + _waitFor([spy.withArgs('unlink')], w(function() { // Wait a while after unlink to ensure stat() had time to return. That's where // an uncaught exception used to happen. spy.should.have.been.calledWith('unlink', testPath); @@ -2031,166 +1858,155 @@ function runTests(baseopts) { }); describe('getWatched', function() { before(closeWatchers); - it('should return the watched paths', function(done) { - var expected = {}; + it('should return the watched paths', async () => { + const expected = {}; expected[sysPath.dirname(fixturesPath)] = [subdir.toString()]; expected[fixturesPath] = ['change.txt', 'unlink.txt']; - stdWatcher().on('ready', function() { - expect(watcher.getWatched()).to.deep.equal(expected); - done(); - }); + await aspy(stdWatcher(), 'ready'); + expect(watcher.getWatched()).to.deep.equal(expected); }); - it('should set keys relative to cwd & include added paths', function(done) { + it('should set keys relative to cwd & include added paths', async () => { options.cwd = fixturesPath; - var expected = { + const expected = { '.': ['change.txt', 'subdir', 'unlink.txt'], '..': [subdir.toString()], 'subdir': [] }; - fs.mkdir(getFixturePath('subdir'), PERM_ARR, function() { - stdWatcher().on('ready', function() { - expect(watcher.getWatched()).to.deep.equal(expected); - done(); - }) - }); + await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await aspy(stdWatcher(), 'ready'); + expect(watcher.getWatched()).to.deep.equal(expected); }); }); describe('unwatch', function() { before(closeWatchers); - beforeEach(function(done) { + beforeEach(async () => { options.ignoreInitial = true; - fs.mkdir(getFixturePath('subdir'), PERM_ARR, w(done)); - }); - it('should stop watching unwatched paths', function(done) { - var spy = sinon.spy(); - var watchPaths = [getFixturePath('subdir'), getFixturePath('change.txt')]; - watcher = chokidar.watch(watchPaths, options) - .on('all', spy) - .on('ready', function() { - watcher.unwatch(getFixturePath('subdir')); - w(function() { - fs.writeFile(getFixturePath('subdir/add.txt'), Date.now(), simpleCb); - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - })(); - waitFor([spy], w(function() { - spy.should.have.been.calledWith('change', getFixturePath('change.txt')); - spy.should.not.have.been.calledWith('add'); - if (!osXFsWatch) spy.should.have.been.calledOnce; - done(); - }, 300)); - }); + await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await delay(); + }); + it('should stop watching unwatched paths', async () => { + const watchPaths = [getFixturePath('subdir'), getFixturePath('change.txt')]; + watcher = chokidar.watch(watchPaths, options); + const spy = await aspy(watcher, 'all'); + watcher.unwatch(getFixturePath('subdir')); + + await delay(); + await write(getFixturePath('subdir/add.txt'), Date.now()); + await write(getFixturePath('change.txt'), Date.now()); + await waitFor([spy]); + + await delay(300); + spy.should.have.been.calledWith('change', getFixturePath('change.txt')); + spy.should.not.have.been.calledWith('add'); + if (!osXFsWatch) spy.should.have.been.calledOnce; + }); + it('should ignore unwatched paths that are a subset of watched paths', async () => { + watcher = chokidar.watch(fixturesPath, options); + const spy = await aspy(watcher, 'all'); + + await delay(); + // test with both relative and absolute paths + const subdirRel = upath.relative(process.cwd(), getFixturePath('subdir')); + watcher.unwatch([subdirRel, getGlobPath('unl*')]); + + await delay(); + await fsunlink(getFixturePath('unlink.txt')); + await write(getFixturePath('subdir/add.txt'), Date.now()); + await write(getFixturePath('change.txt'), Date.now()); + await waitFor([spy.withArgs('change')]); + + await delay(300); + spy.should.have.been.calledWith('change', getFixturePath('change.txt')); + spy.should.not.have.been.calledWith('add', getFixturePath('subdir/add.txt')); + spy.should.not.have.been.calledWith('unlink'); + if (!osXFsWatch) spy.should.have.been.calledOnce; + }); + it('should unwatch relative paths', async () => { + const fixturesDir = sysPath.relative(process.cwd(), fixturesPath); + const subdir = sysPath.join(fixturesDir, 'subdir'); + const changeFile = sysPath.join(fixturesDir, 'change.txt'); + const watchPaths = [subdir, changeFile]; + watcher = chokidar.watch(watchPaths, options); + const spy = await aspy(watcher, 'all'); + + await delay(); + watcher.unwatch(subdir); + await write(getFixturePath('subdir/add.txt'), Date.now()); + await write(getFixturePath('change.txt'), Date.now()); + await waitFor([spy]); + + await delay(300); + spy.should.have.been.calledWith('change', changeFile); + spy.should.not.have.been.calledWith('add'); + if (!osXFsWatch) spy.should.have.been.calledOnce; }); - it('should ignore unwatched paths that are a subset of watched paths', function(done) { - var spy = sinon.spy(); - watcher = chokidar.watch(fixturesPath, options) - .on('all', spy) - .on('ready', w(function() { - // test with both relative and absolute paths - var subdirRel = upath.relative(process.cwd(), getFixturePath('subdir')); - watcher.unwatch([subdirRel, getGlobPath('unl*')]); - w(function() { - fs.unlink(getFixturePath('unlink.txt'), simpleCb); - fs.writeFile(getFixturePath('subdir/add.txt'), Date.now(), simpleCb); - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - })(); - waitFor([spy.withArgs('change')], w(function() { - spy.should.have.been.calledWith('change', getFixturePath('change.txt')); - spy.should.not.have.been.calledWith('add', getFixturePath('subdir/add.txt')); - spy.should.not.have.been.calledWith('unlink'); - if (!osXFsWatch) spy.should.have.been.calledOnce; - done(); - }, 300)); - })); - }); - it('should unwatch relative paths', function(done) { - var spy = sinon.spy(); - var fixturesDir = sysPath.relative(process.cwd(), fixturesPath); - var subdir = sysPath.join(fixturesDir, 'subdir'); - var changeFile = sysPath.join(fixturesDir, 'change.txt'); - var watchPaths = [subdir, changeFile]; - watcher = chokidar.watch(watchPaths, options) - .on('all', spy) - .on('ready', w(function() { - watcher.unwatch(subdir); - fs.writeFile(getFixturePath('subdir/add.txt'), Date.now(), simpleCb); - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - waitFor([spy], w(function() { - spy.should.have.been.calledWith('change', changeFile); - spy.should.not.have.been.calledWith('add'); - if (!osXFsWatch) spy.should.have.been.calledOnce; - done(); - }, 300)); - })); - }); - it('should watch paths that were unwatched and added again', function(done) { - var spy = sinon.spy(); - var watchPaths = [getFixturePath('change.txt')]; - watcher = chokidar.watch(watchPaths, options) - .on('ready', w(function() { - watcher.unwatch(getFixturePath('change.txt')); - w(function() { - watcher.on('all', spy).add(getFixturePath('change.txt')); - w(function() { - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - waitFor([spy], function() { - spy.should.have.been.calledWith('change', getFixturePath('change.txt')); - if (!osXFsWatch) spy.should.have.been.calledOnce; - done(); - }); - })(); - })(); - })); - }); - it('should unwatch paths that are relative to options.cwd', function(done) { + it('should watch paths that were unwatched and added again', async () => { + const spy = sinon.spy(); + const watchPaths = [getFixturePath('change.txt')]; + watcher = chokidar.watch(watchPaths, options); + await aspy(watcher); + + await delay(); + watcher.unwatch(getFixturePath('change.txt')); + + await delay(); + watcher.on('all', spy).add(getFixturePath('change.txt')); + + await delay(); + await write(getFixturePath('change.txt'), Date.now()); + await waitFor([spy]); + spy.should.have.been.calledWith('change', getFixturePath('change.txt')); + if (!osXFsWatch) spy.should.have.been.calledOnce; + }); + it('should unwatch paths that are relative to options.cwd', async () => { options.cwd = fixturesPath; - var spy = sinon.spy(); - watcher = chokidar.watch('.', options) - .on('all', spy) - .on('ready', function() { - watcher.unwatch(['subdir', getFixturePath('unlink.txt')]); - w(function() { - fs.unlink(getFixturePath('unlink.txt'), simpleCb); - fs.writeFile(getFixturePath('subdir/add.txt'), Date.now(), simpleCb); - fs.writeFile(getFixturePath('change.txt'), Date.now(), simpleCb); - })(); - waitFor([spy], w(function() { - spy.should.have.been.calledWith('change', 'change.txt'); - spy.should.not.have.been.calledWith('add'); - spy.should.not.have.been.calledWith('unlink'); - if (!osXFsWatch) spy.should.have.been.calledOnce; - done(); - }, 300)); - }); + watcher = chokidar.watch('.', options); + const spy = await aspy(watcher, 'all'); + watcher.unwatch(['subdir', getFixturePath('unlink.txt')]); + + await delay(); + await fsunlink(getFixturePath('unlink.txt')); + await write(getFixturePath('subdir/add.txt'), Date.now()); + await write(getFixturePath('change.txt'), Date.now()); + await waitFor([spy]); + + await delay(300); + spy.should.have.been.calledWith('change', 'change.txt'); + spy.should.not.have.been.calledWith('add'); + spy.should.not.have.been.calledWith('unlink'); + if (!osXFsWatch) spy.should.have.been.calledOnce; }); }); describe('close', function() { - it('should ignore further events on close', function(done) { - var spy = sinon.spy(); - watcher = chokidar.watch(fixturesPath, options).once('add', function() { - watcher.once('add', function() { - watcher.on('add', spy).close(); - fs.writeFile(getFixturePath('add.txt'), Date.now(), simpleCb); - w(function() { + it('should ignore further events on close', async () => { + return new Promise(async (resolve) => { + const spy = sinon.spy(); + watcher = chokidar.watch(fixturesPath, options); + watcher.once('add', () => { + watcher.once('add', async () => { + watcher.on('add', spy).close(); + await delay(900); + await write(getFixturePath('add.txt'), Date.now()); spy.should.not.have.been.called; - done(); - }, 900)(); - }); - }).on('ready', function() { - fs.writeFile(getFixturePath('add.txt'), 'hello', function() { - fs.unlink(getFixturePath('add.txt'), simpleCb); + resolve(); + }); }); + await aspy(watcher); + await fs.promises.writeFile(getFixturePath('add.txt'), 'hello'); + await fsunlink(getFixturePath('add.txt')); }); }); - it('should not prevent the process from exiting', async function(done) { - var scriptFile = getFixturePath('script.js'); - var scriptContent = '\ - var chokidar = require("' + __dirname.replace(/\\/g, '\\\\') + '");\n\ - var watcher = chokidar.watch("' + scriptFile.replace(/\\/g, '\\\\') + '");\n\ - watcher.close();\n\ - process.stdout.write("closed");\n'; - await write(scriptFile, scriptContent); - const stdout = await exec('node ' + scriptFile); - expect(stdout.toString()).to.equal('closed'); + it('should not prevent the process from exiting', async () => { + const scriptFile = getFixturePath('script.js'); + const scriptContent = '\ + const chokidar = require("' + __dirname.replace(/\\/g, '\\\\') + '");\n\ + const watcher = chokidar.watch("' + scriptFile.replace(/\\/g, '\\\\') + '");\n\ + watcher.close();\n\ + process.stdout.write("closed");\n'; + await write(scriptFile, scriptContent); + const obj = await exec('node ' + scriptFile); + const stdout = obj.stdout; + expect(stdout.toString()).to.equal('closed'); }); }); describe('env variable option override', function() { @@ -2199,70 +2015,80 @@ function runTests(baseopts) { delete process.env.CHOKIDAR_USEPOLLING; }); - it('should make options.usePolling `true` when CHOKIDAR_USEPOLLING is set to true', function(done) { + it('should make options.usePolling `true` when CHOKIDAR_USEPOLLING is set to true', async () => { options.usePolling = false; process.env.CHOKIDAR_USEPOLLING = true; - watcher = chokidar.watch(fixturesPath, options).on('ready', function() { - watcher.options.usePolling.should.be.true; - done(); - }); + watcher = chokidar.watch(fixturesPath, options); + await aspy(watcher); + watcher.options.usePolling.should.be.true; }); - it('should make options.usePolling `true` when CHOKIDAR_USEPOLLING is set to 1', function(done) { + it('should make options.usePolling `true` when CHOKIDAR_USEPOLLING is set to 1', async () => { options.usePolling = false; process.env.CHOKIDAR_USEPOLLING = 1; - watcher = chokidar.watch(fixturesPath, options).on('ready', function() { - watcher.options.usePolling.should.be.true; - done(); - }); + watcher = chokidar.watch(fixturesPath, options); + await aspy(watcher); + watcher.options.usePolling.should.be.true; }); - it('should make options.usePolling `false` when CHOKIDAR_USEPOLLING is set to false', function(done) { + it('should make options.usePolling `false` when CHOKIDAR_USEPOLLING is set to false', async () => { options.usePolling = true; process.env.CHOKIDAR_USEPOLLING = false; - watcher = chokidar.watch(fixturesPath, options).on('ready', function() { - watcher.options.usePolling.should.be.false; - done(); - }); + watcher = chokidar.watch(fixturesPath, options); + await aspy(watcher); + watcher.options.usePolling.should.be.false; }); - it('should make options.usePolling `false` when CHOKIDAR_USEPOLLING is set to 0', function(done) { + it('should make options.usePolling `false` when CHOKIDAR_USEPOLLING is set to 0', async () => { options.usePolling = true; process.env.CHOKIDAR_USEPOLLING = false; - watcher = chokidar.watch(fixturesPath, options).on('ready', function() { - watcher.options.usePolling.should.be.false; - done(); - }); + watcher = chokidar.watch(fixturesPath, options); + await aspy(watcher); + watcher.options.usePolling.should.be.false; }); - it('should not attenuate options.usePolling when CHOKIDAR_USEPOLLING is set to an arbitrary value', function(done) { + it('should not attenuate options.usePolling when CHOKIDAR_USEPOLLING is set to an arbitrary value', async () => { options.usePolling = true; process.env.CHOKIDAR_USEPOLLING = 'foo'; - watcher = chokidar.watch(fixturesPath, options).on('ready', function() { - watcher.options.usePolling.should.be.true; - done(); - }); + watcher = chokidar.watch(fixturesPath, options); + await aspy(watcher); + watcher.options.usePolling.should.be.true; }); }); describe('CHOKIDAR_INTERVAL', function() { - afterEach(function() { + afterEach(() => { delete process.env.CHOKIDAR_INTERVAL; }); - it('should make options.interval = CHOKIDAR_INTERVAL when it is set', function(done) { + it('should make options.interval = CHOKIDAR_INTERVAL when it is set', async () => { options.interval = 100; process.env.CHOKIDAR_INTERVAL = 1500; - watcher = chokidar.watch(fixturesPath, options).on('ready', function() { - watcher.options.interval.should.be.equal(1500); - done(); - }); + watcher = chokidar.watch(fixturesPath, options); + await aspy(watcher); + watcher.options.interval.should.be.equal(1500); }); }); }); -} +}; + +describe('chokidar', function() { + this.timeout(6000); + it('should expose public API methods', function() { + chokidar.FSWatcher.should.be.a('function'); + chokidar.watch.should.be.a('function'); + }); + + if (os === 'darwin') { + describe('fsevents (native extension)', runTests.bind(this, {useFsEvents: true})); + } + if (os !== 'darwin' || !node010) { + describe('fs.watch (non-polling)', runTests.bind(this, {usePolling: false, useFsEvents: false})); + } + describe('fs.watchFile (polling)', runTests.bind(this, {usePolling: true, interval: 10})); +}); From 16abe22162088dac23b9cf8d299d3641729729dd Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 12 Feb 2019 04:45:46 -0800 Subject: [PATCH 05/10] Disable `fs.watch` tests on MacOS. See 4c5747c24a1a2e291b99a247e32d9c3da2c33b44 --- test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.js b/test.js index 8e857c3f..ead74698 100644 --- a/test.js +++ b/test.js @@ -2087,7 +2087,7 @@ describe('chokidar', function() { if (os === 'darwin') { describe('fsevents (native extension)', runTests.bind(this, {useFsEvents: true})); } - if (os !== 'darwin' || !node010) { + if (os !== 'darwin') { describe('fs.watch (non-polling)', runTests.bind(this, {usePolling: false, useFsEvents: false})); } describe('fs.watchFile (polling)', runTests.bind(this, {usePolling: true, interval: 10})); From aefa8043176c8882c7288b00f997306d981c9c09 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 12 Feb 2019 04:47:49 -0800 Subject: [PATCH 06/10] Drop node v6. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e09347d2..a1d0a60a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: node_js node_js: - '10' - '8' - - '6' os: - linux - osx From dd3db50f91f2e6e59676a95fb82c6b0cfd47a41b Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 12 Feb 2019 04:53:41 -0800 Subject: [PATCH 07/10] Fix fs.promises on node 8.x. --- test.js | 150 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 77 insertions(+), 73 deletions(-) diff --git a/test.js b/test.js index ead74698..1a5a740b 100644 --- a/test.js +++ b/test.js @@ -15,8 +15,12 @@ chai.use(require('sinon-chai')); const os = process.platform; const fs_promises = require('fs').promises; -const write = fs_promises.writeFile; -const fsunlink = fs_promises.unlink; +const write = fs_promises ? fs_promises.writeFile : promisify(fs.writeFile); +const fs_symlink = promisify(fs.symlink); +const fs_rename = promisify(fs.rename); +const fs_mkdir = promisify(fs.mkdir); +const fs_rmdir = promisify(fs.rmdir); +const fs_unlink = promisify(fs.unlink); const isTravisMac = process.env.TRAVIS && os === 'darwin'; @@ -81,11 +85,11 @@ if (!fs.readFileSync(__filename).toString().match(/\sit\.only\(/)) { before(async () => { var writtenCount = 0; await rimraf(sysPath.join(__dirname, 'test-fixtures')); - await fs.promises.mkdir(fixturesPath, PERM_ARR); + await fs_mkdir(fixturesPath, PERM_ARR); while (subdir < testCount) { subdir++; fixturesPath = getFixturePath(''); - await fs.promises.mkdir(fixturesPath, PERM_ARR); + await fs_mkdir(fixturesPath, PERM_ARR); await write(sysPath.join(fixturesPath, 'change.txt'), 'b'); if (++writtenCount === testCount * 2) { subdir = 0; @@ -389,7 +393,7 @@ const runTests = function(baseopts) { const testDir = getFixturePath('subdir'); const spy = await aspy(watcher, 'addDir'); spy.should.not.have.been.called; - await fs.promises.mkdir(testDir, PERM_ARR); + await fs_mkdir(testDir, PERM_ARR); await waitFor([spy]); spy.should.have.been.calledOnce; spy.should.have.been.calledWith(testDir); @@ -411,7 +415,7 @@ const runTests = function(baseopts) { const testPath = getFixturePath('unlink.txt'); const spy = await aspy(watcher, 'unlink'); spy.should.not.have.been.called; - await fsunlink(testPath); + await fs_unlink(testPath); await waitFor([spy]); spy.should.have.been.calledWith(testPath); expect(spy.args[0][1]).to.not.be.ok; // no stats @@ -424,7 +428,7 @@ const runTests = function(baseopts) { const spy = await aspy(watcher, 'unlinkDir'); await delay(); - await fs.promises.rmdir(testDir); + await fs_rmdir(testDir); await waitFor([spy]); spy.should.have.been.calledWith(testDir); expect(spy.args[0][1]).to.not.be.ok; // no stats @@ -458,7 +462,7 @@ const runTests = function(baseopts) { addSpy.should.not.have.been.called; await delay(); - await fs.promises.rename(testPath, newPath); + await fs_rename(testPath, newPath); await waitFor([unlinkSpy, addSpy]); unlinkSpy.should.have.been.calledWith(testPath); expect(unlinkSpy.args[0][1]).to.not.be.ok; // no stats @@ -483,7 +487,7 @@ const runTests = function(baseopts) { unlinkSpy.should.not.have.been.called; addSpy.should.not.have.been.called; changeSpy.should.not.have.been.called; - await fs.promises.unlink(testPath); + await fs_unlink(testPath); await waitFor([unlinkSpy.withArgs(testPath)]); unlinkSpy.should.have.been.calledWith(testPath); @@ -499,10 +503,10 @@ const runTests = function(baseopts) { const newPath1 = getFixturePath('moved.txt'); const newPath2 = getFixturePath('moved-again.txt'); await aspy(watcher, 'unlink', unlinkSpy); - await fs.promises.rename(testPath, newPath1); + await fs_rename(testPath, newPath1); await delay(300); - await fs.promises.rename(newPath1, newPath2); + await fs_rename(newPath1, newPath2); await waitFor([unlinkSpy.withArgs(newPath1)]); unlinkSpy.withArgs(testPath).should.have.been.calledOnce; unlinkSpy.withArgs(newPath1).should.have.been.calledOnce; @@ -518,7 +522,7 @@ const runTests = function(baseopts) { const testPath = getFixturePath('subdir/add.txt'); const spy = await aspy(watcher, 'add'); spy.should.not.have.been.called; - await fs.promises.mkdir(testDir, PERM_ARR); + await fs_mkdir(testDir, PERM_ARR); await write(testPath, Date.now()); await waitFor([spy]); spy.should.have.been.calledOnce; @@ -534,16 +538,16 @@ const runTests = function(baseopts) { const subPath = getFixturePath('subdir2/subsub'); watcher.on('unlinkDir', unlinkSpy).on('addDir', addSpy); await aspy(watcher); - await fs.promises.mkdir(parentPath, PERM_ARR); + await fs_mkdir(parentPath, PERM_ARR); await delay(win32Polling ? 900 : 300); - await fs.promises.rmdir(parentPath); + await fs_rmdir(parentPath); await waitFor([unlinkSpy.withArgs(parentPath)]); unlinkSpy.should.have.been.calledWith(parentPath); - await fs.promises.mkdir(parentPath, PERM_ARR); + await fs_mkdir(parentPath, PERM_ARR); await delay(win32Polling ? 2200 : 1200); - await fs.promises.mkdir(subPath, PERM_ARR); + await fs_mkdir(subPath, PERM_ARR); await waitFor([[addSpy, 3]]); addSpy.should.have.been.calledWith(parentPath); addSpy.should.have.been.calledWith(subPath); @@ -565,7 +569,7 @@ const runTests = function(baseopts) { const spy = await aspy(watcher, 'unlink'); await delay(); - await fs.promises.unlink(testPath); + await fs_unlink(testPath); await waitFor([spy]); spy.should.have.been.calledWith(testPath); }); @@ -580,7 +584,7 @@ const runTests = function(baseopts) { await aspy(watcher); await delay(); - await fs.promises.unlink(testPath); + await fs_unlink(testPath); await waitFor([unlinkSpy]); unlinkSpy.should.have.been.calledWith(testPath); @@ -615,7 +619,7 @@ const runTests = function(baseopts) { const spy = await aspy(watcher, 'unlink'); await delay(); - await fs.promises.unlink(testPath); + await fs_unlink(testPath); await waitFor([spy]); spy.should.have.been.calledWith(testPath); }); @@ -632,7 +636,7 @@ const runTests = function(baseopts) { await aspy(watcher); await delay(); - await fs.promises.unlink(testPath); + await fs_unlink(testPath); await waitFor([unlinkSpy]); await delay(); @@ -658,7 +662,7 @@ const runTests = function(baseopts) { await aspy(watcher); await delay(); - await fsunlink(testPath); + await fs_unlink(testPath); await waitFor([unlinkSpy]); await delay(); @@ -682,7 +686,7 @@ const runTests = function(baseopts) { await aspy(watcher); await delay(); - await fsunlink(testPath); + await fs_unlink(testPath); await waitFor([unlinkSpy]); await delay(); @@ -706,10 +710,10 @@ const runTests = function(baseopts) { await aspy(watcher); await delay(); - await fsunlink(otherPath); + await fs_unlink(otherPath); await delay(); - await fsunlink(testPath); + await fs_unlink(testPath); await waitFor([[unlinkSpy, 2]]); await delay(); @@ -735,7 +739,7 @@ const runTests = function(baseopts) { .on('add', addSpy); await aspy(watcher, 'ready'); await delay(); - await fsunlink(testPath); + await fs_unlink(testPath); await waitFor([unlinkSpy]); await delay(); @@ -755,13 +759,13 @@ const runTests = function(baseopts) { const testPath = getFixturePath('subdir/add.txt'); const renamedDir = getFixturePath('subdir-renamed'); const expectedPath = sysPath.join(renamedDir, 'add.txt'); - await fs.promises.mkdir(testDir, PERM_ARR); + await fs_mkdir(testDir, PERM_ARR); await write(testPath, Date.now()); watcher = chokidar.watch(fixturesPath, options); const spy = await aspy(watcher, 'add'); await delay(1000); - await fs.promises.rename(testDir, renamedDir); + await fs_rename(testDir, renamedDir); await waitFor([spy]); spy.should.have.been.calledOnce; spy.should.have.been.calledWith(expectedPath); @@ -786,7 +790,7 @@ const runTests = function(baseopts) { spy.should.not.have.been.called; await delay(win32Polling010 ? 900 : undefined); - await fs.promises.mkdir(testDir, PERM_ARR); + await fs_mkdir(testDir, PERM_ARR); await delay(win32Polling010 ? 900 : undefined); await write(testPath, 'hello'); @@ -825,7 +829,7 @@ const runTests = function(baseopts) { spy.should.have.been.calledWith('add', unlinkPath); await delay(); - await fsunlink(unlinkPath); + await fs_unlink(unlinkPath); await waitFor([[spy, 2], spy.withArgs('unlink')]); if (!osXFsWatch010) spy.should.have.been.calledTwice; spy.should.have.been.calledWith('unlink', unlinkPath); @@ -844,8 +848,8 @@ const runTests = function(baseopts) { setTimeout(() => { write(getFixturePath('add.txt'), Date.now()); write(getFixturePath('subdir/subsub/ab.txt'), Date.now()); - fsunlink(getFixturePath('subdir/a.txt')); - fsunlink(getFixturePath('subdir/b.txt')); + fs_unlink(getFixturePath('subdir/a.txt')); + fs_unlink(getFixturePath('subdir/b.txt')); }, 50); await waitFor([[spy.withArgs('add'), 3], spy.withArgs('unlink'), spy.withArgs('change')]); spy.withArgs('add').should.have.been.calledThrice; @@ -891,7 +895,7 @@ const runTests = function(baseopts) { await delay(); await write(addPath, Date.now()); await write(changePath, Date.now()); - await fsunlink(unlinkPath); + await fs_unlink(unlinkPath); await waitFor([[spy, 4], spy.withArgs('unlink', unlinkPath)]); spy.should.have.been.calledWith('change', changePath); @@ -1049,13 +1053,13 @@ const runTests = function(baseopts) { let linkedDir; beforeEach(async () => { linkedDir = sysPath.resolve(fixturesPath, '..', subdir + '-link'); - await fs.promises.symlink(fixturesPath, linkedDir); - await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await fs_symlink(fixturesPath, linkedDir); + await fs_mkdir(getFixturePath('subdir'), PERM_ARR); await write(getFixturePath('subdir/add.txt'), 'b'); return true; }); afterEach(async () => { - await fs.promises.unlink(linkedDir); + await fs_unlink(linkedDir); return true; }); @@ -1108,7 +1112,7 @@ const runTests = function(baseopts) { spy.should.have.been.calledWith('change', testFile); }); it('should not recurse indefinitely on circular symlinks', async () => { - await fs.promises.symlink(fixturesPath, getFixturePath('subdir/circular')); + await fs_symlink(fixturesPath, getFixturePath('subdir/circular')); watcher = stdWatcher(); await aspy(watcher); // return true; @@ -1128,7 +1132,7 @@ const runTests = function(baseopts) { stdWatcher(); const spy = await aspy(watcher, 'all'); await delay(); - await fs.promises.symlink(getFixturePath('subdir'), getFixturePath('link')); + await fs_symlink(getFixturePath('subdir'), getFixturePath('link')); await waitFor([ spy.withArgs('add', getFixturePath('link/add.txt')), spy.withArgs('addDir', getFixturePath('link')) @@ -1219,7 +1223,7 @@ const runTests = function(baseopts) { it('should accommodate nested arrays in input', async () => { const testPath = getFixturePath('change.txt'); const testDir = getFixturePath('subdir'); - await fs.promises.mkdir(testDir); + await fs_mkdir(testDir); watcher = chokidar.watch([[testDir], [testPath]], options); const spy = await aspy(watcher, 'all'); spy.should.have.been.calledWith('add', testPath); @@ -1251,8 +1255,8 @@ const runTests = function(baseopts) { spy.should.have.been.calledWith(fixturesPath); }); it('should emit `addDir` events for preexisting dirs', async () => { - await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); - await fs.promises.mkdir(getFixturePath('subdir/subsub'), PERM_ARR); + await fs_mkdir(getFixturePath('subdir'), PERM_ARR); + await fs_mkdir(getFixturePath('subdir/subsub'), PERM_ARR); watcher = chokidar.watch(fixturesPath, options); const spy = await aspy(watcher, 'addDir'); spy.should.have.been.calledWith(fixturesPath); @@ -1281,7 +1285,7 @@ const runTests = function(baseopts) { const testPath = getFixturePath('subdir/add.txt'); const spy = await aspy(stdWatcher(), 'add'); spy.should.not.have.been.called; - await fs.promises.mkdir(testDir, PERM_ARR); + await fs_mkdir(testDir, PERM_ARR); await write(testPath, Date.now()); await waitFor([spy]); spy.should.have.been.calledOnce; @@ -1299,7 +1303,7 @@ const runTests = function(baseopts) { it('should not emit for preexisting dirs when depth is 0', async () => { options.depth = 0; const testPath = getFixturePath('add.txt'); - await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await fs_mkdir(getFixturePath('subdir'), PERM_ARR); await delay(win32Polling010 ? 1000 : 200); const spy = await aspy(stdWatcher(), 'all'); @@ -1370,11 +1374,11 @@ const runTests = function(baseopts) { }); describe('depth', function() { beforeEach(async () => { - await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); - await fs.promises.writeFile(getFixturePath('subdir/add.txt'), 'b'); + await fs_mkdir(getFixturePath('subdir'), PERM_ARR); + await write(getFixturePath('subdir/add.txt'), 'b'); await delay(options.useFsEvents && 200); - await fs.promises.mkdir(getFixturePath('subdir/subsub'), PERM_ARR); - await fs.promises.writeFile(getFixturePath('subdir/subsub/ab.txt'), 'b'); + await fs_mkdir(getFixturePath('subdir/subsub'), PERM_ARR); + await write(getFixturePath('subdir/subsub/ab.txt'), 'b'); await delay(options.useFsEvents && 200); }); it('should not recurse if depth is 0', async () => { @@ -1412,7 +1416,7 @@ const runTests = function(baseopts) { it('should respect depth setting when following symlinks', async () => { if (os === 'win32') return true; // skip on windows options.depth = 1; - await fs.promises.symlink(getFixturePath('subdir'), getFixturePath('link')); + await fs_symlink(getFixturePath('subdir'), getFixturePath('link')); await delay(); stdWatcher(); const spy = await aspy(watcher, 'all'); @@ -1429,7 +1433,7 @@ const runTests = function(baseopts) { const dirPath = getFixturePath('link/subsub'); stdWatcher(); const spy = await aspy(watcher, 'all'); - await fs.promises.symlink(getFixturePath('subdir'), linkPath); + await fs_symlink(getFixturePath('subdir'), linkPath); await waitFor([[spy, 3], spy.withArgs('addDir', dirPath)]); spy.should.have.been.calledWith('addDir', linkPath); spy.should.have.been.calledWith('addDir', dirPath); @@ -1444,12 +1448,12 @@ const runTests = function(baseopts) { const unlinkSpy = spy.withArgs('unlinkDir'); spy.should.have.been.calledWith('addDir', fixturesPath); spy.should.have.been.calledWith('addDir', getFixturePath('subdir')); - await fs.promises.mkdir(subdir2, PERM_ARR); + await fs_mkdir(subdir2, PERM_ARR); await waitFor([[addSpy, 3]]); addSpy.should.have.been.calledThrice; if (win32Polling010) return true; - await fs.promises.rmdir(subdir2); + await fs_rmdir(subdir2); await waitFor([unlinkSpy]); await delay(); unlinkSpy.should.have.been.calledWith('unlinkDir', subdir2); @@ -1471,15 +1475,15 @@ const runTests = function(baseopts) { await write(getFixturePath('add.txt\~'), 'c'); await write(getFixturePath('.subl5f4.tmp'), 'c'); await delay(300); - await fsunlink(getFixturePath('.change.txt.swp')); - await fsunlink(getFixturePath('add.txt\~')); - await fsunlink(getFixturePath('.subl5f4.tmp')); + await fs_unlink(getFixturePath('.change.txt.swp')); + await fs_unlink(getFixturePath('add.txt\~')); + await fs_unlink(getFixturePath('.subl5f4.tmp')); await delay(300); spy.should.not.have.been.called; }); it('should ignore stale tilde files', async () => { options.ignoreInitial = false; - await fs.promises.writeFile(getFixturePath('old.txt~'), 'a'); + await write(getFixturePath('old.txt~'), 'a'); await delay(); const spy = await aspy(stdWatcher(), 'all'); spy.should.not.have.been.calledWith(getFixturePath('old.txt')); @@ -1492,7 +1496,7 @@ const runTests = function(baseopts) { watcher = chokidar.watch('**', options); const spy = await aspy(watcher, 'all'); await write(getFixturePath('change.txt'), Date.now()); - await fsunlink(getFixturePath('unlink.txt')); + await fs_unlink(getFixturePath('unlink.txt')); await waitFor([spy.withArgs('unlink')]); spy.should.have.been.calledWith('add', 'change.txt'); spy.should.have.been.calledWith('add', 'unlink.txt'); @@ -1507,12 +1511,12 @@ const runTests = function(baseopts) { const testDir = getFixturePath('subdir'); const renamedDir = getFixturePath('subdir-renamed'); - await fs.promises.mkdir(testDir, PERM_ARR); + await fs_mkdir(testDir, PERM_ARR); watcher = chokidar.watch('.', options); setTimeout(() => { - watcher.on('addDir', spy) - fs.promises.rename(testDir, renamedDir); + watcher.on('addDir', spy); + fs_rename(testDir, renamedDir); }, 1000); await waitFor([spy]); @@ -1534,8 +1538,8 @@ const runTests = function(baseopts) { watcher2 = chokidar.watch(fixturesPath, options2); const spy2 = await aspy(watcher2, 'all'); - await fs.promises.writeFile(getFixturePath('change.txt'), Date.now()); - await fsunlink(getFixturePath('unlink.txt')); + await write(getFixturePath('change.txt'), Date.now()); + await fs_unlink(getFixturePath('unlink.txt')); await waitFor([spy1.withArgs('unlink'), spy2.withArgs('unlink')]); spy1.should.have.been.calledWith('change', 'change.txt'); spy1.should.have.been.calledWith('unlink', 'unlink.txt'); @@ -1559,8 +1563,8 @@ const runTests = function(baseopts) { const spy = await aspy(watcher, 'all'); fs.writeFileSync(getFixturePath('ignored.txt'), Date.now()); fs.writeFileSync(getFixturePath('ignored-option.txt'), Date.now()); - await fsunlink(getFixturePath('ignored.txt')); - await fsunlink(getFixturePath('ignored-option.txt')); + await fs_unlink(getFixturePath('ignored.txt')); + await fs_unlink(getFixturePath('ignored-option.txt')); await delay(win32Polling010 ? 1000 : undefined); await write(getFixturePath('change.txt'), 'change'); await waitFor([spy.withArgs('change', 'change.txt')]); @@ -1692,7 +1696,7 @@ const runTests = function(baseopts) { const spy = await aspy(stdWatcher(), 'all'); await write(testPath, 'hello'); await delay(400); - await fsunlink(testPath); + await fs_unlink(testPath); await delay(400); spy.should.not.have.been.calledWith(sinon.match.string, testPath); }); @@ -1700,7 +1704,7 @@ const runTests = function(baseopts) { const testPath = getFixturePath('subdir/add.txt'); const filename = sysPath.basename(testPath); options.cwd = sysPath.dirname(testPath); - await fs.promises.mkdir(options.cwd); + await fs_mkdir(options.cwd); await delay(win32Polling010 ? 900 : 200); const spy = await aspy(stdWatcher(), 'all'); @@ -1721,14 +1725,14 @@ const runTests = function(baseopts) { const testPath = getFixturePath('subdir/add.txt'); const filename = sysPath.basename(testPath); options.cwd = sysPath.dirname(testPath); - await fs.promises.mkdir(options.cwd); + await fs_mkdir(options.cwd); await delay(); await write(testPath, 'hello'); await delay(); const spy = await aspy(stdWatcher(), 'all'); await write(testPath, 'edit'); await delay(); - await fsunlink(testPath); + await fs_unlink(testPath); await waitFor([spy.withArgs('unlink')]); spy.should.have.been.calledWith('unlink', filename); if (win32Polling010) return true; @@ -1771,7 +1775,7 @@ const runTests = function(baseopts) { // await waitFor([_fs.stat]); // // Once stat call is made, it will take some time to return. Meanwhile, unlink // // the file and wait for that to be noticed. - // await fsunlink(testPath); + // await fs_unlink(testPath); // await waitFor([spy.withArgs('unlink')]); // await delay(400); @@ -1872,7 +1876,7 @@ const runTests = function(baseopts) { '..': [subdir.toString()], 'subdir': [] }; - await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await fs_mkdir(getFixturePath('subdir'), PERM_ARR); await aspy(stdWatcher(), 'ready'); expect(watcher.getWatched()).to.deep.equal(expected); }); @@ -1881,7 +1885,7 @@ const runTests = function(baseopts) { before(closeWatchers); beforeEach(async () => { options.ignoreInitial = true; - await fs.promises.mkdir(getFixturePath('subdir'), PERM_ARR); + await fs_mkdir(getFixturePath('subdir'), PERM_ARR); await delay(); }); it('should stop watching unwatched paths', async () => { @@ -1910,7 +1914,7 @@ const runTests = function(baseopts) { watcher.unwatch([subdirRel, getGlobPath('unl*')]); await delay(); - await fsunlink(getFixturePath('unlink.txt')); + await fs_unlink(getFixturePath('unlink.txt')); await write(getFixturePath('subdir/add.txt'), Date.now()); await write(getFixturePath('change.txt'), Date.now()); await waitFor([spy.withArgs('change')]); @@ -1965,7 +1969,7 @@ const runTests = function(baseopts) { watcher.unwatch(['subdir', getFixturePath('unlink.txt')]); await delay(); - await fsunlink(getFixturePath('unlink.txt')); + await fs_unlink(getFixturePath('unlink.txt')); await write(getFixturePath('subdir/add.txt'), Date.now()); await write(getFixturePath('change.txt'), Date.now()); await waitFor([spy]); @@ -1992,8 +1996,8 @@ const runTests = function(baseopts) { }); }); await aspy(watcher); - await fs.promises.writeFile(getFixturePath('add.txt'), 'hello'); - await fsunlink(getFixturePath('add.txt')); + await write(getFixturePath('add.txt'), 'hello'); + await fs_unlink(getFixturePath('add.txt')); }); }); it('should not prevent the process from exiting', async () => { From 2d21d86977f848226c07b8c796152001735367e2 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 12 Feb 2019 04:54:17 -0800 Subject: [PATCH 08/10] Drop appveyor node 6. --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9a6de83b..828cc03a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,6 @@ environment: matrix: - nodejs_version: "10" - nodejs_version: "8" - - nodejs_version: "6" # matrix: # allow_failures: From 3eaa5e59425378d4547bd6ba008e02914208d7df Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Fri, 22 Mar 2019 13:23:17 +0200 Subject: [PATCH 09/10] Remove path-is-absolute. --- index.js | 9 ++++----- package.json | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 882da405..1c25c622 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,6 @@ const asyncEach = require('async-each'); const anymatch = require('anymatch'); const globParent = require('glob-parent'); const isGlob = require('is-glob'); -const isAbsolute = require('path-is-absolute'); const inherits = require('inherits'); const braces = require('braces'); const normalizePath = require('normalize-path'); @@ -299,7 +298,7 @@ FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit let timeoutHandler; let fullPath = path; - if (this.options.cwd && !isAbsolute(path)) { + if (this.options.cwd && !sysPath.isAbsolute(path)) { fullPath = sysPath.join(this.options.cwd, path); } @@ -362,7 +361,7 @@ FSWatcher.prototype._isIgnored = function(path, stats) { if (cwd && ignored) { ignored = ignored.map(function (path) { if (typeof path !== 'string') return path; - return upath.normalize(isAbsolute(path) ? path : sysPath.join(cwd, path)); + return upath.normalize(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path)); }); } const paths = arrify(ignored) @@ -603,7 +602,7 @@ FSWatcher.prototype.add = function(paths, _origAdd, _internal) { if (cwd) paths = paths.map(function(path) { let absPath; - if (isAbsolute(path)) { + if (sysPath.isAbsolute(path)) { absPath = path; } else if (path[0] === '!') { absPath = '!' + sysPath.join(cwd, path.substring(1)); @@ -670,7 +669,7 @@ FSWatcher.prototype.unwatch = function(paths) { paths.forEach(function(path) { // convert to absolute path unless relative path already matches - if (!isAbsolute(path) && !this._closers[path]) { + if (!sysPath.isAbsolute(path) && !this._closers[path]) { if (this.options.cwd) path = sysPath.join(this.options.cwd, path); path = sysPath.resolve(path); } diff --git a/package.json b/package.json index 10581345..b94da4a0 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.0" }, From 93ce44dbae156fd9485d8bb5db4678dd05243cd2 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Fri, 22 Mar 2019 14:25:39 +0200 Subject: [PATCH 10/10] Use class syntax everywhere. Drop inherits pkg. --- index.js | 97 ++++++++++++++++++----------------------- lib/fsevents-handler.js | 21 ++++----- lib/nodefs-handler.js | 27 +++++------- package.json | 1 - 4 files changed, 64 insertions(+), 82 deletions(-) diff --git a/index.js b/index.js index faa11e5b..6fcb94e5 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,6 @@ const asyncEach = require('async-each'); const anymatch = require('anymatch'); const globParent = require('glob-parent'); const isGlob = require('is-glob'); -const inherits = require('inherits'); const braces = require('braces'); const normalizePath = require('normalize-path'); const upath = require('upath'); @@ -14,14 +13,10 @@ const upath = require('upath'); const NodeFsHandler = require('./lib/nodefs-handler'); const FsEventsHandler = require('./lib/fsevents-handler'); -const arrify = function(value) { - if (value == null) return []; - return Array.isArray(value) ? value : [value]; -}; +const arrify = (value = []) => Array.isArray(value) ? value : [value]; -const flatten = function(list, result) { - if (result == null) result = []; - list.forEach(function(item) { +const flatten = (list, result = []) => { + list.forEach(item => { if (Array.isArray(item)) { flatten(item, result); } else { @@ -31,10 +26,8 @@ const flatten = function(list, result) { return result; }; -// Little isString util for use in Array#every. -const isString = function(thing) { - return typeof thing === 'string'; -}; +const dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/; +const replacerRe = /^\.[\/\\]/; // Public: Main class. // Watches files & directories for changes. @@ -53,8 +46,10 @@ const isString = function(thing) { // .on('unlink', path => console.log('File', path, 'was removed')) // .on('all', (event, path) => console.log(path, ' emitted ', event)) // -function FSWatcher(_opts) { - EventEmitter.call(this); +class FSWatcher extends EventEmitter { +// Not indenting methods for history sake; for now. +constructor(_opts) { + super(); const opts = {}; // in case _opts that is passed in is a frozen object if (_opts) for (const opt in _opts) opts[opt] = _opts[opt]; @@ -148,7 +143,6 @@ function FSWatcher(_opts) { Object.freeze(opts); } -inherits(FSWatcher, EventEmitter); // Common helpers // -------------- @@ -161,7 +155,7 @@ inherits(FSWatcher, EventEmitter); // // Returns the error if defined, otherwise the value of the // FSWatcher instance's `closed` flag -FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { +_emit(event, path, val1, val2, val3) { if (this.options.cwd) path = sysPath.relative(this.options.cwd, path); const args = [event, path]; if (val3 !== undefined) args.push(val1, val2, val3); @@ -221,7 +215,7 @@ FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { } return this; -}; +} // Private method: Common handler for errors // @@ -229,7 +223,7 @@ FSWatcher.prototype._emit = function(event, path, val1, val2, val3) { // // Returns the error if defined, otherwise the value of the // FSWatcher instance's `closed` flag -FSWatcher.prototype._handleError = function(error) { +_handleError(error) { const code = error && error.code; const ipe = this.options.ignorePermissionErrors; if (error && @@ -238,7 +232,7 @@ FSWatcher.prototype._handleError = function(error) { (!ipe || (code !== 'EPERM' && code !== 'EACCES')) ) this.emit('error', error); return error || this.closed; -}; +} // Private method: Helper utility for throttling // @@ -247,7 +241,7 @@ FSWatcher.prototype._handleError = function(error) { // * timeout - int, duration of time to suppress duplicate actions // // Returns throttle tracking object or false if action should be suppressed -FSWatcher.prototype._throttle = function(action, path, timeout) { +_throttle(action, path, timeout) { if (!(action in this._throttled)) { this._throttled[action] = Object.create(null); } @@ -265,7 +259,7 @@ FSWatcher.prototype._throttle = function(action, path, timeout) { const timeoutObject = setTimeout(clear, timeout); throttled[path] = {timeoutObject: timeoutObject, clear: clear, count: 0}; return throttled[path]; -}; +} // Private method: Awaits write operation to finish // @@ -275,7 +269,7 @@ FSWatcher.prototype._throttle = function(action, path, timeout) { // * awfEmit - function, to be called when ready for event to be emitted // Polls a newly created file for size variations. When files size does not // change for 'threshold' milliseconds calls callback. -FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit) { +_awaitWriteFinish(path, threshold, event, awfEmit) { let timeoutHandler; let fullPath = path; @@ -324,7 +318,7 @@ FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit this.options.awaitWriteFinish.pollInterval ); } -}; +} // Private method: Determines whether user has asked to ignore this path // @@ -332,8 +326,7 @@ FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit // * stats - object, result of fs_stat // // Returns boolean -const dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/; -FSWatcher.prototype._isIgnored = function(path, stats) { +_isIgnored(path, stats) { if (!this._userIgnored) { const cwd = this.options.cwd; let ignored = this.options.ignored; @@ -355,7 +348,7 @@ FSWatcher.prototype._isIgnored = function(path, stats) { } return this._userIgnored([path, stats]); -}; +} // Private method: Provides a set of common helpers and properties relating to // symlink and glob handling @@ -364,8 +357,7 @@ FSWatcher.prototype._isIgnored = function(path, stats) { // * depth - int, at any depth > 0, this isn't a glob // // Returns object containing helpers for this path -const replacerRe = /^\.[\/\\]/; -FSWatcher.prototype._getWatchHelpers = function(path, depth) { +_getWatchHelpers(path, depth) { path = path.replace(replacerRe, ''); const watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path); const fullWatchPath = sysPath.resolve(watchPath); @@ -449,7 +441,7 @@ FSWatcher.prototype._getWatchHelpers = function(path, depth) { filterPath: filterPath, filterDir: filterDir }; -}; +} // Directory helpers // ----------------- @@ -459,7 +451,7 @@ FSWatcher.prototype._getWatchHelpers = function(path, depth) { // * directory - string, path of the directory // // Returns the directory's tracking object -FSWatcher.prototype._getWatchedDir = function(directory) { +_getWatchedDir(directory) { const dir = sysPath.resolve(directory); const watcherRemove = this._remove.bind(this); if (!(dir in this._watched)) this._watched[dir] = { @@ -479,7 +471,7 @@ FSWatcher.prototype._getWatchedDir = function(directory) { children: function() {return Object.keys(this._items);} }; return this._watched[dir]; -}; +} // File helpers // ------------ @@ -490,9 +482,9 @@ FSWatcher.prototype._getWatchedDir = function(directory) { // * stats - object, result of fs_stat // // Returns boolean -FSWatcher.prototype._hasReadPermissions = function(stats) { +_hasReadPermissions(stats) { return Boolean(4 & parseInt(((stats && stats.mode) & 0x1ff).toString(8)[0], 10)); -}; +} // Private method: Handles emitting unlink events for // files and directories, and via recursion, for @@ -502,7 +494,7 @@ FSWatcher.prototype._hasReadPermissions = function(stats) { // * item - string, base path of item/directory // // Returns nothing -FSWatcher.prototype._remove = function(directory, item) { +_remove(directory, item) { // if what is being deleted is a directory, get that directory's paths // for recursive deleting and cleaning of watched object // if it is not a directory, nestedDirectoryChildren will be empty array @@ -553,14 +545,14 @@ FSWatcher.prototype._remove = function(directory, item) { if (!this.options.useFsEvents) { this._closePath(path); } -}; +} -FSWatcher.prototype._closePath = function(path) { +_closePath(path) { if (!this._closers[path]) return; this._closers[path](); delete this._closers[path]; this._getWatchedDir(sysPath.dirname(path)).remove(sysPath.basename(path)); -}; +} // Public method: Adds paths to be watched on an existing FSWatcher instance @@ -569,13 +561,13 @@ FSWatcher.prototype._closePath = function(path) { // * _internal - private boolean, indicates a non-user add // Returns an instance of FSWatcher for chaining. -FSWatcher.prototype.add = function(paths, _origAdd, _internal) { +add(paths, _origAdd, _internal) { const disableGlobbing = this.options.disableGlobbing; const cwd = this.options.cwd; this.closed = false; paths = flatten(arrify(paths)); - if (!paths.every(isString)) { + if (!paths.every(p => typeof p === 'string')) { throw new TypeError('Non-string provided as watch path: ' + paths); } @@ -653,14 +645,14 @@ FSWatcher.prototype.add = function(paths, _origAdd, _internal) { } return this; -}; +} // Public method: Close watchers or start ignoring events from specified paths. // * paths - string or array of strings, file/directory paths and/or globs // Returns instance of FSWatcher for chaining. -FSWatcher.prototype.unwatch = function(paths) { +unwatch(paths) { if (this.closed) return this; paths = flatten(arrify(paths)); @@ -684,12 +676,12 @@ FSWatcher.prototype.unwatch = function(paths) { }, this); return this; -}; +} // Public method: Close watchers and remove all listeners from watched paths. // Returns instance of FSWatcher for chaining. -FSWatcher.prototype.close = function() { +close() { if (this.closed) return this; this.closed = true; @@ -701,28 +693,25 @@ FSWatcher.prototype.close = function() { this.removeAllListeners(); return this; -}; +} // Public method: Expose list of watched paths // Returns object w/ dir paths as keys and arrays of contained paths as values. -FSWatcher.prototype.getWatched = function() { +getWatched() { const watchList = {}; Object.keys(this._watched).forEach(function(dir) { const key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir; watchList[key || '.'] = Object.keys(this._watched[dir]._items).sort(); }.bind(this)); return watchList; -}; +} -// Attach watch handler prototype methods -function importHandler(handler) { - Object.keys(handler.prototype).forEach(function(method) { - FSWatcher.prototype[method] = handler.prototype[method]; - }); } -importHandler(NodeFsHandler); -if (FsEventsHandler.canUse()) importHandler(FsEventsHandler); + +// Attach watch handler prototype methods +Object.assign(FSWatcher.prototype, NodeFsHandler); +if (FsEventsHandler.canUse()) Object.assign(FSWatcher.prototype, FsEventsHandler); // Export FSWatcher class exports.FSWatcher = FSWatcher; @@ -733,6 +722,6 @@ exports.FSWatcher = FSWatcher; // * options - object, chokidar options // Returns an instance of FSWatcher for chaining. -exports.watch = function(paths, options) { +exports.watch = (paths, options) => { return new FSWatcher(options).add(paths); }; diff --git a/lib/fsevents-handler.js b/lib/fsevents-handler.js index 22ee4846..dd16b238 100644 --- a/lib/fsevents-handler.js +++ b/lib/fsevents-handler.js @@ -124,9 +124,7 @@ function couldConsolidate(path) { } // returns boolean indicating whether fsevents can be used -function canUse() { - return fsevents && Object.keys(FSEventsWatchers).length < 128; -} +const canUse = () => fsevents && Object.keys(FSEventsWatchers).length < 128; // determines subdirectory traversal levels from root to path function depth(path, root) { @@ -137,7 +135,7 @@ function depth(path, root) { // fake constructor for attaching fsevents-specific prototype methods that // will be copied to FSWatcher's prototype -function FsEventsHandler() {} +const FsEventsHandler = { // Private method: Handle symlinks encountered during directory scan @@ -147,8 +145,7 @@ function FsEventsHandler() {} // * globFilter - function, path filter in case a glob pattern was provided // Returns close function for the watcher instance -FsEventsHandler.prototype._watchWithFsEvents = -function(watchPath, realPath, transform, globFilter) { +_watchWithFsEvents(watchPath, realPath, transform, globFilter) { if (this._isIgnored(watchPath)) return; const watchCallback = function watchCallback(fullPath, flags, info) { if ( @@ -258,7 +255,7 @@ function(watchPath, realPath, transform, globFilter) { this._emitReady(); return closer; -}; +}, // Private method: Handle symlinks encountered during directory scan @@ -268,8 +265,7 @@ function(watchPath, realPath, transform, globFilter) { // * curDepth - int, level of subdirectories traversed to where symlink is // Returns nothing -FsEventsHandler.prototype._handleFsEventsSymlink = -function _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { +_handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { // don't follow the same symlink more than once if (this._symlinkPaths[fullPath]) return; else this._symlinkPaths[fullPath] = true; @@ -296,7 +292,7 @@ function _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { return transform(aliasedPath); }, false, curDepth); }.bind(this)); -}; +}, // Private method: Handle added path with fsevents @@ -306,8 +302,7 @@ function _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { // * priorDepth - int, level of subdirectories already traversed // Returns nothing -FsEventsHandler.prototype._addToFsEvents = -function(path, transform, forceAdd, priorDepth) { +_addToFsEvents(path, transform, forceAdd, priorDepth) { // applies transform if provided, otherwise returns same value const processPath = typeof transform === 'function' ? @@ -399,6 +394,8 @@ function(path, transform, forceAdd, priorDepth) { fs.realpath(wh.watchPath, initWatch); } } +}, + }; module.exports = FsEventsHandler; diff --git a/lib/nodefs-handler.js b/lib/nodefs-handler.js index e90b2496..2e3c8f60 100644 --- a/lib/nodefs-handler.js +++ b/lib/nodefs-handler.js @@ -202,7 +202,7 @@ function setFsWatchFileListener(path, fullPath, options, handlers) { // fake constructor for attaching nodefs-specific prototype methods that // will be copied to FSWatcher's prototype -function NodeFsHandler() {} +const NodeFsHandler = { // Private method: Watch file for changes with fs_watchFile or fs_watch. @@ -210,8 +210,7 @@ function NodeFsHandler() {} // * listener - function, to be executed on fs change. // Returns close function for the watcher instance -NodeFsHandler.prototype._watchWithNodeFs = -function(path, listener) { +_watchWithNodeFs(path, listener) { const directory = sysPath.dirname(path); const basename = sysPath.basename(path); const parent = this._getWatchedDir(directory); @@ -236,7 +235,7 @@ function(path, listener) { }); } return closer; -}; +}, // Private method: Watch a file and emit add event if warranted @@ -246,8 +245,7 @@ function(path, listener) { // * callback - function, called when done processing as a newly seen file // Returns close function for the watcher instance -NodeFsHandler.prototype._handleFile = -function(file, stats, initialAdd, callback) { +_handleFile(file, stats, initialAdd, callback) { const dirname = sysPath.dirname(file); const basename = sysPath.basename(file); const parent = this._getWatchedDir(dirname); @@ -295,7 +293,7 @@ function(file, stats, initialAdd, callback) { if (callback) callback(); return closer; -}; +}, // Private method: Handle symlinks encountered while reading a dir @@ -305,8 +303,7 @@ function(file, stats, initialAdd, callback) { // * item - string, basename of this item // Returns true if no more processing is needed for this entry. -NodeFsHandler.prototype._handleSymlink = -function(entry, directory, path, item) { +_handleSymlink(entry, directory, path, item) { const full = entry.fullPath; const dir = this._getWatchedDir(directory); @@ -332,7 +329,7 @@ function(entry, directory, path, item) { // don't follow the same symlink more than once if (this._symlinkPaths[full]) return true; else this._symlinkPaths[full] = true; -}; +}, // Private method: Read directory to add / remove files from `@watched` list // and re-read it on change. @@ -346,8 +343,7 @@ function(entry, directory, path, item) { // * callback - function, called when dir scan is complete // Returns close function for the watcher instance -NodeFsHandler.prototype._handleDir = -function(dir, stats, initialAdd, depth, target, wh, callback) { +_handleDir(dir, stats, initialAdd, depth, target, wh, callback) { const parentDir = this._getWatchedDir(sysPath.dirname(dir)); const tracked = parentDir.has(sysPath.basename(dir)); if (!(initialAdd && this.options.ignoreInitial) && !target && !tracked) { @@ -436,7 +432,7 @@ function(dir, stats, initialAdd, depth, target, wh, callback) { callback(); } return closer; -}; +}, // Private method: Handle added file, directory, or glob pattern. // Delegates call to _handleFile / _handleDir after checks. @@ -448,8 +444,7 @@ function(dir, stats, initialAdd, depth, target, wh, callback) { // * callback - function, indicates whether the path was found or not // Returns nothing -NodeFsHandler.prototype._addToNodeFs = -function(path, initialAdd, priorWh, depth, target, callback) { +_addToNodeFs(path, initialAdd, priorWh, depth, target, callback) { if (!callback) callback = Function.prototype; const ready = this._emitReady; if (this._isIgnored(path) || this.closed) { @@ -498,6 +493,8 @@ function(path, initialAdd, priorWh, depth, target, callback) { if (closer) this._closers[path] = closer; callback(null, false); }.bind(this)); +}, + }; module.exports = NodeFsHandler; diff --git a/package.json b/package.json index 665ff9d7..d994a1ed 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "async-each": "^1.0.1", "braces": "^2.3.2", "glob-parent": "^3.1.0", - "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", "normalize-path": "^3.0.0",