Huge new feature: File watcher and automatic watch and run, both tests and npm. Major improvements or refactoring in cli.
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
This commit is contained in:
parent
25f50483e1
commit
5c9ead16b6
11 changed files with 1658 additions and 325 deletions
75
cli.mjs
75
cli.mjs
|
@ -6,15 +6,6 @@ const [,, ...args] = process.argv
|
|||
import e from './lib/eltro.mjs'
|
||||
import { CLI, printError } from './lib/cli.mjs'
|
||||
|
||||
e.begin()
|
||||
|
||||
const cli = new CLI(e)
|
||||
cli.parseOptions(args)
|
||||
|
||||
if (cli.errored) {
|
||||
PrintHelp()
|
||||
}
|
||||
|
||||
function PrintHelp() {
|
||||
console.log('')
|
||||
console.log('Usage: eltro <options> <files>')
|
||||
|
@ -25,6 +16,8 @@ function PrintHelp() {
|
|||
console.log(' Supported reporters: list, dot')
|
||||
console.log(' -t, --timeout - Specify the timeout for tests in ms.')
|
||||
console.log(' Default value is 2000ms')
|
||||
console.log(' -w, --watch - specify which group of files to watch from package.json')
|
||||
console.log(' ')
|
||||
console.log(' --ignore-only - Specify to ignore any .only() tests found')
|
||||
console.log('')
|
||||
console.log('eltro test/mytest.mjs')
|
||||
|
@ -34,43 +27,47 @@ function PrintHelp() {
|
|||
process.exit(1)
|
||||
}
|
||||
|
||||
cli.processTargets().then(function() {
|
||||
if (!cli.files.length) {
|
||||
console.log('')
|
||||
console.log('No files were found with pattern', cli.targets.join(','))
|
||||
function showErrorAndExit(message = '', err = null, code = 1) {
|
||||
console.log('')
|
||||
|
||||
if (message) {
|
||||
console.error(`\x1b[31m${message}\x1b[0m`)
|
||||
}
|
||||
|
||||
if (err) {
|
||||
printError(err)
|
||||
} else {
|
||||
PrintHelp()
|
||||
}
|
||||
return cli.loadFiles()
|
||||
.then(function() {
|
||||
e.reporter = cli.reporter
|
||||
e.ignoreOnly = cli.ignoreOnly
|
||||
e.__timeout = cli.timeout
|
||||
process.exit(code)
|
||||
}
|
||||
|
||||
return e.run()
|
||||
.catch(function(err) {
|
||||
console.log('')
|
||||
console.error('\x1b[31mUnknown error occured while running the tests\x1b[0m')
|
||||
printError(err)
|
||||
process.exit(1)
|
||||
})
|
||||
}, function(err) {
|
||||
console.log('')
|
||||
console.error('\x1b[31m' + err.message + '\x1b[0m')
|
||||
printError(err.inner)
|
||||
process.exit(1)
|
||||
})
|
||||
}, function(err) {
|
||||
console.log('')
|
||||
console.error('\x1b[31mUnknown error while processing arguments\x1b[0m')
|
||||
printError(err)
|
||||
process.exit(1)
|
||||
const cli = new CLI(e)
|
||||
cli.parseOptions(args)
|
||||
.catch(function(err) { showErrorAndExit(err.message) })
|
||||
.then(function() {
|
||||
return cli.startWatcher()
|
||||
})
|
||||
.catch(function(err) { showErrorAndExit('Unknown error while starting watcher', err) })
|
||||
.then(function() {
|
||||
return cli.getFiles()
|
||||
})
|
||||
.catch(function(err) { showErrorAndExit('Unknown error while processing arguments', err) })
|
||||
.then(function() {
|
||||
if (!cli.files.length) {
|
||||
showErrorAndExit('No files were found with pattern', cli.targets.join(','))
|
||||
}
|
||||
|
||||
return cli.loadFiles()
|
||||
})
|
||||
.catch(function(err) { showErrorAndExit('', err) })
|
||||
.then(function() {
|
||||
return cli.beginRun()
|
||||
})
|
||||
.catch(function(err) { showErrorAndExit('Unknown error occured while running the tests', err) })
|
||||
.then(function(stats) {
|
||||
if (stats.failed > 0) {
|
||||
process.exit(10)
|
||||
}
|
||||
process.exit(0)
|
||||
}, function(err) {
|
||||
console.error('\x1b[31mInternal error occured:\x1b[0m', err)
|
||||
process.exit(2)
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export function runWithCallbackSafe(test) {
|
||||
let finished = false
|
||||
return new Promise(function(res, rej) {
|
||||
try {
|
||||
let cb = function(err) {
|
||||
|
@ -7,27 +8,30 @@ export function runWithCallbackSafe(test) {
|
|||
}
|
||||
res()
|
||||
}
|
||||
let safeWrap = function(finish) {
|
||||
// return a safe wrap support
|
||||
return function(fun) {
|
||||
return function(a, b, c) {
|
||||
try {
|
||||
fun(a, b, c)
|
||||
if (finish) {
|
||||
res()
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return rej(err)
|
||||
}
|
||||
}
|
||||
let safeWrap = function(fn, ...args) {
|
||||
try {
|
||||
return fn(...args)
|
||||
}
|
||||
catch (err) {
|
||||
return rej(err)
|
||||
}
|
||||
}
|
||||
cb.wrap = safeWrap(false)
|
||||
cb.finish = safeWrap(true)
|
||||
let safeWrapInFunction = function(finish, fn) {
|
||||
return function(...args) {
|
||||
safeWrap(fn, ...args)
|
||||
if (finish && !finished) { res() }
|
||||
}
|
||||
}
|
||||
cb.wrap = safeWrapInFunction.bind(this, false)
|
||||
cb.finish = safeWrapInFunction.bind(this, true)
|
||||
cb.safeWrap = safeWrap
|
||||
test.func(cb)
|
||||
} catch (err) {
|
||||
rej(err)
|
||||
}
|
||||
})
|
||||
.then(
|
||||
function() { finished = true },
|
||||
function(err) { finished = true; return Promise.reject(err) }
|
||||
)
|
||||
}
|
293
lib/cli.mjs
293
lib/cli.mjs
|
@ -1,47 +1,110 @@
|
|||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import fsPromise from 'fs/promises'
|
||||
import cluster from 'cluster'
|
||||
import child_process from 'child_process'
|
||||
import Watcher, { EVENT_REMOVE, EVENT_UPDATE } from './watch/index.mjs'
|
||||
|
||||
export function CLI(e) {
|
||||
export const MESSAGE_FILES_REQUEST = 'message_files_request'
|
||||
export const MESSAGE_FILES_PAYLOAD = 'message_files_payload'
|
||||
export const MESSAGE_RUN_FINISHED = 'message_run_finished'
|
||||
|
||||
export function createMessage(type, data = null) {
|
||||
return {
|
||||
messageType: type,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
const RegexCache = new Map()
|
||||
|
||||
function targetToRegex(target) {
|
||||
let regex = RegexCache.get(target)
|
||||
if (!regex) {
|
||||
let parsed = target.startsWith('./') ? target.slice(2) : target
|
||||
parsed = parsed.endsWith('/') ? parsed + '*' : parsed
|
||||
RegexCache.set(target, regex = new RegExp('^' +
|
||||
parsed.replace(/\./g, '\\.')
|
||||
.replace(/\*\*\/?/g, '&&&&&&&&&&&&&')
|
||||
.replace(/\*/g, '[^/]+')
|
||||
.replace(/&&&&&&&&&&&&&/g, '.*')
|
||||
+ '$'
|
||||
))
|
||||
}
|
||||
return regex
|
||||
}
|
||||
|
||||
export function CLI(e, overrides = {}) {
|
||||
this.e = e
|
||||
this.ac = new AbortController()
|
||||
this.cluster = overrides.cluster || cluster
|
||||
this.logger = overrides.logger || console
|
||||
this.child_process = overrides.child_process || child_process
|
||||
this.process = overrides.process || process
|
||||
this.importer = overrides.importer
|
||||
|
||||
// Eltro specific options
|
||||
this.reporter = 'list'
|
||||
this.ignoreOnly = false
|
||||
this.timeout = 2000
|
||||
|
||||
// Cli specific options
|
||||
this.watch = null
|
||||
this.watcher = null
|
||||
this.worker = null
|
||||
this.run = 'test'
|
||||
this.isSlave = this.cluster.isWorker || false
|
||||
this.targets = ['test/**']
|
||||
this.files = []
|
||||
this.errored = false
|
||||
}
|
||||
|
||||
CLI.prototype.fileMatchesTarget = function(path) {
|
||||
for (let target of this.targets) {
|
||||
if (targetToRegex(target).test(path)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
CLI.prototype.parseOptions = function(args) {
|
||||
if (!args || !args.length) {
|
||||
this.targets.push('test/**')
|
||||
this.errored = false
|
||||
return
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
this.errored = false
|
||||
this.targets.splice(0, this.targets.length)
|
||||
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '-r' || args[i] === '--reporter') {
|
||||
if (!args[i + 1] || (args[i + 1] !== 'list' && args[i + 1] !== 'dot')) {
|
||||
this.errored = true
|
||||
return
|
||||
return Promise.reject(new Error('Reporter was missing or invalid. Only "list" and "dot" are supported.'))
|
||||
}
|
||||
this.reporter = args[i + 1]
|
||||
i++
|
||||
} else if (args[i] === '-t' || args[i] === '--timeout') {
|
||||
if (!args[i + 1] || isNaN(Number(args[i + 1]))) {
|
||||
this.errored = true
|
||||
return
|
||||
return Promise.reject(new Error('Timeout was missing or invalid'))
|
||||
}
|
||||
this.timeout = Number(args[i + 1])
|
||||
i++
|
||||
} else if (args[i] === '-w' || args[i] === '--watch') {
|
||||
if (!args[i + 1] || args[i + 1][0] === '-') {
|
||||
return Promise.reject(new Error('Watch was missing or invalid'))
|
||||
}
|
||||
this.watch = args[i + 1]
|
||||
i++
|
||||
} else if (args[i] === '-n' || args[i] === '--npm') {
|
||||
if (!args[i + 1] || args[i + 1][0] === '-') {
|
||||
return Promise.reject(new Error('Npm was missing or invalid'))
|
||||
}
|
||||
this.run = args[i + 1]
|
||||
i++
|
||||
} else if (args[i] === '--ignore-only') {
|
||||
this.ignoreOnly = true
|
||||
} else if (args[i][0] === '-') {
|
||||
this.errored = true
|
||||
return
|
||||
return Promise.reject(new Error(`Unknown option ${args[i]}`))
|
||||
} else {
|
||||
this.targets.push(args[i])
|
||||
}
|
||||
|
@ -50,9 +113,72 @@ CLI.prototype.parseOptions = function(args) {
|
|||
if (!this.targets.length) {
|
||||
this.targets.push('test/**')
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
CLI.prototype.processTargets = function() {
|
||||
CLI.prototype.startWatcher = async function() {
|
||||
if (!this.watch || this.isSlave) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
let packageJson
|
||||
try {
|
||||
packageJson = JSON.parse(await fsPromise.readFile('package.json'))
|
||||
} catch (err) {
|
||||
throw new Error(`package.json was missing or invalid JSON: ${err.message}`)
|
||||
}
|
||||
|
||||
let currentGroup = packageJson.watch && packageJson.watch[this.watch]
|
||||
|
||||
if (!currentGroup || !currentGroup.patterns) {
|
||||
throw new Error(`package.json was missing watch property or missing ${this.watch} in watch or missing pattern`)
|
||||
}
|
||||
|
||||
if (!currentGroup.extensions) {
|
||||
currentGroup.extensions = 'js,mjs'
|
||||
} else if (!currentGroup.extensions.match(/^([a-zA-Z]{2,3})(,[a-zA-Z]{2,3})*$/)) {
|
||||
throw new Error(`package.json watch ${this.watch} extension "${currentGroup.extensions}" was invalid`)
|
||||
}
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
this.watcher = new Watcher(currentGroup.patterns, {
|
||||
quickNativeCheck: true,
|
||||
delay: currentGroup.delay || 200,
|
||||
skip: function(name) {
|
||||
return name.indexOf('node_modules') >= 0
|
||||
},
|
||||
filter: new RegExp(currentGroup.extensions.split(',').map(x => `(\\.${x}$)`).join('|'))
|
||||
})
|
||||
this.watcher.once('error', rej)
|
||||
this.watcher.once('ready', () => {
|
||||
this.watcher.off('error', rej)
|
||||
res()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
CLI.prototype.getFiles = function() {
|
||||
if (this.isSlave) {
|
||||
return this._askMasterForFiles()
|
||||
} else {
|
||||
return this._processTargets()
|
||||
}
|
||||
}
|
||||
|
||||
CLI.prototype._askMasterForFiles = function() {
|
||||
return new Promise(res => {
|
||||
const handler = (payload) => {
|
||||
if (isMessageInvalid(payload, MESSAGE_FILES_PAYLOAD)) return
|
||||
this.process.off('message', handler)
|
||||
res(this.files = payload.data)
|
||||
}
|
||||
this.process.on('message', handler)
|
||||
this.process.send(createMessage(MESSAGE_FILES_REQUEST))
|
||||
})
|
||||
}
|
||||
|
||||
CLI.prototype._processTargets = function() {
|
||||
this.files.splice(0, this.files.length)
|
||||
|
||||
if (!this.targets.length) {
|
||||
|
@ -60,22 +186,28 @@ CLI.prototype.processTargets = function() {
|
|||
}
|
||||
|
||||
return Promise.all(this.targets.map((target) => {
|
||||
return getFiles(this.files, target)
|
||||
return getFilesFromTarget(this.files, target)
|
||||
})).then(() => {
|
||||
if (!this.files.length) {
|
||||
this.errored = 'empty'
|
||||
}
|
||||
return this.files
|
||||
})
|
||||
}
|
||||
|
||||
CLI.prototype.loadFiles = async function() {
|
||||
let cwd = process.cwd()
|
||||
if (!this.isSlave && this.watch) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
this.e.begin()
|
||||
|
||||
let cwd = this.process.cwd()
|
||||
|
||||
for (let i = 0; i < this.files.length; i++) {
|
||||
if (this.files[i].endsWith('.mjs') || this.files[i].endsWith('.js')) {
|
||||
try {
|
||||
this.e.setFilename(this.files[i])
|
||||
await import('file:///' + path.join(cwd, this.files[i]))
|
||||
await this.import('file:///' + path.join(cwd, this.files[i]))
|
||||
this.e.resetFilename()
|
||||
} catch (e) {
|
||||
let newError = new Error(`Error while loading ${this.files[i]}`)
|
||||
|
@ -86,6 +218,131 @@ CLI.prototype.loadFiles = async function() {
|
|||
}
|
||||
}
|
||||
|
||||
CLI.prototype.beginRun = async function() {
|
||||
if (this.watcher) {
|
||||
return this._runWorker()
|
||||
} else {
|
||||
return this._runTests()
|
||||
}
|
||||
}
|
||||
|
||||
CLI.prototype._runTests = function() {
|
||||
this.e.reporter = this.reporter
|
||||
this.e.ignoreOnly = this.ignoreOnly
|
||||
this.e.__timeout = this.timeout
|
||||
|
||||
return this.e.run().then((stats) => {
|
||||
if (this.isSlave) {
|
||||
this.process.send(createMessage(MESSAGE_RUN_FINISHED, { stats: stats }))
|
||||
}
|
||||
return stats
|
||||
})
|
||||
}
|
||||
|
||||
CLI.prototype._runWorker = function() {
|
||||
let lastStats = null
|
||||
|
||||
const messageHandler = (payload) => {
|
||||
if (isMessageInvalid(payload, MESSAGE_RUN_FINISHED)) return
|
||||
lastStats = payload.data.stats
|
||||
}
|
||||
const changeHandler = (evt, name) => {
|
||||
if (evt === EVENT_UPDATE && !this.files.includes(name) && this.fileMatchesTarget(name)) {
|
||||
this.files.push(name)
|
||||
} else if (evt === EVENT_REMOVE) {
|
||||
let index = this.files.indexOf(name)
|
||||
if (index >= 0) {
|
||||
this.files.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
const changedHandler = () => {
|
||||
this.runProgram()
|
||||
}
|
||||
|
||||
return new Promise(res => {
|
||||
const cleanup = () => {
|
||||
this.process.off('message', messageHandler)
|
||||
this.watcher.off('change', changeHandler)
|
||||
this.watcher.off('changed', changedHandler)
|
||||
res(lastStats)
|
||||
}
|
||||
|
||||
this.process.on('message', messageHandler)
|
||||
this.watcher.on('change', changeHandler)
|
||||
this.watcher.on('changed', changedHandler)
|
||||
|
||||
this.ac.signal.addEventListener('abort', cleanup, { once: true });
|
||||
changedHandler()
|
||||
})
|
||||
}
|
||||
|
||||
CLI.prototype.runProgram = function() {
|
||||
let runningTest = this.run === 'test'
|
||||
|
||||
if (this.worker) {
|
||||
if (runningTest) {
|
||||
return
|
||||
} else {
|
||||
this.worker.kill()
|
||||
}
|
||||
}
|
||||
|
||||
let worker
|
||||
if (runningTest) {
|
||||
worker = this.worker = this.cluster.fork()
|
||||
} else {
|
||||
worker = this.worker = this.child_process.spawn('npm', ['run', this.run])
|
||||
}
|
||||
|
||||
worker.once('exit', (exitCode) => {
|
||||
if (this.worker !== worker) return
|
||||
|
||||
let currentTime = new Date().toISOString().split('T')[1].split('.')[0]
|
||||
if (!runningTest) {
|
||||
console.log()
|
||||
}
|
||||
if (exitCode > 0) {
|
||||
console.error(`\x1b[31m[${this.watch}] ${currentTime}: Exited with error code ${exitCode}. Waiting for file changes before running again...\x1b[0m`)
|
||||
} else {
|
||||
console.error(`\x1b[32m[${this.watch}] ${currentTime}: Ran successfully. Waiting for file changes before running again...\x1b[0m`)
|
||||
}
|
||||
this.worker = null
|
||||
})
|
||||
if (runningTest) {
|
||||
worker.on('message', (msg) => {
|
||||
if (isMessageValid(msg, MESSAGE_FILES_REQUEST)) {
|
||||
worker.send(createMessage(MESSAGE_FILES_PAYLOAD, this.files))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
worker.stdout.on('data', (d) => { this.process.stdout.write(d.toString()) })
|
||||
worker.stderr.on('data', (d) => { this.process.stderr.write(d.toString()) })
|
||||
}
|
||||
}
|
||||
|
||||
CLI.prototype.import = function(path) {
|
||||
if (this.importer) {
|
||||
return this.importer(path)
|
||||
}
|
||||
return import(path)
|
||||
}
|
||||
|
||||
function isMessageInvalid(payload, messageType) {
|
||||
if (!payload
|
||||
|| typeof(payload) !== 'object'
|
||||
|| typeof(payload.messageType) !== 'string'
|
||||
|| payload.messageType !== messageType
|
||||
|| (payload.data != null && typeof(payload.data) !== 'object')) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function isMessageValid(payload, messageType) {
|
||||
return !isMessageInvalid(payload, messageType)
|
||||
}
|
||||
|
||||
function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, includeFiles) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return fs.readdir(curr, function(err, data) {
|
||||
|
@ -105,7 +362,7 @@ function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, inc
|
|||
if (stat.isDirectory() && grabAll) {
|
||||
return res(traverseFolder(files, path.join(curr, file), match, path.join(insidePath, file), grabAll, insideStar, includeFiles))
|
||||
} else if (stat.isDirectory() && match) {
|
||||
return res(getFiles(files, match, path.join(insidePath, file), grabAll, insideStar))
|
||||
return res(getFilesFromTarget(files, match, path.join(insidePath, file), grabAll, insideStar))
|
||||
}
|
||||
res(null)
|
||||
})
|
||||
|
@ -119,7 +376,7 @@ export function fileMatches(filename, match) {
|
|||
return Boolean(filename.match(new RegExp(match.replace(/\./, '\\.').replace(/\*/, '.*'))))
|
||||
}
|
||||
|
||||
export function getFiles(files, match, insidePath, grabAll, insideStar) {
|
||||
export function getFilesFromTarget(files, match, insidePath, grabAll, insideStar) {
|
||||
let isGrabbingAll = grabAll || false
|
||||
let isStarred = insideStar || false
|
||||
let cwd = process.cwd()
|
||||
|
@ -148,7 +405,7 @@ export function getFiles(files, match, insidePath, grabAll, insideStar) {
|
|||
return traverseFolder(files, curr, splitted.slice(start + 1).join('/'), currPath, isGrabbingAll, isStarred, false)
|
||||
.then(res, rej)
|
||||
}
|
||||
return getFiles(files, splitted.slice(start + 1).join('/'), path.join(currPath, first), grabAll, isStarred)
|
||||
return getFilesFromTarget(files, splitted.slice(start + 1).join('/'), path.join(currPath, first), grabAll, isStarred)
|
||||
.then(res, rej)
|
||||
} else if (first.indexOf('*') >= 0) {
|
||||
if (first === '**') {
|
||||
|
|
|
@ -126,7 +126,7 @@ function Eltro() {
|
|||
this.activeGroup = null
|
||||
this.failedTests = []
|
||||
this.hasTests = false
|
||||
this.starting = false
|
||||
this.starting = null
|
||||
this.ignoreOnly = false
|
||||
this.logger = null
|
||||
this.filename = ''
|
||||
|
@ -147,10 +147,12 @@ function Eltro() {
|
|||
Eltro.prototype.begin = function() {
|
||||
if (this.starting) {
|
||||
console.warn('WARNING: Multiple calls to Eltro.begin were done.')
|
||||
console.warn(this.starting)
|
||||
console.warn(new Error('Second call'))
|
||||
return
|
||||
}
|
||||
this.hasTests = false
|
||||
this.starting = true
|
||||
this.starting = new Error('First call')
|
||||
this.filename = ''
|
||||
this.prefix = ''
|
||||
this.fileGroupMap.clear()
|
||||
|
@ -165,6 +167,12 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
|||
|
||||
if (!test.skipTest) {
|
||||
let err = await new Promise((resolve, reject) => {
|
||||
if (test.error) {
|
||||
return reject(test.error)
|
||||
}
|
||||
if (!test.func) {
|
||||
return reject(new Error(`Test ${test.name} was missing function`))
|
||||
}
|
||||
this.captureOutsideExceptions = reject
|
||||
// Flag to check if we finished
|
||||
let finished = false
|
||||
|
@ -518,9 +526,18 @@ Eltro.prototype.test = function(name, func) {
|
|||
throw new Error('Tests outside groups are not allowed.')
|
||||
}
|
||||
|
||||
let test = new Test(this, this.activeGroup, this.activeGroup.name + ' ' + name, func)
|
||||
let test = new Test(
|
||||
this,
|
||||
this.activeGroup,
|
||||
[this.activeGroup.name.trim(), (name || '').trim()].filter(x => x).join(' '),
|
||||
func
|
||||
)
|
||||
this.activeGroup.tests.push(test)
|
||||
|
||||
if (name == null) {
|
||||
test.error = new Error(`An empty test or missing name under ${this.activeGroup.name.trim()} was found`)
|
||||
}
|
||||
|
||||
if (this.temporary.only && !this.temporary.skip) {
|
||||
test.only()
|
||||
this.temporary.only = false
|
||||
|
|
|
@ -52,7 +52,7 @@ TempStack.prototype.cleanup = function(fn) {
|
|||
|
||||
let pending = false
|
||||
|
||||
export default function hasNativeRecursive(fn) {
|
||||
export default function hasNativeRecursive(fn, opts = {}) {
|
||||
if (!is.func(fn)) {
|
||||
return false
|
||||
}
|
||||
|
@ -60,6 +60,10 @@ export default function hasNativeRecursive(fn) {
|
|||
return fn(IS_SUPPORT)
|
||||
}
|
||||
|
||||
if (opts.quickCheck) {
|
||||
return fn(IS_SUPPORT = (process.platform === 'darwin' || process.platform === 'win32'))
|
||||
}
|
||||
|
||||
if (!pending) {
|
||||
pending = true
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ 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';
|
||||
export const EVENT_UPDATE = 'update';
|
||||
export const EVENT_REMOVE = 'remove';
|
||||
const TYPE_FILE = 'file'
|
||||
const TYPE_DIRECTORY = 'directory'
|
||||
|
||||
|
@ -39,6 +39,7 @@ export default class Watcher extends events.EventEmitter {
|
|||
paths = unique(paths)
|
||||
this.options = options || {}
|
||||
this.fn = fn || null
|
||||
this.originalPaths = paths
|
||||
|
||||
if (is.func(this.options)) {
|
||||
this.fn = this.options
|
||||
|
@ -92,7 +93,7 @@ export default class Watcher extends events.EventEmitter {
|
|||
this.supportsNativeRecursive = nativeRecursive
|
||||
this.options.manualRecursive = !nativeRecursive
|
||||
this._startListeners(paths)
|
||||
})
|
||||
}, { quickCheck: this.options.quickNativeCheck || true })
|
||||
} else {
|
||||
this._startListeners(paths)
|
||||
}
|
||||
|
@ -115,10 +116,18 @@ export default class Watcher extends events.EventEmitter {
|
|||
return null
|
||||
}
|
||||
|
||||
shouldInclude(name) {
|
||||
shouldSkip(name) {
|
||||
return this.options.skip
|
||||
?
|
||||
(is.func(this.options.skip) && this.options.skip.call(this, name))
|
||||
|| (is.regExp(this.options.skip) && this.options.skip.test(name))
|
||||
: false
|
||||
}
|
||||
|
||||
shouldNotify(name) {
|
||||
return this.options.filter
|
||||
?
|
||||
(is.func(this.options.filter) && this.options.filter.call(this, name) === true)
|
||||
(is.func(this.options.filter) && this.options.filter.call(this, name))
|
||||
|| (is.regExp(this.options.filter) && this.options.filter.test(name))
|
||||
: true
|
||||
}
|
||||
|
@ -126,10 +135,9 @@ export default class Watcher extends events.EventEmitter {
|
|||
closeWatch(orgItem) {
|
||||
let item = orgItem
|
||||
if (typeof item === 'string') {
|
||||
item = getWatcherOrNull(item)
|
||||
item = this.getWatcherOrNull(item)
|
||||
}
|
||||
if (!item) {
|
||||
this.emit('error', new Error(`attempted to close watcher for ${item} but such a watcher could not be found`))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -145,7 +153,8 @@ export default class Watcher extends events.EventEmitter {
|
|||
|
||||
_emitEvent(item, evt, name) {
|
||||
if (item.type === TYPE_FILE && !is.samePath(name, item.filename)) return
|
||||
if (item.type === TYPE_DIRECTORY && !this.shouldInclude(name)) return
|
||||
if (item.type === TYPE_DIRECTORY && this.shouldSkip(name)) return
|
||||
if (!this.shouldNotify(name)) return
|
||||
|
||||
if (item.flag) {
|
||||
item.flag = ''
|
||||
|
@ -164,6 +173,7 @@ export default class Watcher extends events.EventEmitter {
|
|||
|
||||
if (!this.options.delay) {
|
||||
this.emit('change', evt, outputName)
|
||||
this.emit('changed', evt, outputName)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -183,6 +193,7 @@ export default class Watcher extends events.EventEmitter {
|
|||
this.emit('error', err)
|
||||
}
|
||||
}
|
||||
this.emit('changed')
|
||||
}, this.options.delay)
|
||||
}
|
||||
|
||||
|
@ -197,7 +208,7 @@ export default class Watcher extends events.EventEmitter {
|
|||
return out
|
||||
}
|
||||
|
||||
_watcherSink(item, rawEvt, rawName) {
|
||||
_watcherSink(item, rawEvt, rawName, c) {
|
||||
if (this.closed) return
|
||||
|
||||
let name = path.join(item.path, rawName || '')
|
||||
|
@ -209,9 +220,8 @@ export default class Watcher extends events.EventEmitter {
|
|||
return
|
||||
} else {
|
||||
if (is.directory(name)
|
||||
&& this.getWatcherOrNull(name) === null
|
||||
&& this.shouldInclude(name) === false) {
|
||||
this.safeAdd(subItem, TYPE_DIRECTORY)
|
||||
&& this.getWatcherOrNull(name) === null) {
|
||||
this.safeAdd(name, TYPE_DIRECTORY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +266,10 @@ export default class Watcher extends events.EventEmitter {
|
|||
type = is.file(name) ? TYPE_FILE : TYPE_DIRECTORY
|
||||
}
|
||||
|
||||
if (this.shouldSkip(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
let item = this._pathToItem(name, type)
|
||||
let options = {
|
||||
encoding: 'utf8',
|
||||
|
|
23
package.json
23
package.json
|
@ -4,17 +4,28 @@
|
|||
"description": "Eltro is a tiny no-dependancy test framework for node",
|
||||
"main": "index.mjs",
|
||||
"scripts": {
|
||||
"echo": "echo helloworld",
|
||||
"echo:watch": "node cli.mjs --watch test --npm echo",
|
||||
"test": "node cli.mjs \"test/**/*.test.mjs\"",
|
||||
"test:watch": "npm-watch test"
|
||||
"test:watch": "node cli.mjs \"test/**/*.test.mjs\" --watch test",
|
||||
"test:watch:legacy": "npm-watch test"
|
||||
},
|
||||
"watch": {
|
||||
"test": {
|
||||
"patterns": [
|
||||
"*"
|
||||
],
|
||||
"patterns": [ "lib", "test", "cli.mjs", "index.mjs" ],
|
||||
"extensions": "js,mjs",
|
||||
"quiet": true,
|
||||
"inherit": true
|
||||
"delay": 50
|
||||
},
|
||||
"test_quick": {
|
||||
"patterns": [ "cli.mjs", "index.mjs" ]
|
||||
},
|
||||
"test_quick_js": {
|
||||
"patterns": [ "cli.mjs", "index.mjs" ],
|
||||
"extensions": "js"
|
||||
},
|
||||
"test_invalid_extensions": {
|
||||
"patterns": [ "cli.mjs", "index.mjs" ],
|
||||
"extensions": ".js.bla"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
|
|
1195
test/cli.test.mjs
1195
test/cli.test.mjs
File diff suppressed because it is too large
Load diff
|
@ -244,6 +244,41 @@ e.test('Eltro should support capturing unknown errors outside scope', async func
|
|||
assert.strictEqual(t.failedTests[0].error, assertError)
|
||||
})
|
||||
|
||||
|
||||
e.test('Eltro should log an error if test is missing', async function() {
|
||||
testsWereRun = true
|
||||
const assertError = new Error()
|
||||
const t = CreateT()
|
||||
t.begin()
|
||||
t.describe('', function() {
|
||||
t.describe('herpderp', function() {
|
||||
t.test('blatest')
|
||||
})
|
||||
})
|
||||
await t.run()
|
||||
assert.strictEqual(t.failedTests.length, 1)
|
||||
assert.match(t.failedTests[0].error.message, /herpderp/)
|
||||
assert.match(t.failedTests[0].error.message, /blatest/)
|
||||
assert.match(t.failedTests[0].error.message, /missing/)
|
||||
})
|
||||
|
||||
e.test('Eltro should log an error if text is missing', async function() {
|
||||
testsWereRun = true
|
||||
const assertError = new Error()
|
||||
const t = CreateT()
|
||||
t.begin()
|
||||
t.describe('', function() {
|
||||
t.describe('herpderp', function() {
|
||||
t.test()
|
||||
})
|
||||
})
|
||||
await t.run()
|
||||
assert.strictEqual(t.failedTests.length, 1)
|
||||
assert.match(t.failedTests[0].error.message, /herpderp/)
|
||||
assert.match(t.failedTests[0].error.message, /empty/)
|
||||
assert.match(t.failedTests[0].error.message, /name/)
|
||||
})
|
||||
|
||||
e.test('Eltro should support timing out tests', async function() {
|
||||
testsWereRun = true
|
||||
const t = CreateT()
|
||||
|
@ -559,9 +594,8 @@ e.test('Eltro nested timeout should work as expected', async function() {
|
|||
// Extra testing to make sure tests were run at all
|
||||
process.on('exit', function(e) {
|
||||
try {
|
||||
assert.strictEqual(testsWereRun, true)
|
||||
assert.strictEqual(testsWereRun, true, 'Not all tests were run, remove all .only() and try again.')
|
||||
} catch(err) {
|
||||
console.log('Checking if tests were run at all failed:')
|
||||
printError(err)
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import assert from '../lib/assert.mjs'
|
|||
import t from '../lib/eltro.mjs'
|
||||
import { Builder, Counter } from './watch/builder.mjs'
|
||||
import Watcher from '../lib/watch/index.mjs'
|
||||
import * as is from '../lib/watch/is.mjs'
|
||||
|
||||
const builder = new Builder()
|
||||
let watcher
|
||||
|
@ -170,11 +171,11 @@ t.describe('watcher', function() {
|
|||
})
|
||||
|
||||
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')
|
||||
var fpath = builder.getPath('home/a/removeme/file1')
|
||||
builder.newFile('home/a/removeme/file1')
|
||||
.then(() => {
|
||||
watcher = new Watcher(fpath, null, null, { fs: { watch: function(path, options) {
|
||||
builder.removeSync('home/a')
|
||||
builder.removeSync('home/a/removeme')
|
||||
return fs.watch(path, options)
|
||||
} } })
|
||||
|
||||
|
@ -187,17 +188,17 @@ t.describe('watcher', function() {
|
|||
|
||||
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')
|
||||
var home = builder.getPath('home/c')
|
||||
var dir = builder.getPath('home/c/removeme')
|
||||
|
||||
builder.createDirectory('home/c').then(() => {
|
||||
builder.createDirectory('home/c/removeme').then(() => {
|
||||
watcher = new Watcher(home, { delay: 0, recursive: true }, function(evt, name) {
|
||||
if (name === dir && evt === 'remove') {
|
||||
done()
|
||||
}
|
||||
})
|
||||
watcher.on('ready', function() {
|
||||
builder.remove('home/c').catch(done)
|
||||
builder.remove('home/c/removeme').catch(done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -219,25 +220,25 @@ t.describe('watcher', function() {
|
|||
})
|
||||
})
|
||||
|
||||
t.test('should not watch new created directories which are being skipped in the filter', function(done) {
|
||||
t.test('should not watch new created directories which are being skipped', function(done) {
|
||||
var counter = new Counter(done, 1)
|
||||
var home = builder.getPath('home')
|
||||
|
||||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
filter: function(filePath) {
|
||||
skip: function(filePath) {
|
||||
if (/ignored/.test(filePath)) {
|
||||
counter.count()
|
||||
return false
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
builder.remove('home/ignored/file').then(() => {
|
||||
watcher = new Watcher(home, options, function(evt, name) {
|
||||
assert.fail("should not watch new created directories which are being skipped in the filter event detect: " + name)
|
||||
assert.fail("should not watch new created directories which are being skipped event detect: " + name)
|
||||
})
|
||||
|
||||
|
||||
|
@ -297,8 +298,56 @@ t.describe('watcher', function() {
|
|||
})
|
||||
})
|
||||
|
||||
t.test('should trigger changed at the end of all debounce', function(done) {
|
||||
var counter = new Counter()
|
||||
var fpath = builder.getPath('home/a/')
|
||||
|
||||
watcher = new Watcher(fpath, { delay: 100 })
|
||||
|
||||
watcher.on('change', function(evt, name) {
|
||||
counter.count()
|
||||
})
|
||||
|
||||
watcher.on('changed', done.finish(function(evt, name) {
|
||||
assert.strictEqual(counter.counter, 3)
|
||||
}))
|
||||
|
||||
watcher.on('ready', done.wrap(function() {
|
||||
builder.modify('home/a/file1')
|
||||
.then(() => builder.modify('home/a/file2'))
|
||||
.then(() => builder.modify('home/a/file3'))
|
||||
.catch(done)
|
||||
}))
|
||||
})
|
||||
|
||||
t.test('should trigger changed for each change when delay is zero', function(done) {
|
||||
var counter = new Counter(done, 3)
|
||||
var set = new Set([
|
||||
builder.getPath('home/a/file1'),
|
||||
builder.getPath('home/a/file2'),
|
||||
builder.getPath('home/a/file3'),
|
||||
])
|
||||
var fpath = builder.getPath('home/a')
|
||||
|
||||
watcher = new Watcher(fpath, { delay: 0 })
|
||||
|
||||
watcher.on('changed', done.finish(function(evt, name) {
|
||||
if (set.has(name)) {
|
||||
set.delete(name)
|
||||
counter.count()
|
||||
}
|
||||
}))
|
||||
|
||||
watcher.on('ready', function() {
|
||||
builder.modify('home/a/file1')
|
||||
.then(() => builder.modify('home/a/file2'))
|
||||
.then(() => builder.modify('home/a/file3'))
|
||||
.catch(done)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should error when directory gets deleted before calling fs.watch', function(done) {
|
||||
var dir = 'home/c'
|
||||
var dir = 'home/c/removeme'
|
||||
var fpath = builder.getPath(dir)
|
||||
|
||||
builder.createDirectory(dir).then(() => {
|
||||
|
@ -334,16 +383,18 @@ t.describe('watcher', function() {
|
|||
})
|
||||
|
||||
t.test('should identify `remove` event on directory', function(done) {
|
||||
var dir = 'home/a'
|
||||
var home = builder.getPath('home')
|
||||
var dir = 'home/a/removeme'
|
||||
var home = builder.getPath('home/a')
|
||||
var fpath = builder.getPath(dir)
|
||||
|
||||
watcher = new Watcher(home, { delay: 0 }, function(evt, name) {
|
||||
if (evt === 'remove' && name === fpath) done()
|
||||
})
|
||||
watcher.on('ready', function() {
|
||||
builder.remove(dir).catch(done)
|
||||
})
|
||||
builder.createDirectory('home/a/removeme').then(done.wrap(function() {
|
||||
watcher = new Watcher(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) {
|
||||
|
@ -406,6 +457,19 @@ t.describe('watcher', function() {
|
|||
})
|
||||
})
|
||||
|
||||
t.describe('should store original paths in object', function() {
|
||||
var dir = builder.getPath('home')
|
||||
watcher = new Watcher(dir)
|
||||
assert.deepStrictEqual(watcher.originalPaths, [dir])
|
||||
})
|
||||
|
||||
t.describe('should store all original paths in object', function() {
|
||||
var dir1 = builder.getPath('home/a')
|
||||
var dir2 = builder.getPath('home/b')
|
||||
watcher = new Watcher([dir1, dir2])
|
||||
assert.deepStrictEqual(watcher.originalPaths, [dir1, dir2])
|
||||
})
|
||||
|
||||
t.describe('encoding', function() {
|
||||
let options = {
|
||||
delay: 0,
|
||||
|
@ -480,8 +544,8 @@ t.describe('watcher', function() {
|
|||
})
|
||||
})
|
||||
|
||||
t.describe('filter', function() {
|
||||
t.test('should only watch filtered directories', function(done) {
|
||||
t.describe('skip', function() {
|
||||
t.test('should only watch non-skipped directories', function(done) {
|
||||
var matchRegularDir = false
|
||||
var matchIgnoredDir = false
|
||||
var counter = new Counter(done.finish(function() {
|
||||
|
@ -492,8 +556,8 @@ t.describe('watcher', function() {
|
|||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
filter: function(name) {
|
||||
return !/deep_node_modules/.test(name)
|
||||
skip: function(name) {
|
||||
return /deep_node_modules/.test(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -513,7 +577,7 @@ t.describe('watcher', function() {
|
|||
})
|
||||
})
|
||||
|
||||
t.test('should only report filtered files', function(done) {
|
||||
t.test('should only report non-skipped files', function(done) {
|
||||
var dir = builder.getPath('home')
|
||||
var file1 = 'home/bb/file1'
|
||||
var file2 = 'home/bb/file2'
|
||||
|
@ -526,12 +590,11 @@ t.describe('watcher', function() {
|
|||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
filter: function(name) {
|
||||
return /file2/.test(name)
|
||||
skip: function(name) {
|
||||
return /file1/.test(name)
|
||||
}
|
||||
}
|
||||
|
||||
var times = 0
|
||||
var matchIgnoredFile = false
|
||||
watcher = new Watcher(dir, options, function(evt, name) {
|
||||
if (name === builder.getPath(file1)) {
|
||||
|
@ -548,7 +611,7 @@ t.describe('watcher', function() {
|
|||
})
|
||||
})
|
||||
|
||||
t.test('should be able to filter directly with regexp', function(done) {
|
||||
t.test('should be able to skip directly with regexp', function(done) {
|
||||
var dir = builder.getPath('home')
|
||||
var file1 = 'home/bb/file1'
|
||||
var file2 = 'home/bb/file2'
|
||||
|
@ -561,7 +624,7 @@ t.describe('watcher', function() {
|
|||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
filter: /file2/
|
||||
skip: /file1/
|
||||
}
|
||||
|
||||
var times = 0
|
||||
|
@ -587,8 +650,9 @@ t.describe('watcher', function() {
|
|||
delay: 0,
|
||||
recursive: true,
|
||||
manualRecursive: true,
|
||||
filter: function(name, skip) {
|
||||
if (/\/deep_node_modules/.test(name)) return skip
|
||||
skip: function(name) {
|
||||
if (/\/deep_node_modules/.test(name)) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
watcher = new Watcher(home, options)
|
||||
|
@ -603,6 +667,126 @@ t.describe('watcher', function() {
|
|||
}))
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('filter', function() {
|
||||
t.test('should not have impact on watched directories', function(done) {
|
||||
var matchNonFilterDir = false
|
||||
var matchFilteredDir = false
|
||||
var counter = new Counter(done.finish(function() {
|
||||
assert(!matchNonFilterDir, 'watch should not detect non-filter file')
|
||||
assert(matchFilteredDir, 'watch failed to detect filter path `deep_node_modules`')
|
||||
}), 1, true)
|
||||
|
||||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
filter: function(name) {
|
||||
return /deep_node_modules/.test(name)
|
||||
}
|
||||
}
|
||||
|
||||
watcher = new Watcher(builder.getPath('home'), options, function(evt, name) {
|
||||
if (/deep_node_modules/.test(name)) {
|
||||
matchFilteredDir = true
|
||||
} else {
|
||||
matchNonFilterDir = true
|
||||
}
|
||||
counter.count()
|
||||
})
|
||||
watcher.on('ready', function() {
|
||||
counter.startCounting()
|
||||
builder.modify('home/b/file1')
|
||||
.then(() => builder.modify('home/deep_node_modules/ma/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 matchFilterFile = false
|
||||
|
||||
var counter = new Counter(done.finish(function() {
|
||||
assert.strictEqual(matchFilterFile, true, 'home/bb/file1 should be visible')
|
||||
}), 1, true)
|
||||
|
||||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
filter: function(name) {
|
||||
return /file1/.test(name)
|
||||
}
|
||||
}
|
||||
|
||||
watcher = new Watcher(dir, options, function(evt, name) {
|
||||
if (name === builder.getPath(file1)) {
|
||||
matchFilterFile = 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(matchFilterFile, true, 'home/bb/file1 should be visible')
|
||||
}), 1, true)
|
||||
|
||||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
filter: /file1/
|
||||
}
|
||||
|
||||
var matchFilterFile = false
|
||||
watcher = new Watcher(dir, options, function(evt, name) {
|
||||
if (name === builder.getPath(file1)) {
|
||||
matchFilterFile = true
|
||||
}
|
||||
counter.count()
|
||||
})
|
||||
watcher.on('ready', function() {
|
||||
counter.startCounting()
|
||||
|
||||
builder.modify(file2)
|
||||
.then(() => builder.modify(file1))
|
||||
.catch(done)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should not filter subdirectories with `filter` flag', function(done) {
|
||||
var home = builder.getPath('home')
|
||||
var options = {
|
||||
delay: 0,
|
||||
recursive: true,
|
||||
manualRecursive: true,
|
||||
filter: function(name) {
|
||||
if (/\/deep_node_modules/.test(name)) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
watcher = new Watcher(home, options)
|
||||
|
||||
watcher.on('ready', done.finish(function() {
|
||||
let homeFiltered = builder.getAllDirectories().sort()
|
||||
let watchersPaths = watcher.listeners.map(x => x.path).sort()
|
||||
|
||||
assert.deepStrictEqual(watchersPaths, homeFiltered)
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('parameters', function() {
|
||||
|
|
|
@ -135,8 +135,10 @@ Builder.prototype.modify = function(fpath, delay) {
|
|||
Builder.prototype.remove = function(fpath) {
|
||||
let filePath = this.getPath(fpath)
|
||||
return fs.rm(filePath, { recursive: true, force: true })
|
||||
.catch(() => this.delay(100))
|
||||
.then(() => fs.rm(filePath, { recursive: true, force: true }))
|
||||
.catch(() =>
|
||||
this.delay(100)
|
||||
.then(() => fs.rm(filePath, { recursive: true, force: true }))
|
||||
)
|
||||
}
|
||||
|
||||
Builder.prototype.removeSync = function(fpath) {
|
||||
|
|
Loading…
Reference in a new issue