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 e from './lib/eltro.mjs'
|
||||||
import { CLI, printError } from './lib/cli.mjs'
|
import { CLI, printError } from './lib/cli.mjs'
|
||||||
|
|
||||||
e.begin()
|
|
||||||
|
|
||||||
const cli = new CLI(e)
|
|
||||||
cli.parseOptions(args)
|
|
||||||
|
|
||||||
if (cli.errored) {
|
|
||||||
PrintHelp()
|
|
||||||
}
|
|
||||||
|
|
||||||
function PrintHelp() {
|
function PrintHelp() {
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('Usage: eltro <options> <files>')
|
console.log('Usage: eltro <options> <files>')
|
||||||
|
@ -25,6 +16,8 @@ function PrintHelp() {
|
||||||
console.log(' Supported reporters: list, dot')
|
console.log(' Supported reporters: list, dot')
|
||||||
console.log(' -t, --timeout - Specify the timeout for tests in ms.')
|
console.log(' -t, --timeout - Specify the timeout for tests in ms.')
|
||||||
console.log(' Default value is 2000ms')
|
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(' --ignore-only - Specify to ignore any .only() tests found')
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('eltro test/mytest.mjs')
|
console.log('eltro test/mytest.mjs')
|
||||||
|
@ -34,43 +27,47 @@ function PrintHelp() {
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.processTargets().then(function() {
|
function showErrorAndExit(message = '', err = null, code = 1) {
|
||||||
if (!cli.files.length) {
|
console.log('')
|
||||||
console.log('')
|
|
||||||
console.log('No files were found with pattern', cli.targets.join(','))
|
if (message) {
|
||||||
|
console.error(`\x1b[31m${message}\x1b[0m`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
printError(err)
|
||||||
|
} else {
|
||||||
PrintHelp()
|
PrintHelp()
|
||||||
}
|
}
|
||||||
return cli.loadFiles()
|
process.exit(code)
|
||||||
.then(function() {
|
}
|
||||||
e.reporter = cli.reporter
|
|
||||||
e.ignoreOnly = cli.ignoreOnly
|
|
||||||
e.__timeout = cli.timeout
|
|
||||||
|
|
||||||
return e.run()
|
const cli = new CLI(e)
|
||||||
.catch(function(err) {
|
cli.parseOptions(args)
|
||||||
console.log('')
|
.catch(function(err) { showErrorAndExit(err.message) })
|
||||||
console.error('\x1b[31mUnknown error occured while running the tests\x1b[0m')
|
.then(function() {
|
||||||
printError(err)
|
return cli.startWatcher()
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
.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) {
|
.then(function(stats) {
|
||||||
if (stats.failed > 0) {
|
if (stats.failed > 0) {
|
||||||
process.exit(10)
|
process.exit(10)
|
||||||
}
|
}
|
||||||
process.exit(0)
|
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) {
|
export function runWithCallbackSafe(test) {
|
||||||
|
let finished = false
|
||||||
return new Promise(function(res, rej) {
|
return new Promise(function(res, rej) {
|
||||||
try {
|
try {
|
||||||
let cb = function(err) {
|
let cb = function(err) {
|
||||||
|
@ -7,27 +8,30 @@ export function runWithCallbackSafe(test) {
|
||||||
}
|
}
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
let safeWrap = function(finish) {
|
let safeWrap = function(fn, ...args) {
|
||||||
// return a safe wrap support
|
try {
|
||||||
return function(fun) {
|
return fn(...args)
|
||||||
return function(a, b, c) {
|
}
|
||||||
try {
|
catch (err) {
|
||||||
fun(a, b, c)
|
return rej(err)
|
||||||
if (finish) {
|
|
||||||
res()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
return rej(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb.wrap = safeWrap(false)
|
let safeWrapInFunction = function(finish, fn) {
|
||||||
cb.finish = safeWrap(true)
|
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)
|
test.func(cb)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
rej(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 path from 'path'
|
||||||
import fs from 'fs'
|
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.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.reporter = 'list'
|
||||||
this.ignoreOnly = false
|
this.ignoreOnly = false
|
||||||
this.timeout = 2000
|
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.targets = ['test/**']
|
||||||
this.files = []
|
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) {
|
CLI.prototype.parseOptions = function(args) {
|
||||||
if (!args || !args.length) {
|
if (!args || !args.length) {
|
||||||
this.targets.push('test/**')
|
this.targets.push('test/**')
|
||||||
this.errored = false
|
return Promise.resolve()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errored = false
|
|
||||||
this.targets.splice(0, this.targets.length)
|
this.targets.splice(0, this.targets.length)
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
if (args[i] === '-r' || args[i] === '--reporter') {
|
if (args[i] === '-r' || args[i] === '--reporter') {
|
||||||
if (!args[i + 1] || (args[i + 1] !== 'list' && args[i + 1] !== 'dot')) {
|
if (!args[i + 1] || (args[i + 1] !== 'list' && args[i + 1] !== 'dot')) {
|
||||||
this.errored = true
|
return Promise.reject(new Error('Reporter was missing or invalid. Only "list" and "dot" are supported.'))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
this.reporter = args[i + 1]
|
this.reporter = args[i + 1]
|
||||||
i++
|
i++
|
||||||
} else if (args[i] === '-t' || args[i] === '--timeout') {
|
} else if (args[i] === '-t' || args[i] === '--timeout') {
|
||||||
if (!args[i + 1] || isNaN(Number(args[i + 1]))) {
|
if (!args[i + 1] || isNaN(Number(args[i + 1]))) {
|
||||||
this.errored = true
|
return Promise.reject(new Error('Timeout was missing or invalid'))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
this.timeout = Number(args[i + 1])
|
this.timeout = Number(args[i + 1])
|
||||||
i++
|
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') {
|
} else if (args[i] === '--ignore-only') {
|
||||||
this.ignoreOnly = true
|
this.ignoreOnly = true
|
||||||
} else if (args[i][0] === '-') {
|
} else if (args[i][0] === '-') {
|
||||||
this.errored = true
|
return Promise.reject(new Error(`Unknown option ${args[i]}`))
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
this.targets.push(args[i])
|
this.targets.push(args[i])
|
||||||
}
|
}
|
||||||
|
@ -50,9 +113,72 @@ CLI.prototype.parseOptions = function(args) {
|
||||||
if (!this.targets.length) {
|
if (!this.targets.length) {
|
||||||
this.targets.push('test/**')
|
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)
|
this.files.splice(0, this.files.length)
|
||||||
|
|
||||||
if (!this.targets.length) {
|
if (!this.targets.length) {
|
||||||
|
@ -60,22 +186,28 @@ CLI.prototype.processTargets = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(this.targets.map((target) => {
|
return Promise.all(this.targets.map((target) => {
|
||||||
return getFiles(this.files, target)
|
return getFilesFromTarget(this.files, target)
|
||||||
})).then(() => {
|
})).then(() => {
|
||||||
if (!this.files.length) {
|
if (!this.files.length) {
|
||||||
this.errored = 'empty'
|
this.errored = 'empty'
|
||||||
}
|
}
|
||||||
|
return this.files
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
CLI.prototype.loadFiles = async function() {
|
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++) {
|
for (let i = 0; i < this.files.length; i++) {
|
||||||
if (this.files[i].endsWith('.mjs') || this.files[i].endsWith('.js')) {
|
if (this.files[i].endsWith('.mjs') || this.files[i].endsWith('.js')) {
|
||||||
try {
|
try {
|
||||||
this.e.setFilename(this.files[i])
|
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()
|
this.e.resetFilename()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let newError = new Error(`Error while loading ${this.files[i]}`)
|
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) {
|
function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, includeFiles) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
return fs.readdir(curr, function(err, data) {
|
return fs.readdir(curr, function(err, data) {
|
||||||
|
@ -105,7 +362,7 @@ function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, inc
|
||||||
if (stat.isDirectory() && grabAll) {
|
if (stat.isDirectory() && grabAll) {
|
||||||
return res(traverseFolder(files, path.join(curr, file), match, path.join(insidePath, file), grabAll, insideStar, includeFiles))
|
return res(traverseFolder(files, path.join(curr, file), match, path.join(insidePath, file), grabAll, insideStar, includeFiles))
|
||||||
} else if (stat.isDirectory() && match) {
|
} 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)
|
res(null)
|
||||||
})
|
})
|
||||||
|
@ -119,7 +376,7 @@ export function fileMatches(filename, match) {
|
||||||
return Boolean(filename.match(new RegExp(match.replace(/\./, '\\.').replace(/\*/, '.*'))))
|
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 isGrabbingAll = grabAll || false
|
||||||
let isStarred = insideStar || false
|
let isStarred = insideStar || false
|
||||||
let cwd = process.cwd()
|
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)
|
return traverseFolder(files, curr, splitted.slice(start + 1).join('/'), currPath, isGrabbingAll, isStarred, false)
|
||||||
.then(res, rej)
|
.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)
|
.then(res, rej)
|
||||||
} else if (first.indexOf('*') >= 0) {
|
} else if (first.indexOf('*') >= 0) {
|
||||||
if (first === '**') {
|
if (first === '**') {
|
||||||
|
|
|
@ -126,7 +126,7 @@ function Eltro() {
|
||||||
this.activeGroup = null
|
this.activeGroup = null
|
||||||
this.failedTests = []
|
this.failedTests = []
|
||||||
this.hasTests = false
|
this.hasTests = false
|
||||||
this.starting = false
|
this.starting = null
|
||||||
this.ignoreOnly = false
|
this.ignoreOnly = false
|
||||||
this.logger = null
|
this.logger = null
|
||||||
this.filename = ''
|
this.filename = ''
|
||||||
|
@ -147,10 +147,12 @@ function Eltro() {
|
||||||
Eltro.prototype.begin = function() {
|
Eltro.prototype.begin = function() {
|
||||||
if (this.starting) {
|
if (this.starting) {
|
||||||
console.warn('WARNING: Multiple calls to Eltro.begin were done.')
|
console.warn('WARNING: Multiple calls to Eltro.begin were done.')
|
||||||
|
console.warn(this.starting)
|
||||||
|
console.warn(new Error('Second call'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.hasTests = false
|
this.hasTests = false
|
||||||
this.starting = true
|
this.starting = new Error('First call')
|
||||||
this.filename = ''
|
this.filename = ''
|
||||||
this.prefix = ''
|
this.prefix = ''
|
||||||
this.fileGroupMap.clear()
|
this.fileGroupMap.clear()
|
||||||
|
@ -165,6 +167,12 @@ 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) => {
|
||||||
|
if (test.error) {
|
||||||
|
return reject(test.error)
|
||||||
|
}
|
||||||
|
if (!test.func) {
|
||||||
|
return reject(new Error(`Test ${test.name} was missing function`))
|
||||||
|
}
|
||||||
this.captureOutsideExceptions = reject
|
this.captureOutsideExceptions = reject
|
||||||
// Flag to check if we finished
|
// Flag to check if we finished
|
||||||
let finished = false
|
let finished = false
|
||||||
|
@ -518,9 +526,18 @@ Eltro.prototype.test = function(name, func) {
|
||||||
throw new Error('Tests outside groups are not allowed.')
|
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)
|
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) {
|
if (this.temporary.only && !this.temporary.skip) {
|
||||||
test.only()
|
test.only()
|
||||||
this.temporary.only = false
|
this.temporary.only = false
|
||||||
|
|
|
@ -52,7 +52,7 @@ TempStack.prototype.cleanup = function(fn) {
|
||||||
|
|
||||||
let pending = false
|
let pending = false
|
||||||
|
|
||||||
export default function hasNativeRecursive(fn) {
|
export default function hasNativeRecursive(fn, opts = {}) {
|
||||||
if (!is.func(fn)) {
|
if (!is.func(fn)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,10 @@ export default function hasNativeRecursive(fn) {
|
||||||
return fn(IS_SUPPORT)
|
return fn(IS_SUPPORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.quickCheck) {
|
||||||
|
return fn(IS_SUPPORT = (process.platform === 'darwin' || process.platform === 'win32'))
|
||||||
|
}
|
||||||
|
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
pending = true
|
pending = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import events from 'events'
|
||||||
import hasNativeRecursive from './has-native-recursive.mjs'
|
import hasNativeRecursive from './has-native-recursive.mjs'
|
||||||
import * as is from './is.mjs'
|
import * as is from './is.mjs'
|
||||||
|
|
||||||
const EVENT_UPDATE = 'update';
|
export const EVENT_UPDATE = 'update';
|
||||||
const EVENT_REMOVE = 'remove';
|
export const EVENT_REMOVE = 'remove';
|
||||||
const TYPE_FILE = 'file'
|
const TYPE_FILE = 'file'
|
||||||
const TYPE_DIRECTORY = 'directory'
|
const TYPE_DIRECTORY = 'directory'
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ export default class Watcher extends events.EventEmitter {
|
||||||
paths = unique(paths)
|
paths = unique(paths)
|
||||||
this.options = options || {}
|
this.options = options || {}
|
||||||
this.fn = fn || null
|
this.fn = fn || null
|
||||||
|
this.originalPaths = paths
|
||||||
|
|
||||||
if (is.func(this.options)) {
|
if (is.func(this.options)) {
|
||||||
this.fn = this.options
|
this.fn = this.options
|
||||||
|
@ -92,7 +93,7 @@ export default class Watcher extends events.EventEmitter {
|
||||||
this.supportsNativeRecursive = nativeRecursive
|
this.supportsNativeRecursive = nativeRecursive
|
||||||
this.options.manualRecursive = !nativeRecursive
|
this.options.manualRecursive = !nativeRecursive
|
||||||
this._startListeners(paths)
|
this._startListeners(paths)
|
||||||
})
|
}, { quickCheck: this.options.quickNativeCheck || true })
|
||||||
} else {
|
} else {
|
||||||
this._startListeners(paths)
|
this._startListeners(paths)
|
||||||
}
|
}
|
||||||
|
@ -115,10 +116,18 @@ export default class Watcher extends events.EventEmitter {
|
||||||
return null
|
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
|
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))
|
|| (is.regExp(this.options.filter) && this.options.filter.test(name))
|
||||||
: true
|
: true
|
||||||
}
|
}
|
||||||
|
@ -126,10 +135,9 @@ export default class Watcher extends events.EventEmitter {
|
||||||
closeWatch(orgItem) {
|
closeWatch(orgItem) {
|
||||||
let item = orgItem
|
let item = orgItem
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
item = getWatcherOrNull(item)
|
item = this.getWatcherOrNull(item)
|
||||||
}
|
}
|
||||||
if (!item) {
|
if (!item) {
|
||||||
this.emit('error', new Error(`attempted to close watcher for ${item} but such a watcher could not be found`))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +153,8 @@ export default class Watcher extends events.EventEmitter {
|
||||||
|
|
||||||
_emitEvent(item, evt, name) {
|
_emitEvent(item, evt, name) {
|
||||||
if (item.type === TYPE_FILE && !is.samePath(name, item.filename)) return
|
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) {
|
if (item.flag) {
|
||||||
item.flag = ''
|
item.flag = ''
|
||||||
|
@ -164,6 +173,7 @@ export default class Watcher extends events.EventEmitter {
|
||||||
|
|
||||||
if (!this.options.delay) {
|
if (!this.options.delay) {
|
||||||
this.emit('change', evt, outputName)
|
this.emit('change', evt, outputName)
|
||||||
|
this.emit('changed', evt, outputName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +193,7 @@ export default class Watcher extends events.EventEmitter {
|
||||||
this.emit('error', err)
|
this.emit('error', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.emit('changed')
|
||||||
}, this.options.delay)
|
}, this.options.delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +208,7 @@ export default class Watcher extends events.EventEmitter {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
_watcherSink(item, rawEvt, rawName) {
|
_watcherSink(item, rawEvt, rawName, c) {
|
||||||
if (this.closed) return
|
if (this.closed) return
|
||||||
|
|
||||||
let name = path.join(item.path, rawName || '')
|
let name = path.join(item.path, rawName || '')
|
||||||
|
@ -209,9 +220,8 @@ export default class Watcher extends events.EventEmitter {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if (is.directory(name)
|
if (is.directory(name)
|
||||||
&& this.getWatcherOrNull(name) === null
|
&& this.getWatcherOrNull(name) === null) {
|
||||||
&& this.shouldInclude(name) === false) {
|
this.safeAdd(name, TYPE_DIRECTORY)
|
||||||
this.safeAdd(subItem, TYPE_DIRECTORY)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,6 +266,10 @@ export default class Watcher extends events.EventEmitter {
|
||||||
type = is.file(name) ? TYPE_FILE : TYPE_DIRECTORY
|
type = is.file(name) ? TYPE_FILE : TYPE_DIRECTORY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.shouldSkip(name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let item = this._pathToItem(name, type)
|
let item = this._pathToItem(name, type)
|
||||||
let options = {
|
let options = {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
|
|
23
package.json
23
package.json
|
@ -4,17 +4,28 @@
|
||||||
"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": {
|
||||||
|
"echo": "echo helloworld",
|
||||||
|
"echo:watch": "node cli.mjs --watch test --npm echo",
|
||||||
"test": "node cli.mjs \"test/**/*.test.mjs\"",
|
"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": {
|
"watch": {
|
||||||
"test": {
|
"test": {
|
||||||
"patterns": [
|
"patterns": [ "lib", "test", "cli.mjs", "index.mjs" ],
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"extensions": "js,mjs",
|
"extensions": "js,mjs",
|
||||||
"quiet": true,
|
"delay": 50
|
||||||
"inherit": true
|
},
|
||||||
|
"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": {
|
"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)
|
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() {
|
e.test('Eltro should support timing out tests', async function() {
|
||||||
testsWereRun = true
|
testsWereRun = true
|
||||||
const t = CreateT()
|
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
|
// Extra testing to make sure tests were run at all
|
||||||
process.on('exit', function(e) {
|
process.on('exit', function(e) {
|
||||||
try {
|
try {
|
||||||
assert.strictEqual(testsWereRun, true)
|
assert.strictEqual(testsWereRun, true, 'Not all tests were run, remove all .only() and try again.')
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log('Checking if tests were run at all failed:')
|
|
||||||
printError(err)
|
printError(err)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import assert from '../lib/assert.mjs'
|
||||||
import t from '../lib/eltro.mjs'
|
import t from '../lib/eltro.mjs'
|
||||||
import { Builder, Counter } from './watch/builder.mjs'
|
import { Builder, Counter } from './watch/builder.mjs'
|
||||||
import Watcher from '../lib/watch/index.mjs'
|
import Watcher from '../lib/watch/index.mjs'
|
||||||
|
import * as is from '../lib/watch/is.mjs'
|
||||||
|
|
||||||
const builder = new Builder()
|
const builder = new Builder()
|
||||||
let watcher
|
let watcher
|
||||||
|
@ -170,11 +171,11 @@ t.describe('watcher', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should error when parent gets deleted before calling fs.watch', function(done) {
|
t.test('should error when parent gets deleted before calling fs.watch', function(done) {
|
||||||
var fpath = builder.getPath('home/a/file1')
|
var fpath = builder.getPath('home/a/removeme/file1')
|
||||||
builder.newFile('home/a/file1')
|
builder.newFile('home/a/removeme/file1')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
watcher = new Watcher(fpath, null, null, { fs: { watch: function(path, options) {
|
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)
|
return fs.watch(path, options)
|
||||||
} } })
|
} } })
|
||||||
|
|
||||||
|
@ -187,17 +188,17 @@ t.describe('watcher', function() {
|
||||||
|
|
||||||
t.describe('watch for directories', function() {
|
t.describe('watch for directories', function() {
|
||||||
t.test('should watch directories inside a directory', function(done) {
|
t.test('should watch directories inside a directory', function(done) {
|
||||||
var home = builder.getPath('home')
|
var home = builder.getPath('home/c')
|
||||||
var dir = 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) {
|
watcher = new Watcher(home, { delay: 0, recursive: true }, function(evt, name) {
|
||||||
if (name === dir && evt === 'remove') {
|
if (name === dir && evt === 'remove') {
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
watcher.on('ready', function() {
|
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 counter = new Counter(done, 1)
|
||||||
var home = builder.getPath('home')
|
var home = builder.getPath('home')
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
filter: function(filePath) {
|
skip: function(filePath) {
|
||||||
if (/ignored/.test(filePath)) {
|
if (/ignored/.test(filePath)) {
|
||||||
counter.count()
|
counter.count()
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.remove('home/ignored/file').then(() => {
|
builder.remove('home/ignored/file').then(() => {
|
||||||
watcher = new Watcher(home, options, function(evt, name) {
|
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) {
|
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)
|
var fpath = builder.getPath(dir)
|
||||||
|
|
||||||
builder.createDirectory(dir).then(() => {
|
builder.createDirectory(dir).then(() => {
|
||||||
|
@ -334,16 +383,18 @@ t.describe('watcher', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should identify `remove` event on directory', function(done) {
|
t.test('should identify `remove` event on directory', function(done) {
|
||||||
var dir = 'home/a'
|
var dir = 'home/a/removeme'
|
||||||
var home = builder.getPath('home')
|
var home = builder.getPath('home/a')
|
||||||
var fpath = builder.getPath(dir)
|
var fpath = builder.getPath(dir)
|
||||||
|
|
||||||
watcher = new Watcher(home, { delay: 0 }, function(evt, name) {
|
builder.createDirectory('home/a/removeme').then(done.wrap(function() {
|
||||||
if (evt === 'remove' && name === fpath) done()
|
watcher = new Watcher(home, { delay: 0 }, function(evt, name) {
|
||||||
})
|
if (evt === 'remove' && name === fpath) done()
|
||||||
watcher.on('ready', function() {
|
})
|
||||||
builder.remove(dir).catch(done)
|
watcher.on('ready', function() {
|
||||||
})
|
builder.remove(dir).catch(done)
|
||||||
|
})
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should be able to handle many events on deleting', function(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() {
|
t.describe('encoding', function() {
|
||||||
let options = {
|
let options = {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
|
@ -480,8 +544,8 @@ t.describe('watcher', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('filter', function() {
|
t.describe('skip', function() {
|
||||||
t.test('should only watch filtered directories', function(done) {
|
t.test('should only watch non-skipped directories', function(done) {
|
||||||
var matchRegularDir = false
|
var matchRegularDir = false
|
||||||
var matchIgnoredDir = false
|
var matchIgnoredDir = false
|
||||||
var counter = new Counter(done.finish(function() {
|
var counter = new Counter(done.finish(function() {
|
||||||
|
@ -492,8 +556,8 @@ t.describe('watcher', function() {
|
||||||
var options = {
|
var options = {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
filter: function(name) {
|
skip: function(name) {
|
||||||
return !/deep_node_modules/.test(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 dir = builder.getPath('home')
|
||||||
var file1 = 'home/bb/file1'
|
var file1 = 'home/bb/file1'
|
||||||
var file2 = 'home/bb/file2'
|
var file2 = 'home/bb/file2'
|
||||||
|
@ -526,12 +590,11 @@ t.describe('watcher', function() {
|
||||||
var options = {
|
var options = {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
filter: function(name) {
|
skip: function(name) {
|
||||||
return /file2/.test(name)
|
return /file1/.test(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var times = 0
|
|
||||||
var matchIgnoredFile = false
|
var matchIgnoredFile = false
|
||||||
watcher = new Watcher(dir, options, function(evt, name) {
|
watcher = new Watcher(dir, options, function(evt, name) {
|
||||||
if (name === builder.getPath(file1)) {
|
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 dir = builder.getPath('home')
|
||||||
var file1 = 'home/bb/file1'
|
var file1 = 'home/bb/file1'
|
||||||
var file2 = 'home/bb/file2'
|
var file2 = 'home/bb/file2'
|
||||||
|
@ -561,7 +624,7 @@ t.describe('watcher', function() {
|
||||||
var options = {
|
var options = {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
filter: /file2/
|
skip: /file1/
|
||||||
}
|
}
|
||||||
|
|
||||||
var times = 0
|
var times = 0
|
||||||
|
@ -587,8 +650,9 @@ t.describe('watcher', function() {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
manualRecursive: true,
|
manualRecursive: true,
|
||||||
filter: function(name, skip) {
|
skip: function(name) {
|
||||||
if (/\/deep_node_modules/.test(name)) return skip
|
if (/\/deep_node_modules/.test(name)) return true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watcher = new Watcher(home, options)
|
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() {
|
t.describe('parameters', function() {
|
||||||
|
|
|
@ -135,8 +135,10 @@ Builder.prototype.modify = function(fpath, delay) {
|
||||||
Builder.prototype.remove = function(fpath) {
|
Builder.prototype.remove = function(fpath) {
|
||||||
let filePath = this.getPath(fpath)
|
let filePath = this.getPath(fpath)
|
||||||
return fs.rm(filePath, { recursive: true, force: true })
|
return fs.rm(filePath, { recursive: true, force: true })
|
||||||
.catch(() => this.delay(100))
|
.catch(() =>
|
||||||
.then(() => fs.rm(filePath, { recursive: true, force: true }))
|
this.delay(100)
|
||||||
|
.then(() => fs.rm(filePath, { recursive: true, force: true }))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Builder.prototype.removeSync = function(fpath) {
|
Builder.prototype.removeSync = function(fpath) {
|
||||||
|
|
Loading…
Reference in a new issue