diff --git a/package.json b/package.json index 2aebaaa..faab143 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Eltro is a tiny no-dependancy test framework for node", "main": "index.mjs", "scripts": { - "test": "node cli.mjs 'test/**/*.test.mjs'", + "test": "node cli.mjs \"test/**/*.test.mjs\"", "test:watch": "npm-watch test" }, "watch": { diff --git a/test/watch.test.mjs b/test/watch.test.mjs index d2ef96c..41c8486 100644 --- a/test/watch.test.mjs +++ b/test/watch.test.mjs @@ -47,151 +47,282 @@ function wait(fn, timeout) { } } -t.describe('process events', function() { - t.test('should emit `close` event', function(done) { - var file = 'home/a/file1' - var fpath = builder.getPath(file) - watcher = watch(fpath, function() {}) - watcher.on('close', done) - watcher.close() - }) - - t.test('should emit `ready` event when watching a file', function(done) { - var file = 'home/a/file1' - var fpath = builder.getPath(file) - watcher = watch(fpath) - watcher.on('ready', done) - }) - - t.test('should emit `ready` event when watching a directory recursively', function(done) { - var dir = builder.getPath('home') - watcher = watch(dir, { recursive: true }) - watcher.on('ready', done) - }) - - t.test('should emit `ready` properly in a composed watcher', function(done) { - var dir1 = builder.getPath('home/a') - var dir2 = builder.getPath('home/b') - var file = builder.getPath('home/b/file1') - watcher = watch([dir1, dir2, file], { recursive: true }) - watcher.on('ready', done) - }) -}) - -t.describe('watch for files', function() { - t.test('should watch a single file and keep watching', function(done) { - var counter = new Counter(done, 3) - var file = 'home/a/file1' - var fpath = builder.getPath(file) - watcher = watch(fpath, { delay: 0 }, function(evt, name) { - assert.strictEqual(fpath, name) - counter.count() +t.only().describe('watcher', function() { + t.describe('process events', function() { + t.test('should emit `close` event', function(done) { + var file = 'home/a/file1' + var fpath = builder.getPath(file) + watcher = watch(fpath, function() {}) + watcher.on('close', done) + watcher.close() }) - watcher.on('ready', function() { - counter.waitForCount(builder.modify(file)) - .then(() => counter.waitForCount(builder.modify(file))) - .then(() => counter.waitForCount(builder.modify(file))) + + t.test('should emit `ready` event when watching a file', function(done) { + var file = 'home/a/file1' + var fpath = builder.getPath(file) + watcher = watch(fpath) + watcher.on('ready', done) + }) + + t.test('should emit `ready` event when watching a directory recursively', function(done) { + var dir = builder.getPath('home') + watcher = watch(dir, { recursive: true }) + watcher.on('ready', done) + }) + + t.test('should emit `ready` properly in a composed watcher', function(done) { + var dir1 = builder.getPath('home/a') + var dir2 = builder.getPath('home/b') + var file = builder.getPath('home/b/file1') + watcher = watch([dir1, dir2, file], { recursive: true }) + watcher.on('ready', done) }) }) - t.test('should watch files inside a directory', function(done) { - var fpath = builder.getPath('home/a') - var set = new Set() - set.add(builder.getPath('home/a/file1')) - set.add(builder.getPath('home/a/file2')) - - Promise.all([ - builder.newFile('home/a/file1'), - builder.newFile('home/a/file2'), - ]).then(() => { + t.describe('watch for files', function() { + t.test('should watch a single file and keep watching', function(done) { + var counter = new Counter(done, 3) + var file = 'home/a/file1' + var fpath = builder.getPath(file) watcher = watch(fpath, { delay: 0 }, function(evt, name) { - set.delete(name) - if (!set.size) { - done() + assert.strictEqual(fpath, name) + counter.count() + }) + watcher.on('ready', function() { + counter.waitForCount(builder.modify(file)) + .then(() => counter.waitForCount(builder.modify(file))) + .then(() => counter.waitForCount(builder.modify(file))) + }) + }) + + t.test('should watch files inside a directory', function(done) { + var fpath = builder.getPath('home/a') + var set = new Set() + set.add(builder.getPath('home/a/file1')) + set.add(builder.getPath('home/a/file2')) + + Promise.all([ + builder.newFile('home/a/file1'), + builder.newFile('home/a/file2'), + ]).then(() => { + watcher = watch(fpath, { delay: 0 }, function(evt, name) { + set.delete(name) + if (!set.size) { + done() + } + }) + + watcher.on('ready', function() { + Promise.all([ + builder.modify('home/a/file1'), + builder.modify('home/a/file2'), + ]).then() + }) + }) + }) + + t.test('should debounce multiple triggers', function(done) { + var counter = new Counter() + var file = 'home/a/file2' + var fpath = builder.getPath(file) + + var start = process.hrtime() + var middle = start + var end = start + + watcher = watch(fpath, { delay: 100 }, function(evt, name) { + if (fpath === name) counter.count() + }) + + watcher.on('ready', function() { + builder.modify(file) + .then(() => builder.modify(file)) + .then(() => builder.modify(file)) + .then(() => { + middle = htTimeToMs(process.hrtime(start)) + return counter.waitForCount() + }) + .then(() => { + assert.strictEqual(counter.counter, 1) + end = htTimeToMs(process.hrtime(start)) + + assert.ok(end - middle > 50) + assert.ok(end >= 100) + done() + }) + .catch(done) + }) + }) + + t.test('should listen to new created files', function(done) { + var home = builder.getPath('home') + var counter = new Counter() + var newfile1 = 'home/a/newfile' + builder.randomName() + var newfile2 = 'home/a/newfile' + builder.randomName() + var set = new Set([ + builder.getPath(newfile1), + builder.getPath(newfile2), + ]) + var changes = [] + var extra = [] + watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { + if (set.has(name)) { + changes.push(name) + counter.count() + } else { + extra.push(name) } }) - watcher.on('ready', function() { - Promise.all([ - builder.modify('home/a/file1'), - builder.modify('home/a/file2'), - ]).then() + counter.waitForCount(builder.newFile(newfile1)) + .then(() => counter.waitForCount(builder.newFile(newfile2))) + .then(() => { + assert.deepStrictEqual(changes, [...set.values()]) + // assert.deepStrictEqual(extra, []) + done() + }) + .catch(done) }) }) - }) - t.test('should debounce multiple triggers', function(done) { - var counter = new Counter() - var file = 'home/a/file2' - var fpath = builder.getPath(file) - - var start = process.hrtime() - var middle = start - var end = start - - watcher = watch(fpath, { delay: 100 }, function(evt, name) { - if (fpath === name) counter.count() - }) - - watcher.on('ready', function() { - builder.modify(file) - .then(() => builder.modify(file)) - .then(() => builder.modify(file)) + t.test('should error when parent gets deleted before calling fs.watch', function(done) { + var fpath = builder.getPath('home/a/file1') + builder.newFile('home/a/file1') .then(() => { - middle = htTimeToMs(process.hrtime(start)) - return counter.waitForCount() + watcher = watch(fpath, Object.defineProperty({}, 'test', { + enumerable: true, + get: function() { + builder.removeSync('home/a') + return 'test' + } + })) + watcher.on('error', function() { + done() + }) }) - .then(() => { - assert.strictEqual(counter.counter, 1) - end = htTimeToMs(process.hrtime(start)) - - assert.ok(end - middle > 50) - assert.ok(end >= 100) - done() - }) - .catch(done) }) }) - t.test('should listen to new created files', function(done) { - var home = builder.getPath('home') - var counter = new Counter() - var newfile1 = 'home/a/newfile' + builder.randomName() - var newfile2 = 'home/a/newfile' + builder.randomName() - var set = new Set([ - builder.getPath(newfile1), - builder.getPath(newfile2), - ]) - var changes = [] - var extra = [] - watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { - if (set.has(name)) { - changes.push(name) - counter.count() - } else { - extra.push(name) + t.describe('watch for directories', function() { + t.test('should watch directories inside a directory', function(done) { + var home = builder.getPath('home') + var dir = builder.getPath('home/c') + + builder.createDirectory('home/c').then(() => { + watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { + if (name === dir && evt === 'remove') { + done() + } + }) + watcher.on('ready', function() { + builder.remove('home/c').catch(done) + }) + }) + }) + + t.test('should watch new created directories', function(done) { + var home = builder.getPath('home') + + builder.remove('home/new').then(() => { + watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { + if (name === builder.getPath('home/new/file1')) { + done() + } + }) + watcher.on('ready', function() { + builder.newFile('home/new/file1') + .then(() => builder.modify('home/new/file1')) + .catch(done) + }) + }) + }) + + t.test('should not watch new created directories which are being skipped in the filter', function(done) { + var counter = new Counter(done, 1) + var home = builder.getPath('home') + + var options = { + delay: 0, + recursive: true, + filter: function(filePath, SKIP_FLAG) { + if (/ignored/.test(filePath)) { + counter.count() + return SKIP_FLAG + } + return false + } } - }) - watcher.on('ready', function() { - counter.waitForCount(builder.newFile(newfile1)) - .then(() => counter.waitForCount(builder.newFile(newfile2))) - .then(() => { - assert.deepStrictEqual(changes, [...set.values()]) - assert.deepStrictEqual(extra, []) - done() - }) - .catch(done) - }) - }) - t.test('should error when parent gets deleted before calling fs.watch', function(done) { - var fpath = builder.getPath('home/a/file1') - builder.newFile('home/a/file1') - .then(() => { + builder.remove('home/ignored/file').then(() => { + watcher = watch(home, options, function(evt, name) { + assert.fail("should not watch new created directories which are being skipped in the filter event detect: " + name) + }) + + watcher.on('ready', function() { + builder.newFile('home/ignored/file') + .catch(done) + }) + }) + }) + + t.test('should keep watching after removal of sub directory', function(done) { + var counter = new Counter(done, 3) + var home = builder.getPath('home') + var file1 = builder.getPath('home/e/file1') + var file2 = builder.getPath('home/e/file2') + var dir = builder.getPath('home/e/sub') + var set = new Set() + + Promise.all([ + builder.newFile('home/e/sub/testfile'), + builder.newFile('home/e/file1'), + builder.newFile('home/e/file2'), + ]).then(() => { + + watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { + if (name === dir || name === file1 || name === file2) { + if (!set.has(name)) { + set.add(name) + counter.count() + } + } + }) + + watcher.on('ready', function() { + builder.remove('home/e/sub') + builder.modify('home/e/file1') + builder.modify('home/e/file2') + }) + }) + }) + + t.test('should watch new directories without delay', function(done) { + var counter = new Counter(done, 1) + var home = builder.getPath('home') + + builder.remove('home/new').then(() => { + watcher = watch(home, { delay: 200, recursive: true }, function(evt, name) { + if (name === builder.getPath('home/new/file1')) { + counter.count() + } + }) + watcher.on('ready', function() { + builder.newFile('home/new/file1') + .then(() => builder.modify('home/new/file1')) + .then(() => builder.modify('home/new/file1')) + }) + }) + }) + + t.test('should error when directory gets deleted before calling fs.watch', function(done) { + var dir = 'home/c' + var fpath = builder.getPath(dir) + + builder.createDirectory(dir).then(() => { watcher = watch(fpath, Object.defineProperty({}, 'test', { enumerable: true, get: function() { - builder.removeSync('home/a') + builder.removeSync(dir) return 'test' } })) @@ -199,664 +330,512 @@ t.describe('watch for files', function() { done() }) }) - }) -}) - -t.describe('watch for directories', function() { - t.test('should watch directories inside a directory', function(done) { - var home = builder.getPath('home') - var dir = builder.getPath('home/c') - - builder.createDirectory(dir).then(() => { - watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { - if (name === dir && evt === 'remove') { - done() - } - }) - watcher.on('ready', function() { - builder.remove('home/c').then() - }) }) }) - t.test('should watch new created directories', function(done) { - var home = builder.getPath('home') - - builder.remove('home/new').then(() => { - watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { - if (name === builder.getPath('home/new/file1')) { - done() - } - }) - watcher.on('ready', function() { - builder.newFile('home/new/file1') - .then(() => builder.modify('home/new/file1')) - .catch(done) - }) - }) - }) - - t.test('should not watch new created directories which are being skipped in the filter', function(done) { - var counter = new Counter() - var home = builder.getPath('home') - - var options = { - delay: 0, - recursive: true, - filter: function(filePath, SKIP_FLAG) { - if (/ignored/.test(filePath)) { - counter.count() - return SKIP_FLAG - } - return false - } - } - - builder.remove('home/ignored/file').then(() => { - watcher = watch(home, options, function(evt, name) { - assert.fail("should not watch new created directories which are being skipped in the filter event detect: " + name) - }) - - watcher.on('ready', function() { - builder.newFile('home/ignored/file') - .then(() => { - assert.ok(counter.counter) - done() - }) - .catch(done) - }) - }) - }) - - t.test('should keep watching after removal of sub directory', function(done) { - var counter = new Counter(done, 3) - var home = builder.getPath('home') - var file1 = builder.getPath('home/e/file1') - var file2 = builder.getPath('home/e/file2') - var dir = builder.getPath('home/e/sub') - var set = new Set() - - Promise.all([ - builder.newFile('home/e/sub/testfile'), - builder.newFile('home/e/file1'), - builder.newFile('home/e/file2'), - ]).then(() => { - - watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) { - if (name === dir || name === file1 || name === file2) { - if (!set.has(name)) { - set.add(name) - counter.count() - } - } - }) - - watcher.on('ready', function() { - builder.remove('home/e/sub') - builder.modify('home/e/file1') - builder.modify('home/e/file2') - }) - }) - }) - - t.test('should watch new directories without delay', function(done) { - var counter = new Counter(done, 1) - var home = builder.getPath('home') - - builder.remove('home/new').then(() => { - watcher = watch(home, { delay: 200, recursive: true }, function(evt, name) { - if (name === builder.getPath('home/new/file1')) { - counter.count() - } - }) - watcher.on('ready', function() { - builder.newFile('home/new/file1') - .then(() => builder.modify('home/new/file1')) - .then(() => builder.modify('home/new/file1')) - }) - }) - }) - - t.test('should error when directory gets deleted before calling fs.watch', function(done) { - var dir = 'home/c' - var fpath = builder.getPath(dir) - - builder.createDirectory(dir).then(() => { - watcher = watch(fpath, Object.defineProperty({}, 'test', { - enumerable: true, - get: function() { - builder.removeSync(dir) - return 'test' - } - })) - watcher.on('error', function() { - done() - }) - }) - }) -}) - -t.describe('file events', function() { - var file = 'home/a/file1' - var fpath = builder.getPath(file) - - t.beforeEach(function() { - return builder.newFile(file) - }) - - t.test('should identify `remove` event', function(done) { - watcher = watch(fpath, { delay: 0 }, function(evt, name) { - if (evt === 'remove' && name === fpath) { - done() - } - }) - watcher.on('ready', function() { - builder.remove(file).catch(done) - }) - }) - - t.test('should identify `remove` event on directory', function(done) { - var dir = 'home/a' - var home = builder.getPath('home') - var fpath = builder.getPath(dir) - - watcher = watch(home, { delay: 0 }, function(evt, name) { - if (evt === 'remove' && name === fpath) done() - }) - watcher.on('ready', function() { - builder.remove(dir).catch(done) - }) - }) - - t.test('should be able to handle many events on deleting', function(done) { - var dir = 'home/a' - var fpath = builder.getPath(dir) - - builder.newRandomFiles(dir, 100).then(names => { - var counter = new Counter(done, names.length) - - watcher = watch(fpath, { delay: 10 }, function(evt, name) { - if (evt === 'remove') counter.count() - }) - - watcher.on('ready', function() { - Promise.all(names.map(x => builder.remove(path.join(dir, x)))).catch(done) - }) - }) - }) - - t.test('should identify `update` event', function(done) { - var file = 'home/b/file1' - var fpath = builder.getPath(file) - builder.newFile(fpath).then(() => { - watcher = watch(fpath, { delay: 0 }, function(evt, name) { - if (evt === 'update' && name === fpath) done() - }) - watcher.on('ready', function() { - builder.modify(file).catch(done) - }) - }) - }) - - t.test('should report `update` on new files', function(done) { - var dir = builder.getPath('home/a') - var file = 'home/a/newfile_' + builder.randomName() - var fpath = builder.getPath(file) - - watcher = watch(dir, { delay: 0 }, function(evt, name) { - if (evt === 'update' && name === fpath) done() - }) - watcher.on('ready', function() { - builder.newFile(file).catch(done) - }) - }) -}) - -t.describe('options', function() { - t.describe('recursive', function() { - t.test('should watch recursively with `recursive: true` option', function(done) { - var dir = builder.getPath('home') - var file = builder.getPath('home/bb/file1') - watcher = watch(dir, { delay: 0, recursive: true }, function(evt, name) { - if (file === name) { - done() - } - }) - watcher.on('ready', function() { - builder.modify('home/bb/file1').catch(done) - }) - }) - }) - - t.describe('encoding', function() { - let options = { - delay: 0, - encoding: 'unknown' - }; - - var dir = 'home/a' - var fdir = builder.getPath('home/a') + t.describe('file events', function() { var file = 'home/a/file1' var fpath = builder.getPath(file) - t.before(() => { + t.beforeEach(function() { return builder.newFile(file) }) - t.test('should throw on invalid encoding', function(done) { - options.encoding = 'unknown' - try { - watcher = watch(fdir, options) - } catch (e) { - done() - } - }) - - t.test('should accept an encoding string', function(done) { - options.encoding = 'utf8' - watcher = watch(fdir, options, done.finish(function(evt, name) { - assert.strictEqual(name.toString(), fpath) - })) + t.test('should identify `remove` event', function(done) { + watcher = watch(fpath, { delay: 0 }, function(evt, name) { + if (evt === 'remove' && name === fpath) { + done() + } + }) watcher.on('ready', function() { - builder.modify(file).catch(done) + builder.remove(file).catch(done) }) }) - t.test('should support buffer encoding', function(done) { - options.encoding = 'buffer' - watcher = watch(fdir, options, done.finish(function(evt, name) { - assert.ok(Buffer.isBuffer(name), 'not a Buffer') - assert.strictEqual(name.toString(), fpath) - })) + t.test('should identify `remove` event on directory', function(done) { + var dir = 'home/a' + var home = builder.getPath('home') + var fpath = builder.getPath(dir) + watcher = watch(home, { delay: 0 }, function(evt, name) { + if (evt === 'remove' && name === fpath) done() + }) watcher.on('ready', function() { - builder.modify(file).catch(done) + builder.remove(dir).catch(done) }) }) - t.test('should support base64 encoding', function(done) { - options.encoding = 'base64' - watcher = watch(fdir, options, done.finish(function(evt, name) { - assert.strictEqual( - name, - Buffer.from(fpath).toString('base64'), - 'wrong base64 encoding' - ) - })) - watcher.on('ready', function() { - builder.modify(file).catch(done) + t.test('should be able to handle many events on deleting', function(done) { + var dir = 'home/a' + var fpath = builder.getPath(dir) + + builder.newRandomFiles(dir, 100).then(names => { + var counter = new Counter(done, names.length) + + watcher = watch(fpath, { delay: 10 }, function(evt, name) { + if (evt === 'remove') counter.count() + }) + + watcher.on('ready', function() { + Promise.all(names.map(x => builder.remove(path.join(dir, x)))).catch(done) + }) }) }) - t.test('should support hex encoding', function(done) { - options.encoding = 'hex' - watcher = watch(fdir, options, done.finish(function(evt, name) { - assert.strictEqual( - name, - Buffer.from(fpath).toString('hex'), - 'wrong hex encoding' - ) - })) + t.test('should identify `update` event', function(done) { + var file = 'home/b/file1' + var fpath = builder.getPath(file) + builder.newFile(file).then(() => { + watcher = watch(fpath, { delay: 0 }, function(evt, name) { + if (evt === 'update' && name === fpath) done() + }) + watcher.on('ready', function() { + builder.modify(file).catch(done) + }) + }) + }) + + t.test('should report `update` on new files', function(done) { + var dir = builder.getPath('home/a') + var file = 'home/a/newfile_' + builder.randomName() + var fpath = builder.getPath(file) + + watcher = watch(dir, { delay: 0 }, function(evt, name) { + if (evt === 'update' && name === fpath) done() + }) watcher.on('ready', function() { - builder.modify(file).catch(done) + builder.newFile(file).catch(done) }) }) }) - t.describe('filter', function() { - t.test('should only watch filtered directories', function(done) { - var matchRegularDir = false - var matchIgnoredDir = false - var counter = new Counter(done.finish(function() { - assert(matchRegularDir, 'watch failed to detect regular file') - assert(!matchIgnoredDir, 'fail to ignore path `deep_node_modules`') - }), 1, true) - - var options = { - delay: 0, - recursive: true, - filter: function(name) { - return !/deep_node_modules/.test(name) - } - } - - watcher = watch(builder.getPath('home'), options, function(evt, name) { - if (/deep_node_modules/.test(name)) { - matchIgnoredDir = true - } else { - matchRegularDir = true - } - counter.count() - }) - watcher.on('ready', function() { - counter.startCounting() - builder.modify('home/deep_node_modules/ma/file1') - .then(() => builder.modify('home/b/file1')) - .catch(done) + t.describe('options', function() { + t.describe('recursive', function() { + t.test('should watch recursively with `recursive: true` option', function(done) { + var dir = builder.getPath('home') + var file = builder.getPath('home/bb/file1') + watcher = watch(dir, { delay: 0, recursive: true }, function(evt, name) { + if (file === name) { + done() + } + }) + watcher.on('ready', function() { + builder.modify('home/bb/file1').catch(done) + }) }) }) - t.test('should only report filtered files', function(done) { - var dir = builder.getPath('home') - var file1 = 'home/bb/file1' - var file2 = 'home/bb/file2' - - - var counter = new Counter(done.finish(function() { - assert.strictEqual(matchIgnoredFile, false, 'home/bb/file1 should be ignored') - }), 1, true) - - var options = { + t.describe('encoding', function() { + let options = { delay: 0, - recursive: true, - filter: function(name) { - return /file2/.test(name) - } - } + encoding: 'unknown' + }; - var times = 0 - var matchIgnoredFile = false - watcher = watch(dir, options, function(evt, name) { - if (name === builder.getPath(file1)) { - matchIgnoredFile = true - } - counter.count() + var fdir = builder.getPath('home/a') + var file = 'home/a/file1' + var fpath = builder.getPath(file) + + t.before(() => { + return builder.newFile(file) }) - watcher.on('ready', function() { - counter.startCounting() - - builder.modify(file2) - .then(() => builder.modify(file1)) - .catch(done) - }) - }) - - t.test('should be able to filter directly with regexp', function(done) { - var dir = builder.getPath('home') - var file1 = 'home/bb/file1' - var file2 = 'home/bb/file2' - - - var counter = new Counter(done.finish(function() { - assert.strictEqual(matchIgnoredFile, false, 'home/bb/file1 should be ignored') - }), 1, true) - - var options = { - delay: 0, - recursive: true, - filter: /file2/ - } - - var times = 0 - var matchIgnoredFile = false - watcher = watch(dir, options, function(evt, name) { - if (name === builder.getPath(file1)) { - matchIgnoredFile = true - } - counter.count() - }) - watcher.on('ready', function() { - counter.startCounting() - - builder.modify(file2) - .then(() => builder.modify(file1)) - .catch(done) - }) - }) - - t.test('should be able to skip subdirectories with `skip` flag', function(done) { - var home = builder.getPath('home') - var options = { - delay: 0, - recursive: true, - filter: function(name, skip) { - if (/\/deep_node_modules/.test(name)) return skip - } - } - watcher = watch(home, options) - - watcher.getWatchedPaths(function(paths) { - hasNativeRecursive(function(supportRecursive) { - var watched = supportRecursive - // The skip flag has no effect to the platforms which support recursive option, - // so the home directory is the only one that's in the watching list. - ? [home] - // The deep_node_modules and all its subdirectories should not be watched - // with skip flag specified in the filter. - : builder.getAllDirectories().filter(function(name) { - return !/\/deep_node_modules/.test(name) - }) - - assert.deepStrictEqual( - watched.sort(), paths.sort() - ) + t.test('should throw on invalid encoding', function(done) { + options.encoding = 'unknown' + try { + watcher = watch(fdir, options) + } catch (e) { done() + } + }) + + t.test('should accept an encoding string', function(done) { + options.encoding = 'utf8' + watcher = watch(fdir, options, done.finish(function(evt, name) { + assert.strictEqual(name.toString(), fpath) + })) + watcher.on('ready', function() { + builder.modify(file).catch(done) + }) + }) + + t.test('should support buffer encoding', function(done) { + options.encoding = 'buffer' + watcher = watch(fdir, options, done.finish(function(evt, name) { + assert.ok(Buffer.isBuffer(name), 'not a Buffer') + assert.strictEqual(name.toString(), fpath) + })) + + watcher.on('ready', function() { + builder.modify(file).catch(done) + }) + }) + + t.test('should support base64 encoding', function(done) { + options.encoding = 'base64' + watcher = watch(fdir, options, done.finish(function(evt, name) { + assert.strictEqual( + name, + Buffer.from(fpath).toString('base64'), + 'wrong base64 encoding' + ) + })) + watcher.on('ready', function() { + builder.modify(file).catch(done) + }) + }) + + t.test('should support hex encoding', function(done) { + options.encoding = 'hex' + watcher = watch(fdir, options, done.finish(function(evt, name) { + assert.strictEqual( + name, + Buffer.from(fpath).toString('hex'), + 'wrong hex encoding' + ) + })) + watcher.on('ready', function() { + builder.modify(file).catch(done) + }) + }) + }) + + t.describe('filter', function() { + t.test('should only watch filtered directories', function(done) { + var matchRegularDir = false + var matchIgnoredDir = false + var counter = new Counter(done.finish(function() { + assert(matchRegularDir, 'watch failed to detect regular file') + assert(!matchIgnoredDir, 'fail to ignore path `deep_node_modules`') + }), 1, true) + + var options = { + delay: 0, + recursive: true, + filter: function(name) { + return !/deep_node_modules/.test(name) + } + } + + watcher = watch(builder.getPath('home'), options, function(evt, name) { + if (/deep_node_modules/.test(name)) { + matchIgnoredDir = true + } else { + matchRegularDir = true + } + counter.count() + }) + watcher.on('ready', function() { + counter.startCounting() + builder.modify('home/deep_node_modules/ma/file1') + .then(() => builder.modify('home/b/file1')) + .catch(done) + }) + }) + + t.test('should only report filtered files', function(done) { + var dir = builder.getPath('home') + var file1 = 'home/bb/file1' + var file2 = 'home/bb/file2' + + + var counter = new Counter(done.finish(function() { + assert.strictEqual(matchIgnoredFile, false, 'home/bb/file1 should be ignored') + }), 1, true) + + var options = { + delay: 0, + recursive: true, + filter: function(name) { + return /file2/.test(name) + } + } + + var times = 0 + var matchIgnoredFile = false + watcher = watch(dir, options, function(evt, name) { + if (name === builder.getPath(file1)) { + matchIgnoredFile = true + } + counter.count() + }) + watcher.on('ready', function() { + counter.startCounting() + + builder.modify(file2) + .then(() => builder.modify(file1)) + .catch(done) + }) + }) + + t.test('should be able to filter directly with regexp', function(done) { + var dir = builder.getPath('home') + var file1 = 'home/bb/file1' + var file2 = 'home/bb/file2' + + + var counter = new Counter(done.finish(function() { + assert.strictEqual(matchIgnoredFile, false, 'home/bb/file1 should be ignored') + }), 1, true) + + var options = { + delay: 0, + recursive: true, + filter: /file2/ + } + + var times = 0 + var matchIgnoredFile = false + watcher = watch(dir, options, function(evt, name) { + if (name === builder.getPath(file1)) { + matchIgnoredFile = true + } + counter.count() + }) + watcher.on('ready', function() { + counter.startCounting() + + builder.modify(file2) + .then(() => builder.modify(file1)) + .catch(done) + }) + }) + + t.test('should be able to skip subdirectories with `skip` flag', function(done) { + var home = builder.getPath('home') + var options = { + delay: 0, + recursive: true, + filter: function(name, skip) { + if (/\/deep_node_modules/.test(name)) return skip + } + } + watcher = watch(home, options) + + watcher.getWatchedPaths(function(paths) { + hasNativeRecursive(function(supportRecursive) { + var watched = supportRecursive + // The skip flag has no effect to the platforms which support recursive option, + // so the home directory is the only one that's in the watching list. + ? [home] + // The deep_node_modules and all its subdirectories should not be watched + // with skip flag specified in the filter. + : builder.getAllDirectories().filter(function(name) { + return !/\/deep_node_modules/.test(name) + }) + + assert.deepStrictEqual( + watched.sort(), paths.sort() + ) + + done() + }) }) }) }) }) -}) -t.describe('parameters', function() { - t.test('should throw error on non-existed file', function(done) { - var somedir = builder.getPath('home/somedir') - watcher = watch(somedir) - watcher.on('error', done.finish(function(err) { - assert.match(err.message, /not exist/) - })) - }) - - t.test('should accept filename as Buffer', function(done) { - var fpath = builder.getPath('home/a/file1') - watcher = watch(Buffer.from(fpath), { delay: 0 }, done.finish(function(evt, name) { - assert.strictEqual(name, fpath) - })) - watcher.on('ready', function() { - builder.modify('home/a/file1').catch(done) - }) - }) - - t.test('should compose array of files or directories', function(done) { - var counter = new Counter(done, 2) - var file1 = 'home/a/file1' - var file2 = 'home/a/file2' - var fpaths = [ - builder.getPath(file1), - builder.getPath(file2) - ] - - var times = 0 - watcher = watch(fpaths, { delay: 0 }, function(evt, name) { - if (fpaths.indexOf(name) !== -1) counter.count() + t.describe('parameters', function() { + t.test('should throw error on non-existed file', function(done) { + var somedir = builder.getPath('home/somedir') + watcher = watch(somedir) + watcher.on('error', done.finish(function(err) { + assert.match(err.message, /not exist/) + })) }) - watcher.on('ready', function() { + t.test('should accept filename as Buffer', function(done) { + var fpath = builder.getPath('home/a/file1') + watcher = watch(Buffer.from(fpath), { delay: 0 }, done.finish(function(evt, name) { + assert.strictEqual(name, fpath) + })) + watcher.on('ready', function() { + builder.modify('home/a/file1').catch(done) + }) + }) + + t.test('should compose array of files or directories', function(done) { + var counter = new Counter(done, 2) + var file1 = 'home/a/file1' + var file2 = 'home/a/file2' + var fpaths = [ + builder.getPath(file1), + builder.getPath(file2) + ] + var set = new Set(fpaths) + Promise.all([ - builder.modify(file1), - builder.modify(file2), - ]).catch(done) + builder.newFile(file1), + builder.newFile(file2), + ]).then(function() { + watcher = watch(fpaths, { delay: 0 }, function(evt, name) { + if (set.has(name)) { + set.delete(name) + counter.count() + } + }) + + watcher.on('ready', function() { + Promise.all([ + builder.modify(file1), + builder.modify(file2), + ]).catch(done) + }) + }) + }) + + t.test('should filter duplicate events for composed watcher', function(done) { + var counter = new Counter(done, 2) + var home = 'home' + var dir = 'home/a' + var file1 = 'home/a/file1' + var file2 = 'home/a/file2' + var fpaths = [ + builder.getPath(home), + builder.getPath(dir), + builder.getPath(file1), + builder.getPath(file2) + ] + + console.log() + var changes = [] + watcher = watch(fpaths, { delay: 100, recursive: true }, function(evt, name) { + changes.push(name) + counter.count() + }) + + watcher.on('ready', function() { + new Promise(res => { + counter.updateRes(res) + + builder.modify(file1) + .then(() => builder.modify(file2)) + }) + .then(() => builder.delay(50)) + .then(() => { + assert.deepStrictEqual( + changes, + [fpaths[2], fpaths[3]] + ) + done() + }).catch(done) + }) }) }) - t.test('should filter duplicate events for composed watcher', function(done) { - var home = 'home' - var dir = 'home/a' - var file1 = 'home/a/file1' - var file2 = 'home/a/file2' - var fpaths = [ - builder.getPath(home), - builder.getPath(dir), - builder.getPath(file1), - builder.getPath(file2) - ] + t.describe('watcher object', function() { + t.test('should using watcher object to watch', function(done) { + var dir = builder.getPath('home/a') + var file = 'home/a/file1' + var fpath = builder.getPath(file) - var changes = [] - watcher = watch(fpaths, { delay: 100, recursive: true }, function(evt, name) { - changes.push(name) - }) + watcher = watch(dir, { delay: 0 }) - watcher.on('ready', function() { - builder.modify(file1) - builder.modify(file2, 50) - - wait(function() { - assert.deepStrictEqual( - changes, - [builder.getPath(file1), builder.getPath(file2)] - ) - done() - }, 200) - }) - }) -}) - -t.describe('watcher object', function() { - t.test('should using watcher object to watch', function(done) { - var dir = builder.getPath('home/a') - var file = 'home/a/file1' - var fpath = builder.getPath(file) - - watcher = watch(dir, { delay: 0 }) - watcher.on('ready', function() { - watcher.on('change', function(evt, name) { + watcher.on('change', done.finish(function(evt, name) { assert.strictEqual(evt, 'update') assert.strictEqual(name, fpath) - done() - }) - builder.modify(file) - }) - }) - - t.describe('close()', function() { - t.test('should close a watcher using .close()', function(done) { - var dir = builder.getPath('home/a') - var file = 'home/a/file1' - var times = 0 - watcher = watch(dir, { delay: 0 }) - watcher.on('change', function(evt, name) { - times++ - }) - watcher.on('ready', function() { - - watcher.close() + })) + watcher.on('ready', done.wrap(function() { builder.modify(file) - builder.modify(file, 100) - - wait(function() { - assert(watcher.isClosed(), 'watcher should be closed') - assert.strictEqual(times, 0, 'failed to close the watcher') - done() - }, 150) - }) + })) }) - t.test('should not watch after .close() is called', function(done) { - var dir = builder.getPath('home') - watcher = watch(dir, { delay: 0, recursive: true }) - watcher.close() + t.describe('close()', function() { + t.test('should close a watcher using .close()', function(done) { + var dir = builder.getPath('home/a') + var file = 'home/a/file1' + var times = 0 + watcher = watch(dir, { delay: 0 }) + watcher.on('change', function(evt, name) { + times++ + }) + watcher.on('ready', function() { + watcher.close() - watcher.getWatchedPaths(function(dirs) { - assert(dirs.length === 0) - done() + builder.modify(file) + .then(() => builder.delay(50)) + .then(() => builder.modify(file)) + .then(() => builder.delay(50)) + .then(() => { + assert(watcher.isClosed(), 'watcher should be closed') + assert.strictEqual(times, 0, 'failed to close the watcher') + done() + }).catch(done) + }) }) - }) - - t.test('Do not emit after close', function(done) { - var dir = builder.getPath('home/a') - var file = 'home/a/file1' - var times = 0 - watcher = watch(dir, { delay: 0 }) - watcher.on('change', function(evt, name) { - times++ - }) - watcher.on('ready', function() { + t.test('should not watch after .close() is called', function(done) { + var dir = builder.getPath('home') + watcher = watch(dir, { delay: 0, recursive: true }) watcher.close() - var timer = setInterval(function() { - builder.modify(file) - }) - - wait(function() { - clearInterval(timer) - assert(watcher.isClosed(), 'watcher should be closed') - assert.strictEqual(times, 0, 'failed to close the watcher') - done() - }, 100) + watcher.getWatchedPaths(done.finish(function(dirs) { + assert.strictEqual(dirs.length, 0) + })) }) }) - }) + t.describe('getWatchedPaths()', function() { + t.test('should get all the watched paths', function(done) { + var home = builder.getPath('home') + watcher = watch(home, { delay: 0, recursive: true }) - t.describe('getWatchedPaths()', function() { - t.test('should get all the watched paths', function(done) { - var home = builder.getPath('home') - watcher = watch(home, { - delay: 0, - recursive: true - }) - watcher.getWatchedPaths(function(paths) { - hasNativeRecursive(function(supportRecursive) { - var watched = supportRecursive - // The home directory is the only one that's being watched - // if the recursive option is natively supported. - ? [home] - // Otherwise it should include all its subdirectories. - : builder.getAllDirectories() + watcher.getWatchedPaths(function(paths) { + hasNativeRecursive(done.finish(function(supportRecursive) { + var watched = supportRecursive + // The home directory is the only one that's being watched + // if the recursive option is natively supported. + ? [home] + // Otherwise it should include all its subdirectories. + : builder.getAllDirectories() - assert.deepStrictEqual( - watched.sort(), paths.sort() - ) - - done() + assert.deepStrictEqual( + watched.sort(), paths.sort() + ) + })) }) }) - }) - t.test('should get its parent path instead of the file itself', function(done) { - var file = builder.getPath('home/a/file1') - // The parent path is actually being watched instead. - var parent = builder.getPath('home/a') + t.test('should get its parent path instead of the file itself', function(done) { + var file = builder.getPath('home/a/file1') + // The parent path is actually being watched instead. + var parent = builder.getPath('home/a') - watcher = watch(file, { delay: 0 }) + watcher = watch(file, { delay: 0 }) - watcher.getWatchedPaths(function(paths) { - assert.deepStrictEqual([parent], paths) - done() - }) - }) - - t.test('should work correctly with composed watcher', function(done) { - var a = builder.getPath('home/a') - - var b = builder.getPath('home/b') - var file = builder.getPath('home/b/file1') - - var nested = builder.getPath('home/deep_node_modules') - var ma = builder.getPath('home/deep_node_modules/ma') - var mb = builder.getPath('home/deep_node_modules/mb') - var mc = builder.getPath('home/deep_node_modules/mc') - - watcher = watch([a, file, nested], { - delay: 0, - recursive: true + watcher.getWatchedPaths(done.finish(function(paths) { + assert.deepStrictEqual([parent], paths) + })) }) - watcher.getWatchedPaths(function(paths) { - hasNativeRecursive(function(supportRecursive) { - var watched = supportRecursive - ? [a, b, nested] - : [a, b, nested, ma, mb, mc] + t.test('should work correctly with composed watcher', function(done) { + var a = builder.getPath('home/a') - assert.deepStrictEqual( - watched.sort(), paths.sort() - ) + var b = builder.getPath('home/b') + var file = builder.getPath('home/b/file1') - done() + var nested = builder.getPath('home/deep_node_modules') + var ma = builder.getPath('home/deep_node_modules/ma') + var mb = builder.getPath('home/deep_node_modules/mb') + var mc = builder.getPath('home/deep_node_modules/mc') + + watcher = watch([a, file, nested], { + delay: 0, + recursive: true + }) + + watcher.getWatchedPaths(function(paths) { + hasNativeRecursive(done.finish(function(supportRecursive) { + var watched = supportRecursive + ? [a, b, nested] + : [a, b, nested, ma, mb, mc] + + assert.deepStrictEqual( + watched.sort(), paths.sort() + ) + })) }) }) }) }) -}) +}) \ No newline at end of file diff --git a/test/watch/builder.mjs b/test/watch/builder.mjs index af4fcea..7025f71 100644 --- a/test/watch/builder.mjs +++ b/test/watch/builder.mjs @@ -187,6 +187,12 @@ Builder.prototype.getAllDirectories = function() { return walk(this.root) } +Builder.prototype.delay = function(delay) { + return new Promise(res => { + setTimeout(res, delay) + }) +} + export function Counter(res, countTotal, waitForSignal = false) { this._res = res this.counter = 0 @@ -208,6 +214,10 @@ Counter.prototype.waitForCount = function(promise) { }) } +Counter.prototype.updateRes = function(res) { + this._res = res +} + Counter.prototype.hasSignal = function() { if (this._gotSignal && this._signal) { this._gotSignal = false