watcher: Imported node-watch into the project including tests.
This commit is contained in:
parent
9d2b71339c
commit
b47fa2b068
18 changed files with 1999 additions and 35 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -102,3 +102,5 @@ dist
|
||||||
|
|
||||||
# TernJS port file
|
# TernJS port file
|
||||||
.tern-port
|
.tern-port
|
||||||
|
|
||||||
|
test/watch/__TREE__
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
47
lib/eltro.d.ts
vendored
Normal file
47
lib/eltro.d.ts
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
declare interface Stats {
|
||||||
|
passed: number
|
||||||
|
failed: number
|
||||||
|
skipped: number
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Eltro {
|
||||||
|
__timeout: number
|
||||||
|
hasExclusive: boolean
|
||||||
|
reporter: 'dot' | 'list'
|
||||||
|
Eltro: Eltro
|
||||||
|
fileGroupMap: Map<string, Group>
|
||||||
|
groups: Array<Group>
|
||||||
|
activeGroup: Group | null
|
||||||
|
failedTests: Array<Test>
|
||||||
|
hasTests: boolean
|
||||||
|
starting: boolean
|
||||||
|
ignoreOnly: boolean
|
||||||
|
logger: null | { log: function(...param): void }
|
||||||
|
filename: string
|
||||||
|
prefix: string
|
||||||
|
temporary: { timeout: number, skip: boolean, only: boolean }
|
||||||
|
describeTemporary: { timeout: number, skip: boolean, only: boolean }
|
||||||
|
|
||||||
|
__runTest(stats: Stats, test: Test, prefix: string = 'Test', child?: Test | null = null)
|
||||||
|
__runGroup(group: Group, stats: Stats)
|
||||||
|
begin()
|
||||||
|
run()
|
||||||
|
setFilename(filename: string)
|
||||||
|
resetFilename(filename: string)
|
||||||
|
|
||||||
|
before(fn: (done?: Function) => void | Promise)
|
||||||
|
after(fn: (done?: Function) => void | Promise)
|
||||||
|
beforeEach(fn: (done?: Function) => void | Promise)
|
||||||
|
afterEach(fn: (done?: Function) => void | Promise)
|
||||||
|
describe(name: string, fn: Function)
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Test {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Group {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Eltro()
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="./eltro.d.ts"/>
|
||||||
import * as readline from 'readline'
|
import * as readline from 'readline'
|
||||||
import { runWithCallbackSafe } from './callback.mjs'
|
import { runWithCallbackSafe } from './callback.mjs'
|
||||||
import { printError } from './cli.mjs'
|
import { printError } from './cli.mjs'
|
||||||
|
@ -63,6 +64,13 @@ function Test(e, group, name, func) {
|
||||||
this.name = name
|
this.name = name
|
||||||
this.func = func
|
this.func = func
|
||||||
this.error = null
|
this.error = null
|
||||||
|
this.startTime = null
|
||||||
|
this.totalTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test.prototype.calculateTime = function() {
|
||||||
|
let end = process.hrtime(this.startTime)
|
||||||
|
this.totalTime = (end[0] * 1000 + Math.round(end[1] / 1000000))
|
||||||
}
|
}
|
||||||
|
|
||||||
Test.prototype.timeout = function(time) {
|
Test.prototype.timeout = function(time) {
|
||||||
|
@ -89,6 +97,25 @@ Test.prototype.clone = function(prefix = '') {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stack = []
|
||||||
|
function captureUnknownErrors(e) {
|
||||||
|
stack.push(e)
|
||||||
|
}
|
||||||
|
function cancelCaptureUnknown(e) {
|
||||||
|
stack.splice(stack.indexOf(e), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('uncaughtException', function(err) {
|
||||||
|
for (let i = stack.length - 1; i >= 0; i--) {
|
||||||
|
if (stack[i].captureOutsideExceptions) {
|
||||||
|
stack[i].captureOutsideExceptions(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.error('-- UNCAUGHT EXCPEPTION OUTSIDE OF TEST RUNNER --')
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
|
||||||
function Eltro() {
|
function Eltro() {
|
||||||
this.__timeout = 2000
|
this.__timeout = 2000
|
||||||
this.hasExclusive = false
|
this.hasExclusive = false
|
||||||
|
@ -114,6 +141,7 @@ function Eltro() {
|
||||||
skip: false,
|
skip: false,
|
||||||
only: false
|
only: false
|
||||||
}
|
}
|
||||||
|
this.captureOutsideExceptions = null
|
||||||
}
|
}
|
||||||
|
|
||||||
Eltro.prototype.begin = function() {
|
Eltro.prototype.begin = function() {
|
||||||
|
@ -137,6 +165,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
|
|
||||||
if (!test.skipTest) {
|
if (!test.skipTest) {
|
||||||
let err = await new Promise((resolve, reject) => {
|
let err = await new Promise((resolve, reject) => {
|
||||||
|
this.captureOutsideExceptions = reject
|
||||||
// Flag to check if we finished
|
// Flag to check if we finished
|
||||||
let finished = false
|
let finished = false
|
||||||
let timeout = test.customTimeout || this.__timeout
|
let timeout = test.customTimeout || this.__timeout
|
||||||
|
@ -144,6 +173,8 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// Timeout timer in case test times out
|
// Timeout timer in case test times out
|
||||||
let timer = setTimeout(function() {
|
let timer = setTimeout(function() {
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
reject(new Error('timeout of ' + timeout + 'ms exceeded. Ensure the done() callback is being called in this test.'))
|
reject(new Error('timeout of ' + timeout + 'ms exceeded. Ensure the done() callback is being called in this test.'))
|
||||||
}, timeout)
|
}, timeout)
|
||||||
|
|
||||||
|
@ -153,6 +184,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
let checkIsCallback = (test.func.toString()).match(/^(function)? *\([^\)]+\)/)
|
let checkIsCallback = (test.func.toString()).match(/^(function)? *\([^\)]+\)/)
|
||||||
let promise
|
let promise
|
||||||
|
|
||||||
|
test.startTime = process.hrtime()
|
||||||
// If the test requires callback, wrap it in a promise where callback
|
// If the test requires callback, wrap it in a promise where callback
|
||||||
// either resolves or rejects that promise
|
// either resolves or rejects that promise
|
||||||
if (checkIsCallback) {
|
if (checkIsCallback) {
|
||||||
|
@ -170,6 +202,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
finished = true
|
finished = true
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
resolve()
|
resolve()
|
||||||
|
@ -177,6 +210,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
finished = true
|
finished = true
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
reject(err)
|
reject(err)
|
||||||
|
@ -185,6 +219,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
// Possible this was a synchronous test, pass immediately
|
// Possible this was a synchronous test, pass immediately
|
||||||
finished = true
|
finished = true
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
|
@ -194,6 +229,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
// An error occured while running function. Possible exception
|
// An error occured while running function. Possible exception
|
||||||
// during a synchronous test or something else.
|
// during a synchronous test or something else.
|
||||||
finished = true
|
finished = true
|
||||||
|
@ -232,15 +268,17 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
stats.skipped++
|
stats.skipped++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.captureOutsideExceptions = null
|
||||||
|
|
||||||
if (this.reporter === 'list') {
|
if (this.reporter === 'list') {
|
||||||
readline.clearLine(process.stdout, 0)
|
readline.clearLine(process.stdout, 0)
|
||||||
readline.cursorTo(process.stdout, 0, null)
|
readline.cursorTo(process.stdout, 0, null)
|
||||||
if (markRealTest.skipTest) {
|
if (markRealTest.skipTest) {
|
||||||
process.stdout.write(' \x1b[94m- ' + markRealTest.name + '\x1b[0m\n')
|
process.stdout.write(' \x1b[94m- ' + markRealTest.name + '\x1b[0m\n')
|
||||||
} else if (!markRealTest.error) {
|
} else if (!markRealTest.error) {
|
||||||
process.stdout.write(' \x1b[32m√\x1b[90m ' + markRealTest.name + '\x1b[0m\n')
|
process.stdout.write(' \x1b[32m√\x1b[90m ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n')
|
||||||
} else if (prefix === 'Test') {
|
} else if (prefix === 'Test') {
|
||||||
process.stdout.write(' \x1b[31m' + this.failedTests.length + ') ' + markRealTest.name + '\x1b[0m\n')
|
process.stdout.write(' \x1b[31m' + this.failedTests.length + ') ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n')
|
||||||
}
|
}
|
||||||
} else if (this.reporter === 'dot') {
|
} else if (this.reporter === 'dot') {
|
||||||
if (markRealTest.skipTest) {
|
if (markRealTest.skipTest) {
|
||||||
|
@ -302,6 +340,8 @@ Eltro.prototype.run = async function() {
|
||||||
console.log('')
|
console.log('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captureUnknownErrors(this)
|
||||||
|
|
||||||
let stats = {
|
let stats = {
|
||||||
passed: 0,
|
passed: 0,
|
||||||
failed: 0,
|
failed: 0,
|
||||||
|
@ -314,9 +354,10 @@ Eltro.prototype.run = async function() {
|
||||||
await this.__runGroup(this.groups[i], stats)
|
await this.__runGroup(this.groups[i], stats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let end = process.hrtime(start)
|
let end = process.hrtime(start)
|
||||||
|
|
||||||
|
cancelCaptureUnknown(this)
|
||||||
|
|
||||||
if (this.reporter === 'test') {
|
if (this.reporter === 'test') {
|
||||||
if (this.logger && this.logger.log) {
|
if (this.logger && this.logger.log) {
|
||||||
if (this.failedTests.length) {
|
if (this.failedTests.length) {
|
||||||
|
|
22
lib/watch/LICENSE
Normal file
22
lib/watch/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2012-2021 Yuan Chuan <yuanchuan23@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
114
lib/watch/has-native-recursive.mjs
Normal file
114
lib/watch/has-native-recursive.mjs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import os from 'os'
|
||||||
|
import path from 'path'
|
||||||
|
import * as is from './is.mjs'
|
||||||
|
|
||||||
|
let IS_SUPPORT
|
||||||
|
let TEMP_DIR = os.tmpdir && os.tmpdir()
|
||||||
|
|| process.env.TMPDIR
|
||||||
|
|| process.env.TEMP
|
||||||
|
|| process.cwd()
|
||||||
|
|
||||||
|
function TempStack() {
|
||||||
|
this.stack = []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TempStack.prototype.create = function(type, base) {
|
||||||
|
let name = path.join(base,
|
||||||
|
'node-watch-' + Math.random().toString(16).substr(2)
|
||||||
|
)
|
||||||
|
this.stack.push({ name: name, type: type })
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
TempStack.prototype.write = function(/* file */) {
|
||||||
|
for (let i = 0; i < arguments.length; ++i) {
|
||||||
|
fs.writeFileSync(arguments[i], ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TempStack.prototype.mkdir = function(/* dirs */) {
|
||||||
|
for (let i = 0; i < arguments.length; ++i) {
|
||||||
|
fs.mkdirSync(arguments[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TempStack.prototype.cleanup = function(fn) {
|
||||||
|
try {
|
||||||
|
let temp
|
||||||
|
while ((temp = this.stack.pop())) {
|
||||||
|
let type = temp.type
|
||||||
|
let name = temp.name
|
||||||
|
if (type === 'file' && is.file(name)) {
|
||||||
|
fs.unlinkSync(name)
|
||||||
|
}
|
||||||
|
else if (type === 'dir' && is.directory(name)) {
|
||||||
|
fs.rmdirSync(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (is.func(fn)) fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pending = false
|
||||||
|
|
||||||
|
export default function hasNativeRecursive(fn) {
|
||||||
|
if (!is.func(fn)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (IS_SUPPORT !== undefined) {
|
||||||
|
return fn(IS_SUPPORT)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pending) {
|
||||||
|
pending = true
|
||||||
|
}
|
||||||
|
// check again later
|
||||||
|
else {
|
||||||
|
return setTimeout(function() {
|
||||||
|
hasNativeRecursive(fn)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
let stack = new TempStack()
|
||||||
|
let parent = stack.create('dir', TEMP_DIR)
|
||||||
|
let child = stack.create('dir', parent)
|
||||||
|
let file = stack.create('file', child)
|
||||||
|
|
||||||
|
stack.mkdir(parent, child)
|
||||||
|
|
||||||
|
let options = { recursive: true }
|
||||||
|
let watcher
|
||||||
|
|
||||||
|
try {
|
||||||
|
watcher = fs.watch(parent, options)
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code == 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM') {
|
||||||
|
return fn(IS_SUPPORT = false)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!watcher) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer = setTimeout(function() {
|
||||||
|
watcher.close()
|
||||||
|
stack.cleanup(function() {
|
||||||
|
fn(IS_SUPPORT = false)
|
||||||
|
})
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
watcher.on('change', function(evt, name) {
|
||||||
|
if (path.basename(file) === path.basename(name)) {
|
||||||
|
watcher.close()
|
||||||
|
clearTimeout(timer)
|
||||||
|
stack.cleanup(function() {
|
||||||
|
fn(IS_SUPPORT = true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stack.write(file)
|
||||||
|
}
|
549
lib/watch/index.mjs
Normal file
549
lib/watch/index.mjs
Normal file
|
@ -0,0 +1,549 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import util from 'util'
|
||||||
|
import events from 'events'
|
||||||
|
import hasNativeRecursive from './has-native-recursive.mjs'
|
||||||
|
import * as is from './is.mjs'
|
||||||
|
|
||||||
|
const EVENT_UPDATE = 'update';
|
||||||
|
const EVENT_REMOVE = 'remove';
|
||||||
|
|
||||||
|
const SKIP_FLAG = Symbol('skip');
|
||||||
|
|
||||||
|
function hasDup(arr) {
|
||||||
|
return arr.some(function(v, i, self) {
|
||||||
|
return self.indexOf(v) !== i;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function unique(arr) {
|
||||||
|
return arr.filter(function(v, i, self) {
|
||||||
|
return self.indexOf(v) === i;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// One level flat
|
||||||
|
function flat1(arr) {
|
||||||
|
return arr.reduce(function(acc, v) {
|
||||||
|
return acc.concat(v);
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertEncoding(encoding) {
|
||||||
|
if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
|
||||||
|
throw new Error('Unknown encoding: ' + encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function guard(fn) {
|
||||||
|
if (is.func(fn)) {
|
||||||
|
return function(arg, action) {
|
||||||
|
if (fn(arg, false)) action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.regExp(fn)) {
|
||||||
|
return function(arg, action) {
|
||||||
|
if (fn.test(arg)) action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return function(arg, action) {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function composeMessage(names) {
|
||||||
|
return names.map(function(n) {
|
||||||
|
return is.exists(n)
|
||||||
|
? [EVENT_UPDATE, n]
|
||||||
|
: [EVENT_REMOVE, n];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessages(cache) {
|
||||||
|
var filtered = unique(cache);
|
||||||
|
|
||||||
|
// Saving file from an editor? If so, assuming the
|
||||||
|
// non-existed files in the cache are temporary files
|
||||||
|
// generated by an editor and thus be filtered.
|
||||||
|
var reg = /~$|^\.#|^##$/g;
|
||||||
|
var hasSpecialChar = cache.some(function(c) {
|
||||||
|
return reg.test(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasSpecialChar) {
|
||||||
|
var dup = hasDup(cache.map(function(c) {
|
||||||
|
return c.replace(reg, '');
|
||||||
|
}));
|
||||||
|
if (dup) {
|
||||||
|
filtered = filtered.filter(function(m) {
|
||||||
|
return is.exists(m);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return composeMessage(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounce(info, fn) {
|
||||||
|
var timer, cache = [];
|
||||||
|
var encoding = info.options.encoding;
|
||||||
|
var delay = info.options.delay;
|
||||||
|
if (!is.number(delay)) {
|
||||||
|
delay = 200;
|
||||||
|
}
|
||||||
|
function handle() {
|
||||||
|
getMessages(cache).forEach(function(msg) {
|
||||||
|
msg[1] = Buffer.from(msg[1]);
|
||||||
|
if (encoding !== 'buffer') {
|
||||||
|
msg[1] = msg[1].toString(encoding);
|
||||||
|
}
|
||||||
|
fn.apply(null, msg);
|
||||||
|
});
|
||||||
|
timer = null;
|
||||||
|
cache = [];
|
||||||
|
}
|
||||||
|
return function(rawEvt, name) {
|
||||||
|
cache.push(name);
|
||||||
|
if (!timer) {
|
||||||
|
timer = setTimeout(handle, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDupsFilter() {
|
||||||
|
var memo = {};
|
||||||
|
return function(fn) {
|
||||||
|
return function(evt, name) {
|
||||||
|
memo[evt + name] = [evt, name];
|
||||||
|
setTimeout(function() {
|
||||||
|
Object.keys(memo).forEach(function(n) {
|
||||||
|
fn.apply(null, memo[n]);
|
||||||
|
});
|
||||||
|
memo = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryWatch(watcher, dir, opts) {
|
||||||
|
try {
|
||||||
|
return fs.watch(dir, opts);
|
||||||
|
} catch (e) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
watcher.emit('error', e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSubDirectories(dir, fn, done = function() {}) {
|
||||||
|
if (is.directory(dir)) {
|
||||||
|
fs.readdir(dir, function(err, all) {
|
||||||
|
if (err) {
|
||||||
|
// don't throw permission errors.
|
||||||
|
if (/^(EPERM|EACCES)$/.test(err.code)) {
|
||||||
|
console.warn('Warning: Cannot access %s.', dir);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
all.forEach(function(f) {
|
||||||
|
var sdir = path.join(dir, f);
|
||||||
|
if (is.directory(sdir)) fn(sdir);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function semaphore(final) {
|
||||||
|
var counter = 0;
|
||||||
|
return function start() {
|
||||||
|
counter++;
|
||||||
|
return function stop() {
|
||||||
|
counter--;
|
||||||
|
if (counter === 0) final();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function nullCounter() {
|
||||||
|
return function nullStop() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldNotSkip(filePath, filter) {
|
||||||
|
// watch it only if the filter is not function
|
||||||
|
// or not being skipped explicitly.
|
||||||
|
return !is.func(filter) || filter(filePath, SKIP_FLAG) !== SKIP_FLAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deprecationWarning = util.deprecate(
|
||||||
|
function() {},
|
||||||
|
'(node-watch) First param in callback function\
|
||||||
|
is replaced with event name since 0.5.0, use\
|
||||||
|
`(evt, filename) => {}` if you want to get the filename'
|
||||||
|
);
|
||||||
|
|
||||||
|
function Watcher() {
|
||||||
|
events.EventEmitter.call(this);
|
||||||
|
this.watchers = {};
|
||||||
|
this._isReady = false;
|
||||||
|
this._isClosed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Watcher, events.EventEmitter);
|
||||||
|
|
||||||
|
Watcher.prototype.expose = function() {
|
||||||
|
var expose = {};
|
||||||
|
var self = this;
|
||||||
|
var methods = [
|
||||||
|
'on', 'emit', 'once',
|
||||||
|
'close', 'isClosed',
|
||||||
|
'listeners', 'setMaxListeners', 'getMaxListeners',
|
||||||
|
'getWatchedPaths'
|
||||||
|
];
|
||||||
|
methods.forEach(function(name) {
|
||||||
|
expose[name] = function() {
|
||||||
|
return self[name].apply(self, arguments);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expose.options = self.options
|
||||||
|
return expose;
|
||||||
|
}
|
||||||
|
|
||||||
|
Watcher.prototype.isClosed = function() {
|
||||||
|
return this._isClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Watcher.prototype.close = function(fullPath) {
|
||||||
|
var self = this;
|
||||||
|
if (fullPath) {
|
||||||
|
var watcher = this.watchers[fullPath];
|
||||||
|
if (watcher && watcher.close) {
|
||||||
|
watcher.close();
|
||||||
|
delete self.watchers[fullPath];
|
||||||
|
}
|
||||||
|
getSubDirectories(fullPath, function(fpath) {
|
||||||
|
self.close(fpath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Object.keys(self.watchers).forEach(function(fpath) {
|
||||||
|
var watcher = self.watchers[fpath];
|
||||||
|
if (watcher && watcher.close) {
|
||||||
|
watcher.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.watchers = {};
|
||||||
|
}
|
||||||
|
// Do not close the Watcher unless all child watchers are closed.
|
||||||
|
// https://github.com/yuanchuan/node-watch/issues/75
|
||||||
|
if (is.emptyObject(self.watchers)) {
|
||||||
|
// should emit once
|
||||||
|
if (!this._isClosed) {
|
||||||
|
this._isClosed = true;
|
||||||
|
process.nextTick(emitClose, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Watcher.prototype.getWatchedPaths = function(fn) {
|
||||||
|
if (is.func(fn)) {
|
||||||
|
var self = this;
|
||||||
|
if (self._isReady) {
|
||||||
|
fn(Object.keys(self.watchers));
|
||||||
|
} else {
|
||||||
|
self.on('ready', function() {
|
||||||
|
fn(Object.keys(self.watchers));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitReady(self) {
|
||||||
|
if (!self._isReady) {
|
||||||
|
self._isReady = true;
|
||||||
|
// do not call emit for 'ready' until after watch() has returned,
|
||||||
|
// so that consumer can call on().
|
||||||
|
process.nextTick(function () {
|
||||||
|
self.emit('ready');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitClose(self) {
|
||||||
|
self.emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
Watcher.prototype.add = function(watcher, info) {
|
||||||
|
var self = this;
|
||||||
|
info = info || { fpath: '' };
|
||||||
|
var watcherPath = path.resolve(info.fpath);
|
||||||
|
this.watchers[watcherPath] = watcher;
|
||||||
|
|
||||||
|
// Internal callback for handling fs.FSWatcher 'change' events
|
||||||
|
var internalOnChange = function(rawEvt, rawName) {
|
||||||
|
if (self.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalise lack of name and convert to full path
|
||||||
|
var name = rawName;
|
||||||
|
if (is.nil(name)) {
|
||||||
|
name = '';
|
||||||
|
}
|
||||||
|
name = path.join(info.fpath, name);
|
||||||
|
|
||||||
|
if (info.options.recursive) {
|
||||||
|
hasNativeRecursive(function(has) {
|
||||||
|
if (!has) {
|
||||||
|
var fullPath = path.resolve(name);
|
||||||
|
// remove watcher on removal
|
||||||
|
if (!is.exists(name)) {
|
||||||
|
self.close(fullPath);
|
||||||
|
}
|
||||||
|
// watch new created directory
|
||||||
|
else {
|
||||||
|
var shouldWatch = is.directory(name)
|
||||||
|
&& !self.watchers[fullPath]
|
||||||
|
&& shouldNotSkip(name, info.options.filter);
|
||||||
|
|
||||||
|
if (shouldWatch) {
|
||||||
|
self.watchDirectory(name, info.options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePublicEvents(rawEvt, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Debounced based on the 'delay' option
|
||||||
|
var handlePublicEvents = debounce(info, function (evt, name) {
|
||||||
|
// watch single file
|
||||||
|
if (info.compareName) {
|
||||||
|
if (info.compareName(name)) {
|
||||||
|
self.emit('change', evt, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// watch directory
|
||||||
|
else {
|
||||||
|
var filterGuard = guard(info.options.filter);
|
||||||
|
filterGuard(name, function() {
|
||||||
|
if (self.flag) self.flag = '';
|
||||||
|
else self.emit('change', evt, name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.on('error', function(err) {
|
||||||
|
if (self.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (is.windows() && err.code === 'EPERM') {
|
||||||
|
watcher.emit('change', EVENT_REMOVE, info.fpath && '');
|
||||||
|
self.flag = 'windows-error';
|
||||||
|
self.close(watcherPath);
|
||||||
|
} else {
|
||||||
|
self.emit('error', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.on('change', internalOnChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
Watcher.prototype.watchFile = function(file, options, fn) {
|
||||||
|
var parent = path.join(file, '../');
|
||||||
|
var opts = Object.assign({}, options, {
|
||||||
|
// no filter for single file
|
||||||
|
filter: null,
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
|
||||||
|
// no need to watch recursively
|
||||||
|
delete opts.recursive;
|
||||||
|
|
||||||
|
var watcher = tryWatch(this, parent, opts);
|
||||||
|
if (!watcher) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.add(watcher, {
|
||||||
|
type: 'file',
|
||||||
|
fpath: parent,
|
||||||
|
options: Object.assign({}, opts, {
|
||||||
|
encoding: options.encoding
|
||||||
|
}),
|
||||||
|
compareName: function(n) {
|
||||||
|
return is.samePath(n, file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is.func(fn)) {
|
||||||
|
if (fn.length === 1) deprecationWarning();
|
||||||
|
this.on('change', fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Watcher.prototype.updateDelay = function(delay) {
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
|
||||||
|
Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
|
||||||
|
var self = this;
|
||||||
|
var done = counter();
|
||||||
|
hasNativeRecursive(function(has) {
|
||||||
|
// always specify recursive
|
||||||
|
options.recursive = !!options.recursive;
|
||||||
|
// using utf8 internally
|
||||||
|
var opts = Object.assign({}, options, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
if (!has) {
|
||||||
|
delete opts.recursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it's closed before calling watch.
|
||||||
|
if (self._isClosed) {
|
||||||
|
done();
|
||||||
|
return self.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
var watcher = tryWatch(self, dir, opts);
|
||||||
|
if (!watcher) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.add(watcher, {
|
||||||
|
type: 'dir',
|
||||||
|
fpath: dir,
|
||||||
|
options: options
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is.func(fn)) {
|
||||||
|
if (fn.length === 1) deprecationWarning();
|
||||||
|
self.on('change', fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.recursive && !has) {
|
||||||
|
getSubDirectories(dir, function(d) {
|
||||||
|
if (shouldNotSkip(d, options.filter)) {
|
||||||
|
self.watchDirectory(d, options, null, counter);
|
||||||
|
}
|
||||||
|
}, counter());
|
||||||
|
}
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function composeWatcher(watchers) {
|
||||||
|
var watcher = new Watcher();
|
||||||
|
var filterDups = createDupsFilter();
|
||||||
|
var counter = watchers.length;
|
||||||
|
|
||||||
|
watchers.forEach(function(w) {
|
||||||
|
w.on('change', filterDups(function(evt, name) {
|
||||||
|
watcher.emit('change', evt, name);
|
||||||
|
}));
|
||||||
|
w.on('error', function(err) {
|
||||||
|
watcher.emit('error', err);
|
||||||
|
});
|
||||||
|
w.on('ready', function() {
|
||||||
|
if (!(--counter)) {
|
||||||
|
emitReady(watcher);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.close = function() {
|
||||||
|
watchers.forEach(function(w) {
|
||||||
|
w.close();
|
||||||
|
});
|
||||||
|
process.nextTick(emitClose, watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher.getWatchedPaths = function(fn) {
|
||||||
|
if (is.func(fn)) {
|
||||||
|
var promises = watchers.map(function(w) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
w.getWatchedPaths(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Promise.all(promises).then(function(result) {
|
||||||
|
var ret = unique(flat1(result));
|
||||||
|
fn(ret);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return watcher.expose();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function watch(fpath, options, fn) {
|
||||||
|
var watcher = new Watcher();
|
||||||
|
|
||||||
|
if (is.buffer(fpath)) {
|
||||||
|
fpath = fpath.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is.array(fpath) && !is.exists(fpath)) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
watcher.emit('error',
|
||||||
|
new Error(fpath + ' does not exist.')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.string(options)) {
|
||||||
|
throw new Error(`Invalid option, encoding as string is no longer supported. Use { encoding: "${options}" } instead.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.func(options)) {
|
||||||
|
fn = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.encoding) {
|
||||||
|
assertEncoding(options.encoding);
|
||||||
|
} else {
|
||||||
|
options.encoding = 'utf8';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.array(fpath)) {
|
||||||
|
if (fpath.length === 1) {
|
||||||
|
return watch(fpath[0], options, fn);
|
||||||
|
}
|
||||||
|
var filterDups = createDupsFilter();
|
||||||
|
return composeWatcher(unique(fpath).map(function(f) {
|
||||||
|
var w = watch(f, options);
|
||||||
|
if (is.func(fn)) {
|
||||||
|
w.on('change', filterDups(fn));
|
||||||
|
}
|
||||||
|
return w;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.file(fpath)) {
|
||||||
|
watcher.watchFile(fpath, options, fn);
|
||||||
|
emitReady(watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (is.directory(fpath)) {
|
||||||
|
var counter = semaphore(function () {
|
||||||
|
emitReady(watcher);
|
||||||
|
});
|
||||||
|
watcher.watchDirectory(fpath, options, fn, counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return watcher.expose();
|
||||||
|
}
|
74
lib/watch/is.mjs
Normal file
74
lib/watch/is.mjs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import os from 'os'
|
||||||
|
|
||||||
|
function matchObject(item, str) {
|
||||||
|
return Object.prototype.toString.call(item)
|
||||||
|
=== '[object ' + str + ']'
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStat(name, fn) {
|
||||||
|
try {
|
||||||
|
return fn(name)
|
||||||
|
} catch (err) {
|
||||||
|
if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
console.warn('Warning: Cannot access %s', name)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nil(item) {
|
||||||
|
return item == null
|
||||||
|
}
|
||||||
|
export function array(item) {
|
||||||
|
return Array.isArray(item)
|
||||||
|
}
|
||||||
|
export function emptyObject(item) {
|
||||||
|
for (var key in item) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
export function buffer(item) {
|
||||||
|
return Buffer.isBuffer(item)
|
||||||
|
}
|
||||||
|
export function regExp(item) {
|
||||||
|
return matchObject(item, 'RegExp')
|
||||||
|
}
|
||||||
|
export function string(item) {
|
||||||
|
return matchObject(item, 'String')
|
||||||
|
}
|
||||||
|
export function func(item) {
|
||||||
|
return typeof item === 'function'
|
||||||
|
}
|
||||||
|
export function number(item) {
|
||||||
|
return matchObject(item, 'Number')
|
||||||
|
}
|
||||||
|
export function exists(name) {
|
||||||
|
return fs.existsSync(name)
|
||||||
|
}
|
||||||
|
export function file(name) {
|
||||||
|
return checkStat(name, function(n) {
|
||||||
|
return fs.statSync(n).isFile()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function samePath(a, b) {
|
||||||
|
return path.resolve(a) === path.resolve(b)
|
||||||
|
}
|
||||||
|
export function directory(name) {
|
||||||
|
return checkStat(name, function(n) {
|
||||||
|
return fs.statSync(n).isDirectory()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function symbolicLink(name) {
|
||||||
|
return checkStat(name, function(n) {
|
||||||
|
return fs.lstatSync(n).isSymbolicLink()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function windows() {
|
||||||
|
return os.platform() === 'win32'
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
"description": "Eltro is a tiny no-dependancy test framework for node",
|
"description": "Eltro is a tiny no-dependancy test framework for node",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node cli.mjs test/**/*.test.mjs",
|
"test": "node cli.mjs 'test/**/*.test.mjs'",
|
||||||
"test:watch": "npm-watch test"
|
"test:watch": "npm-watch test"
|
||||||
},
|
},
|
||||||
"watch": {
|
"watch": {
|
||||||
|
|
|
@ -124,7 +124,7 @@ t.describe('CLI', function() {
|
||||||
|
|
||||||
t.describe('#processTargets()', function() {
|
t.describe('#processTargets()', function() {
|
||||||
t.test('should mark errored if empty', async function() {
|
t.test('should mark errored if empty', async function() {
|
||||||
cli.targets = ['test/folder1/*.txt']
|
cli.targets = ['test/testtree/folder1/*.txt']
|
||||||
await cli.processTargets()
|
await cli.processTargets()
|
||||||
|
|
||||||
assert.strictEqual(cli.files.length, 0)
|
assert.strictEqual(cli.files.length, 0)
|
||||||
|
@ -132,31 +132,31 @@ t.describe('CLI', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should support direct file path if exists', async function() {
|
t.test('should support direct file path if exists', async function() {
|
||||||
cli.targets = ['test/folder1/sampletest1.temp.mjs']
|
cli.targets = ['test/testtree/folder1/sampletest1.temp.mjs']
|
||||||
await cli.processTargets()
|
await cli.processTargets()
|
||||||
|
|
||||||
assert.strictEqual(cli.files.length, 1)
|
assert.strictEqual(cli.files.length, 1)
|
||||||
assert.strictEqual(cli.files[0], 'test/folder1/sampletest1.temp.mjs')
|
assert.strictEqual(cli.files[0], 'test/testtree/folder1/sampletest1.temp.mjs')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should return all files in a directory', async function() {
|
t.test('should return all files in a directory', async function() {
|
||||||
cli.targets = ['test/folder1/']
|
cli.targets = ['test/testtree/folder1/']
|
||||||
await cli.processTargets()
|
await cli.processTargets()
|
||||||
|
|
||||||
assert.strictEqual(cli.files.length, 2)
|
assert.strictEqual(cli.files.length, 2)
|
||||||
cli.files.sort()
|
cli.files.sort()
|
||||||
assert.strictEqual(cli.files[0], 'test/folder1/sampletest1.temp.mjs')
|
assert.strictEqual(cli.files[0], 'test/testtree/folder1/sampletest1.temp.mjs')
|
||||||
assert.strictEqual(cli.files[1], 'test/folder1/sampletest2.temp.mjs')
|
assert.strictEqual(cli.files[1], 'test/testtree/folder1/sampletest2.temp.mjs')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should support start as folder substitute', async function() {
|
t.test('should support start as folder substitute', async function() {
|
||||||
cli.targets = ['*/folder1/']
|
cli.targets = ['*/testtree/folder1/']
|
||||||
await cli.processTargets()
|
await cli.processTargets()
|
||||||
|
|
||||||
assert.strictEqual(cli.files.length, 2)
|
assert.strictEqual(cli.files.length, 2)
|
||||||
cli.files.sort()
|
cli.files.sort()
|
||||||
assert.strictEqual(cli.files[0], 'test/folder1/sampletest1.temp.mjs')
|
assert.strictEqual(cli.files[0], 'test/testtree/folder1/sampletest1.temp.mjs')
|
||||||
assert.strictEqual(cli.files[1], 'test/folder1/sampletest2.temp.mjs')
|
assert.strictEqual(cli.files[1], 'test/testtree/folder1/sampletest2.temp.mjs')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should support grabbing only files in folder', async function() {
|
t.test('should support grabbing only files in folder', async function() {
|
||||||
|
@ -182,28 +182,28 @@ t.describe('CLI', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should support multiple star pattern', async function() {
|
t.test('should support multiple star pattern', async function() {
|
||||||
cli.targets = ['test/*/*.mjs']
|
cli.targets = ['test/testtree/*/*.mjs']
|
||||||
await cli.processTargets()
|
await cli.processTargets()
|
||||||
|
|
||||||
|
console.log(cli.files)
|
||||||
assert.strictEqual(cli.files.length, 4)
|
assert.strictEqual(cli.files.length, 4)
|
||||||
cli.files.sort()
|
cli.files.sort()
|
||||||
assert.deepEqual(cli.files, [
|
assert.deepEqual(cli.files, [
|
||||||
'test/folder1/sampletest1.temp.mjs',
|
'test/testtree/folder1/sampletest1.temp.mjs',
|
||||||
'test/folder1/sampletest2.temp.mjs',
|
'test/testtree/folder1/sampletest2.temp.mjs',
|
||||||
'test/folder2/sampletest3.temp.mjs',
|
'test/testtree/folder2/sampletest3.temp.mjs',
|
||||||
'test/folder2/sampletest4.temp.mjs',
|
'test/testtree/folder2/sampletest4.temp.mjs',
|
||||||
])
|
])
|
||||||
|
|
||||||
cli.targets = ['test/*/sampletest*.mjs']
|
cli.targets = ['test/testtree/*/sampletest*.mjs']
|
||||||
await cli.processTargets()
|
await cli.processTargets()
|
||||||
|
|
||||||
assert.strictEqual(cli.files.length, 4)
|
assert.strictEqual(cli.files.length, 4)
|
||||||
cli.files.sort()
|
cli.files.sort()
|
||||||
assert.deepEqual(cli.files, [
|
assert.deepEqual(cli.files, [
|
||||||
'test/folder1/sampletest1.temp.mjs',
|
'test/testtree/folder1/sampletest1.temp.mjs',
|
||||||
'test/folder1/sampletest2.temp.mjs',
|
'test/testtree/folder1/sampletest2.temp.mjs',
|
||||||
'test/folder2/sampletest3.temp.mjs',
|
'test/testtree/folder2/sampletest3.temp.mjs',
|
||||||
'test/folder2/sampletest4.temp.mjs',
|
'test/testtree/folder2/sampletest4.temp.mjs',
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -223,11 +223,11 @@ t.describe('CLI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < cli.files.length; i++) {
|
for (let i = 0; i < cli.files.length; i++) {
|
||||||
found.sampletest1 = found.sampletest1 || cli.files[i] === 'test/folder1/sampletest1.temp.mjs'
|
found.sampletest1 = found.sampletest1 || cli.files[i] === 'test/testtree/folder1/sampletest1.temp.mjs'
|
||||||
found.sampletest2 = found.sampletest2 || cli.files[i] === 'test/folder1/sampletest2.temp.mjs'
|
found.sampletest2 = found.sampletest2 || cli.files[i] === 'test/testtree/folder1/sampletest2.temp.mjs'
|
||||||
found.sampletest3 = found.sampletest3 || cli.files[i] === 'test/folder2/sampletest3.temp.mjs'
|
found.sampletest3 = found.sampletest3 || cli.files[i] === 'test/testtree/folder2/sampletest3.temp.mjs'
|
||||||
found.sampletest4 = found.sampletest4 || cli.files[i] === 'test/folder2/sampletest4.temp.mjs'
|
found.sampletest4 = found.sampletest4 || cli.files[i] === 'test/testtree/folder2/sampletest4.temp.mjs'
|
||||||
found.sampletest5 = found.sampletest5 || cli.files[i] === 'test/folder2/sampletest5.temp.txt'
|
found.sampletest5 = found.sampletest5 || cli.files[i] === 'test/testtree/folder2/sampletest5.temp.txt'
|
||||||
found.cli = found.cli || cli.files[i] === 'test/cli.test.mjs'
|
found.cli = found.cli || cli.files[i] === 'test/cli.test.mjs'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,11 +257,11 @@ t.describe('CLI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < cli.files.length; i++) {
|
for (let i = 0; i < cli.files.length; i++) {
|
||||||
found.sampletest1 = found.sampletest1 || cli.files[i] === 'test/folder1/sampletest1.temp.mjs'
|
found.sampletest1 = found.sampletest1 || cli.files[i] === 'test/testtree/folder1/sampletest1.temp.mjs'
|
||||||
found.sampletest2 = found.sampletest2 || cli.files[i] === 'test/folder1/sampletest2.temp.mjs'
|
found.sampletest2 = found.sampletest2 || cli.files[i] === 'test/testtree/folder1/sampletest2.temp.mjs'
|
||||||
found.sampletest3 = found.sampletest3 || cli.files[i] === 'test/folder2/sampletest3.temp.mjs'
|
found.sampletest3 = found.sampletest3 || cli.files[i] === 'test/testtree/folder2/sampletest3.temp.mjs'
|
||||||
found.sampletest4 = found.sampletest4 || cli.files[i] === 'test/folder2/sampletest4.temp.mjs'
|
found.sampletest4 = found.sampletest4 || cli.files[i] === 'test/testtree/folder2/sampletest4.temp.mjs'
|
||||||
found.sampletest5 = found.sampletest5 || cli.files[i] === 'test/folder2/sampletest5.temp.txt'
|
found.sampletest5 = found.sampletest5 || cli.files[i] === 'test/testtree/folder2/sampletest5.temp.txt'
|
||||||
found.cli = found.cli || cli.files[i] === 'test/cli.test.mjs'
|
found.cli = found.cli || cli.files[i] === 'test/cli.test.mjs'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -226,6 +226,24 @@ e.test('Eltro should support callback rejected errors', async function() {
|
||||||
assert.strictEqual(t.failedTests[0].error, assertError)
|
assert.strictEqual(t.failedTests[0].error, assertError)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
e.test('Eltro should support capturing unknown errors outside scope', async function() {
|
||||||
|
testsWereRun = true
|
||||||
|
const assertError = new Error()
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.test('', function(cb) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
console.log('throw')
|
||||||
|
throw assertError
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 1)
|
||||||
|
assert.strictEqual(t.failedTests[0].error, assertError)
|
||||||
|
})
|
||||||
|
|
||||||
e.test('Eltro should support timing out tests', async function() {
|
e.test('Eltro should support timing out tests', async function() {
|
||||||
testsWereRun = true
|
testsWereRun = true
|
||||||
const t = CreateT()
|
const t = CreateT()
|
||||||
|
|
862
test/watch.test.mjs
Normal file
862
test/watch.test.mjs
Normal file
|
@ -0,0 +1,862 @@
|
||||||
|
import path from 'path'
|
||||||
|
import assert from '../lib/assert.mjs'
|
||||||
|
import t from '../lib/eltro.mjs'
|
||||||
|
import { Builder, Counter } from './watch/builder.mjs'
|
||||||
|
import watch from '../lib/watch/index.mjs'
|
||||||
|
import * as is from '../lib/watch/is.mjs'
|
||||||
|
import hasNativeRecursive from '../lib/watch/has-native-recursive.mjs'
|
||||||
|
|
||||||
|
const builder = new Builder()
|
||||||
|
let watcher
|
||||||
|
|
||||||
|
function htTimeToMs(end) {
|
||||||
|
return end[0] * 1000 + Math.round(end[1] / 1000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
return builder.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.afterEach(function(done) {
|
||||||
|
if (watcher && !watcher.isClosed()) {
|
||||||
|
watcher.on('close', done)
|
||||||
|
watcher.close()
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.after(function() {
|
||||||
|
if (builder) {
|
||||||
|
builder.cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function wait(fn, timeout) {
|
||||||
|
try {
|
||||||
|
fn()
|
||||||
|
} catch (error) {
|
||||||
|
timeout -= 30
|
||||||
|
if (timeout >= 0) {
|
||||||
|
setTimeout(function() {
|
||||||
|
wait(fn, timeout)
|
||||||
|
}, 30)
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
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() {
|
||||||
|
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(() => {
|
||||||
|
watcher = watch(fpath, Object.defineProperty({}, 'test', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function() {
|
||||||
|
builder.removeSync('home/a')
|
||||||
|
return 'test'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
watcher.on('error', 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')
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
|
||||||
|
t.before(() => {
|
||||||
|
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)
|
||||||
|
}))
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
]
|
||||||
|
|
||||||
|
var changes = []
|
||||||
|
watcher = watch(fpaths, { delay: 100, recursive: true }, function(evt, name) {
|
||||||
|
changes.push(name)
|
||||||
|
})
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
watcher.getWatchedPaths(function(dirs) {
|
||||||
|
assert(dirs.length === 0)
|
||||||
|
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() {
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
watched.sort(), paths.sort()
|
||||||
|
)
|
||||||
|
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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.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(function(paths) {
|
||||||
|
hasNativeRecursive(function(supportRecursive) {
|
||||||
|
var watched = supportRecursive
|
||||||
|
? [a, b, nested]
|
||||||
|
: [a, b, nested, ma, mb, mc]
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
watched.sort(), paths.sort()
|
||||||
|
)
|
||||||
|
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
234
test/watch/builder.mjs
Normal file
234
test/watch/builder.mjs
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import fsSync from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
let structure = `
|
||||||
|
home/
|
||||||
|
a/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
b/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
c/
|
||||||
|
bb/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
d/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
e/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
sub/
|
||||||
|
deep_node_modules/
|
||||||
|
ma/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
mb/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
mc/
|
||||||
|
`
|
||||||
|
|
||||||
|
let code = structure
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line)
|
||||||
|
.map(function(line) {
|
||||||
|
return {
|
||||||
|
indent: line.length - line.replace(/^\s+/,'').length,
|
||||||
|
type: /\/$/.test(line) ? 'dir': 'file',
|
||||||
|
text: line.replace(/^\s+|\s*\/\s*|\s+$/g, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function join(arr) {
|
||||||
|
return arr.join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
function transform(arr) {
|
||||||
|
let result = []
|
||||||
|
let temp = []
|
||||||
|
let indent = 0
|
||||||
|
arr.forEach(function(line) {
|
||||||
|
if (!line.text) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (!line.indent) {
|
||||||
|
temp.push(line.text)
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
else if (indent < line.indent) {
|
||||||
|
temp.push(line.text)
|
||||||
|
result[result.length - 1].type = 'dir'
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
else if (indent === line.indent) {
|
||||||
|
temp.pop()
|
||||||
|
temp.push(line.text)
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
else if(indent > line.indent) {
|
||||||
|
temp.pop()
|
||||||
|
temp.pop()
|
||||||
|
temp.push(line.text)
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
|
||||||
|
indent = line.indent
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function gracefully(promise) {
|
||||||
|
return promise.catch(function(err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyFile(target) {
|
||||||
|
let folder = target.slice(0, target.lastIndexOf('/'))
|
||||||
|
|
||||||
|
/*return fs.mkdir(folder, { recursive: true, force: true })
|
||||||
|
.then(() => fs.open(target, 'w').then(fd => fd.close()))*/
|
||||||
|
|
||||||
|
return fs.stat(folder)
|
||||||
|
.catch(() => fs.mkdir(folder, { recursive: true, force: true }))
|
||||||
|
.then(() => {
|
||||||
|
return fs.open(target, 'w').then(fd => fd.close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Builder() {
|
||||||
|
this.root = path.join(__dirname, '__TREE__')
|
||||||
|
this.transformed = transform(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.init = async function() {
|
||||||
|
await Promise.all(
|
||||||
|
this.transformed.filter(line => line.type === 'dir').map(line => {
|
||||||
|
let target = path.join(this.root, line.text)
|
||||||
|
return gracefully(fs.mkdir(target, { recursive: true }))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await Promise.all(
|
||||||
|
this.transformed.filter(line => line.type !== 'dir').map(line => {
|
||||||
|
let target = path.join(this.root, line.text)
|
||||||
|
return gracefully(emptyFile(target))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.getPath = function(fpath, sub) {
|
||||||
|
return path.join(this.root, fpath, sub || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.modify = function(fpath, delay) {
|
||||||
|
if (delay) throw new Error('remove delay')
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fs.appendFile(filePath, 'hello')
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.remove = function(fpath) {
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fs.rm(filePath, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.removeSync = function(fpath) {
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fsSync.rmSync(filePath, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.newFile = function(fpath, delay) {
|
||||||
|
if (delay) throw new Error('remove delay')
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return emptyFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.createDirectory = function(fpath) {
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fs.mkdir(filePath, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.randomName = function() {
|
||||||
|
return Math.random().toString(36).substring(2, 14)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.newRandomFiles = function(fpath, count) {
|
||||||
|
return Promise.all(
|
||||||
|
new Array(count).fill(0).map(() => {
|
||||||
|
let name = this.randomName()
|
||||||
|
let filePath = this.getPath(fpath, name)
|
||||||
|
|
||||||
|
return emptyFile(filePath).then(() => name)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.cleanup = function() {
|
||||||
|
return fs.rmdir(this.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.getAllDirectories = function() {
|
||||||
|
function walk(dir) {
|
||||||
|
let ret = []
|
||||||
|
fsSync.readdirSync(dir).forEach(function(d) {
|
||||||
|
let fpath = path.join(dir, d)
|
||||||
|
if (fsSync.statSync(fpath).isDirectory()) {
|
||||||
|
ret.push(fpath)
|
||||||
|
ret = ret.concat(walk(fpath))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return walk(this.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Counter(res, countTotal, waitForSignal = false) {
|
||||||
|
this._res = res
|
||||||
|
this.counter = 0
|
||||||
|
this._countTotal = countTotal
|
||||||
|
this._startCount = !waitForSignal
|
||||||
|
this._gotSignal = false
|
||||||
|
this._signal = null
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.waitForCount = function(promise) {
|
||||||
|
if (!promise) {
|
||||||
|
promise = Promise.resolve()
|
||||||
|
}
|
||||||
|
return new Promise(res => {
|
||||||
|
promise.then(() => {
|
||||||
|
this._signal = res
|
||||||
|
this.hasSignal()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.hasSignal = function() {
|
||||||
|
if (this._gotSignal && this._signal) {
|
||||||
|
this._gotSignal = false
|
||||||
|
let temp = this._signal
|
||||||
|
this._signal = null
|
||||||
|
temp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.startCounting = function() {
|
||||||
|
this._startCount = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.count = function() {
|
||||||
|
if (!this._startCount) return
|
||||||
|
|
||||||
|
this._gotSignal = true
|
||||||
|
this.counter++
|
||||||
|
if (this.counter === this._countTotal && this._res) {
|
||||||
|
this._res()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasSignal()
|
||||||
|
}
|
Loading…
Reference in a new issue