import fsPromise from 'fs/promises' import path from 'path' import t from '../lib/eltro.mjs' import assert from '../lib/assert.mjs' import Watcher, { EVENT_REMOVE, EVENT_UPDATE } from '../lib/watch/index.mjs' import { stub } from '../lib/sinon.mjs' import { CLI, MESSAGE_FILES_PAYLOAD, MESSAGE_FILES_REQUEST, MESSAGE_RUN_FINISHED, createMessage, fileMatches } from '../lib/cli.mjs' const masterSlave = [ 'master', 'slave' ] function createSafeCli(e, overrides = { }) { overrides.cluster = overrides.cluster || { isWorker: false } return new CLI(e, overrides) } t.describe('CLI', function() { let cli = createSafeCli() t.describe('#constructor()', function() { t.test('give default options', function() { const eltro = { a: 1 } const logger = { b: 2 } const cluster = { c: 3 } const process = { d: 4 } const importer = { e: 5 } let cliTest = createSafeCli(eltro, { logger, cluster, process, importer }) assert.strictEqual(cliTest.reporter, 'list') assert.strictEqual(cliTest.ignoreOnly, false) assert.strictEqual(cliTest.timeout, 2000) assert.strictEqual(cliTest.watch, null) assert.strictEqual(cliTest.run, 'test') assert.strictEqual(cliTest.isSlave, false) assert.deepEqual(cliTest.targets, ['test/**']) assert.deepEqual(cliTest.files, []) assert.notOk(cliTest.errored) assert.strictEqual(cliTest.e, eltro) assert.strictEqual(cliTest.logger, logger) assert.strictEqual(cliTest.cluster, cluster) assert.strictEqual(cliTest.process, process) assert.strictEqual(cliTest.importer, importer) }) t.test('should detect isSlave from cluster', function() { const cluster = { isWorker: true } let cliTest = createSafeCli(null, { cluster }) assert.strictEqual(cliTest.isSlave, true) }) }) /***************************************** * #parseOptions() *****************************************/ t.describe('#parseOptions()', function() { t.beforeEach(function() { cli.loadDefaults() }) t.test('should not do anything if no options', async function() { await cli.parseOptions([]) assert.strictEqual(cli.reporter, 'list') }) t.test('should support overriding reporter with shorthand option', async function() { await cli.parseOptions(['-r', 'dot']) assert.strictEqual(cli.reporter, 'dot') }) t.test('should support overriding reporter with long option', async function() { await cli.parseOptions(['--reporter', 'dot']) assert.strictEqual(cli.reporter, 'dot') }) t.test('should support enabling ignore-only long option', async function() { await cli.parseOptions(['--ignore-only', '-r', 'dot']) assert.strictEqual(cli.ignoreOnly, true) }) t.test('should support reporter list', async function() { await cli.parseOptions(['-r', 'list']) assert.strictEqual(cli.reporter, 'list') }) t.test('should mark errored if missing reporter', async function() { let err = await assert.isRejected(cli.parseOptions(['--reporter'])) assert.match(err.message, /reporter/i) }) t.test('should mark errored if invalid reporter', async function() { let err = await assert.isRejected(cli.parseOptions(['--reporter', 'test'])) assert.match(err.message, /reporter/i) }) t.test('should support overriding timeout with shorthand option', async function() { await cli.parseOptions(['-t', '1000']) assert.strictEqual(cli.timeout, 1000) }) t.test('should support overriding timeout with long option', async function() { await cli.parseOptions(['--timeout', '250']) assert.strictEqual(cli.timeout, 250) }) t.test('should mark errored if missing timeout', async function() { let err = await assert.isRejected(cli.parseOptions(['--timeout'])) assert.match(err.message, /timeout/i) }) t.test('should mark errored if invalid timeout', async function() { let err = await assert.isRejected(cli.parseOptions(['--timeout', 'test'])) assert.match(err.message, /timeout/i) }) t.test('should support overriding watch', async function() { await cli.parseOptions(['-w', 'unittest_test1']) assert.strictEqual(cli.watch, 'unittest_test1') }) t.test('should support overriding watch with long option', async function() { await cli.parseOptions(['--watch', 'unittest_test1']) assert.strictEqual(cli.watch, 'unittest_test1') }) t.test('should fail setting watch if value is missing', async function() { let err = await assert.isRejected(cli.parseOptions(['--watch'])) assert.strictEqual(cli.watch, null) assert.match(err.message, /watch/i) }) t.test('should fail setting watch if value is parameter', async function() { let err = await assert.isRejected(cli.parseOptions(['-w', '--reporter', 'list'])) assert.strictEqual(cli.watch, null) assert.match(err.message, /watch/i) }) t.test('should support overriding run', async function() { await cli.parseOptions(['-n', 'unittest_run1']) assert.strictEqual(cli.run, 'unittest_run1') }) t.test('should support overriding run with long option', async function() { await cli.parseOptions(['--npm', 'unittest_run1']) assert.strictEqual(cli.run, 'unittest_run1') }) t.test('should fail setting npm if value is missing', async function() { let err = await assert.isRejected(cli.parseOptions(['--npm'])) assert.strictEqual(cli.run, 'test') assert.match(err.message, /npm/i) }) t.test('should fail setting npm if value is parameter', async function() { let err = await assert.isRejected(cli.parseOptions(['-n', '--reporter', 'list'])) assert.strictEqual(cli.run, 'test') assert.match(err.message, /npm/i) }) t.test('when using run and no target, leave target empty', async function() { await cli.parseOptions(['--npm', 'unittest_run1']) assert.strictEqual(cli.targets.length, 0) }) t.test('should add file to targets', async function() { await cli.parseOptions(['test']) assert.deepEqual(cli.targets, ['test']) }) t.test('should add file to targets no matter where it is', async function() { await cli.parseOptions(['test', '-r', 'list', 'test2']) assert.deepEqual(cli.targets, ['test', 'test2']) }) t.test('should default add test to target if no target', async function() { await cli.parseOptions(['-r', 'list']) assert.deepEqual(cli.targets, ['test/**']) }) t.test('should mark errored if invalid shorthand option', async function() { let err = await assert.isRejected(cli.parseOptions(['-A'])) assert.match(err.message, /unknown/i) }) t.test('should mark errored if invalid longhand option', async function() { let err = await assert.isRejected(cli.parseOptions(['--asdf'])) assert.match(err.message, /unknown/i) }) }) /***************************************** * #startWatcher() *****************************************/ t.describe('#startWatcher()', function() { let cli let logger t.afterEach(function() { if (cli.watcher) { return cli.watcher.close() } }) t.beforeEach(function() { logger = { log: stub(), error: stub(), } cli = createSafeCli(null, { logger }) cli.watch = null }) t.test('should do nothing if watch is empty or null', async function() { assert.strictEqual(cli.watcher, null) await cli.startWatcher() assert.strictEqual(cli.watcher, null) cli.watch = '' await cli.startWatcher() assert.strictEqual(cli.watcher, null) }) t.test('should do nothing if isSlave', async function () { cli.isSlave = true cli.watch = 'test_quick' await cli.startWatcher() assert.strictEqual(cli.watcher, null) }) t.test('should otherwise call watcher', async function () { cli.isSlave = false cli.watch = 'test_quick' await cli.startWatcher() assert.ok(cli.watcher) assert.strictEqual(cli.watcher instanceof Watcher, true) }) t.test('should fetch folders to watch from package.json', async function() { cli.watch = 'test_quick' await cli.startWatcher() let packageJson = JSON.parse(await fsPromise.readFile('package.json')) assert.ok(cli.watcher) assert.deepStrictEqual( cli.watcher.originalPaths.sort(), packageJson.watch.test_quick.patterns.sort(), ) }) t.test('should properly skip node_modules by default', async function() { cli.watch = 'test_quick' await cli.startWatcher() assert.ok(cli.watcher) assert.strictEqual(typeof cli.watcher.options.skip, 'function') assert.notOk(cli.watcher.options.skip('filename.js')) assert.notOk(cli.watcher.options.skip('folder/filename.js')) assert.notOk(cli.watcher.options.skip('filename.mjs')) assert.notOk(cli.watcher.options.skip('folder/filename.mjs')) assert.notOk(cli.watcher.options.skip('filename')) assert.notOk(cli.watcher.options.skip('folder/filename')) assert.notOk(cli.watcher.options.skip('filename')) assert.notOk(cli.watcher.options.skip('folder/filename')) assert.ok(cli.watcher.options.skip('node_modules')) }) t.test('should properly filter only javascript files by default', async function() { cli.watch = 'test_quick' await cli.startWatcher() assert.ok(cli.watcher) assert.strictEqual(typeof cli.watcher.options.filter.test, 'function') assert.ok(cli.watcher.options.filter.test('filename.js')) assert.ok(cli.watcher.options.filter.test('folder/filename.js')) assert.ok(cli.watcher.options.filter.test('filename.mjs')) assert.ok(cli.watcher.options.filter.test('folder/filename.mjs')) assert.notOk(cli.watcher.options.filter.test('filename')) assert.notOk(cli.watcher.options.filter.test('folder/filename')) assert.notOk(cli.watcher.options.filter.test('filename.jsx')) assert.notOk(cli.watcher.options.filter.test('folder/filename.jsx')) assert.notOk(cli.watcher.options.filter.test('filename.xjs')) assert.notOk(cli.watcher.options.filter.test('folder/filename.xjs')) assert.notOk(cli.watcher.options.filter.test('filename.doc')) assert.notOk(cli.watcher.options.filter.test('folder/filename.doc')) }) t.test('should support custom extension list', async function() { cli.watch = 'test_quick_js' await cli.startWatcher() assert.ok(cli.watcher) assert.strictEqual(typeof cli.watcher.options.filter.test, 'function') assert.ok(cli.watcher.options.filter.test('filename.js')) assert.ok(cli.watcher.options.filter.test('folder/filename.js')) assert.notOk(cli.watcher.options.filter.test('filename.bla')) assert.notOk(cli.watcher.options.filter.test('folder/filename.bla')) assert.notOk(cli.watcher.options.filter.test('filename.mjs')) assert.notOk(cli.watcher.options.filter.test('folder/filename.mjs')) assert.notOk(cli.watcher.options.filter.test('filename.doc')) assert.notOk(cli.watcher.options.filter.test('folder/filename.doc')) }) t.test('should throw if missing watch', async function() { cli.watch = 'test_not_exist' let err = await assert.isRejected(cli.startWatcher()) assert.match(err.message, /missing/i) assert.match(err.message, /test_not_exist/i) }) t.test('should throw if invalid extensions', async function() { cli.watch = 'test_invalid_extensions' let err = await assert.isRejected(cli.startWatcher()) assert.match(err.message, /extension/i) assert.match(err.message, /test_invalid_extensions/i) }) }) /***************************************** * #fileMatchesTarget() *****************************************/ t.describe('#fileMatchesTarget()', function() { t.test('should match files correctly based on extension in folder', function() { cli.targets = ['test/testtree/folder1/*.txt'] assert.ok(cli.fileMatchesTarget('test/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.txt.mjs')) }) t.test('should match files correctly only in that folder', function() { cli.targets = ['test/*.txt'] assert.ok(cli.fileMatchesTarget('test/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/test/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/bla/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.txt.mjs')) }) t.test('should match single file correctly', function() { cli.targets = ['test/testtree/folder1/sampletest1.temp.mjs'] assert.ok(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs.mjs')) }) t.test('should match every file in a folder', function() { cli.targets = ['test/testtree/folder1/'] assert.ok(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.ok(cli.fileMatchesTarget('test/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) t.test('should work properly if there is a dot in front', function() { cli.targets = ['./test/testtree/folder1/*.txt'] assert.ok(cli.fileMatchesTarget('test/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.txt.mjs')) cli.targets = ['./test/testtree/folder1/sampletest1.temp.mjs'] assert.ok(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs.mjs')) cli.targets = ['./test/testtree/folder1/'] assert.ok(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.ok(cli.fileMatchesTarget('test/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) t.test('should support start as folder substitute', function() { cli.targets = ['*/testtree/folder1/'] assert.ok(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.ok(cli.fileMatchesTarget('test1/testtree/folder1/sampletest1.temp.mjs')) assert.ok(cli.fileMatchesTarget('bla/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) t.test('should support grabbing only files in folder', function() { cli.targets = ['test/*'] assert.ok(cli.fileMatchesTarget('test/test.mjs')) assert.ok(cli.fileMatchesTarget('test/test.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test1/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('bla/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) t.test('should support grabbing only pattern files in folder', function() { cli.targets = ['test/*.mjs'] assert.ok(cli.fileMatchesTarget('test/test.mjs')) assert.notOk(cli.fileMatchesTarget('test/test.txt')) assert.ok(cli.fileMatchesTarget('test/test.herp.mjs')) assert.notOk(cli.fileMatchesTarget('test/test.mjs.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test1/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('bla/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) t.test('should support multiple star pattern', function() { cli.targets = ['test/*/*.mjs'] assert.ok(cli.fileMatchesTarget('test/bla/test.mjs')) assert.notOk(cli.fileMatchesTarget('test/bla/test.txt')) assert.ok(cli.fileMatchesTarget('test/herp/test.herp.mjs')) assert.notOk(cli.fileMatchesTarget('test/test.mjs.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test1/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('bla/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) cli.targets = ['test/*/sample*.mjs'] assert.ok(cli.fileMatchesTarget('test/bla/sampletest.mjs')) assert.ok(cli.fileMatchesTarget('test/herp/sample.test.herp.mjs')) assert.notOk(cli.fileMatchesTarget('test/bla/test.mjs')) assert.notOk(cli.fileMatchesTarget('test/bla/test.txt')) assert.notOk(cli.fileMatchesTarget('test/herp/test.herp.mjs')) assert.notOk(cli.fileMatchesTarget('test/test.mjs.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test1/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('bla/testtree/folder1/bla.txt')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) t.test('should support double star pattern', function() { cli.targets = ['test/**/*.mjs'] assert.ok(cli.fileMatchesTarget('test/sampletest.mjs')) assert.ok(cli.fileMatchesTarget('test/sample.test.herp.mjs')) assert.ok(cli.fileMatchesTarget('test/test.mjs')) assert.notOk(cli.fileMatchesTarget('test/test.mjs.txt')) assert.ok(cli.fileMatchesTarget('test/bla/sampletest.mjs')) assert.ok(cli.fileMatchesTarget('test/herp/sample.test.herp.mjs')) assert.ok(cli.fileMatchesTarget('test/bla/test.mjs')) assert.notOk(cli.fileMatchesTarget('test/bla/test.txt')) assert.ok(cli.fileMatchesTarget('test/herp/test.herp.mjs')) assert.notOk(cli.fileMatchesTarget('test/test.mjs.txt')) assert.ok(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test1/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('bla/testtree/folder1/bla.txt')) assert.ok(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.ok(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) t.test('should support double star pattern at end', function() { cli.targets = ['test/**'] assert.ok(cli.fileMatchesTarget('test/sampletest.mjs')) assert.ok(cli.fileMatchesTarget('test/sample.test.herp.mjs')) assert.ok(cli.fileMatchesTarget('test/test.mjs')) assert.ok(cli.fileMatchesTarget('test/test.mjs.txt')) assert.ok(cli.fileMatchesTarget('test/bla/sampletest.mjs')) assert.ok(cli.fileMatchesTarget('test/herp/sample.test.herp.mjs')) assert.ok(cli.fileMatchesTarget('test/bla/test.mjs')) assert.ok(cli.fileMatchesTarget('test/bla/test.txt')) assert.ok(cli.fileMatchesTarget('test/herp/test.herp.mjs')) assert.ok(cli.fileMatchesTarget('test/test.mjs.txt')) assert.ok(cli.fileMatchesTarget('test/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('test1/testtree/folder1/sampletest1.temp.mjs')) assert.notOk(cli.fileMatchesTarget('bla/testtree/folder1/bla.txt')) assert.ok(cli.fileMatchesTarget('test/testtree/folder2/sampletest1.temp.mjs')) assert.ok(cli.fileMatchesTarget('test/testtree/folder1/bla/bla.mjs')) }) }) /***************************************** * #getFiles() *****************************************/ t.describe('#getFiles()', function() { t.describe('master', function() { t.test('should mark errored if empty', async function() { cli.targets = ['test/testtree/folder1/*.txt'] let files = await cli.getFiles() assert.strictEqual(cli.files.length, 0) assert.strictEqual(cli.files, files) assert.ok(cli.errored) }) t.test('should support direct file path if exists', async function() { cli.targets = ['test/testtree/folder1/sampletest1.temp.mjs'] let files = await cli.getFiles() assert.strictEqual(cli.files.length, 1) assert.strictEqual(cli.files[0], 'test/testtree/folder1/sampletest1.temp.mjs') assert.strictEqual(cli.files, files) }) t.test('should return all files in a directory', async function() { cli.targets = ['test/testtree/folder1/'] let files = await cli.getFiles() assert.strictEqual(cli.files.length, 2) cli.files.sort() assert.strictEqual(cli.files[0], 'test/testtree/folder1/sampletest1.temp.mjs') assert.strictEqual(cli.files[1], 'test/testtree/folder1/sampletest2.temp.mjs') assert.strictEqual(cli.files, files) }) t.test('should support start as folder substitute', async function() { cli.targets = ['*/testtree/folder1/'] let files = await cli.getFiles() assert.strictEqual(cli.files.length, 2) cli.files.sort() assert.strictEqual(cli.files[0], 'test/testtree/folder1/sampletest1.temp.mjs') assert.strictEqual(cli.files[1], 'test/testtree/folder1/sampletest2.temp.mjs') assert.strictEqual(cli.files, files) }) t.test('should support grabbing only files in folder', async function() { cli.targets = ['test/*'] let files = await cli.getFiles() assert.ok(cli.files.length) for (let i = 0; i < cli.files.length; i++) { assert.notOk(cli.files[i].match(/\/folder1\//)) assert.notOk(cli.files[i].match(/\/folder2\//)) } assert.strictEqual(cli.files, files) }) t.test('should support grabbing only pattern files in folder', async function() { cli.targets = ['test/*.test.mjs'] let files = await cli.getFiles() assert.ok(cli.files.length) for (let i = 0; i < cli.files.length; i++) { assert.notOk(cli.files[i].match(/\/folder1\//)) assert.notOk(cli.files[i].match(/\/folder2\//)) } assert.strictEqual(cli.files, files) }) t.test('should support multiple star pattern', async function() { cli.targets = ['test/testtree/*/*.mjs'] let files = await cli.getFiles() assert.strictEqual(cli.files.length, 4) cli.files.sort() assert.deepEqual(cli.files, [ 'test/testtree/folder1/sampletest1.temp.mjs', 'test/testtree/folder1/sampletest2.temp.mjs', 'test/testtree/folder2/sampletest3.temp.mjs', 'test/testtree/folder2/sampletest4.temp.mjs', ]) assert.strictEqual(cli.files, files) cli.targets = ['test/testtree/*/sampletest*.mjs'] files = await cli.getFiles() assert.strictEqual(cli.files.length, 4) cli.files.sort() assert.deepEqual(cli.files, [ 'test/testtree/folder1/sampletest1.temp.mjs', 'test/testtree/folder1/sampletest2.temp.mjs', 'test/testtree/folder2/sampletest3.temp.mjs', 'test/testtree/folder2/sampletest4.temp.mjs', ]) assert.strictEqual(cli.files, files) }) t.test('should support double star pattern', async function() { cli.targets = ['test/**/*.mjs'] let files = await cli.getFiles() assert.ok(cli.files.length) let found = { sampletest1: false, sampletest2: false, sampletest3: false, sampletest4: false, sampletest5: false, cli: false } for (let i = 0; i < cli.files.length; i++) { found.sampletest1 = found.sampletest1 || cli.files[i] === 'test/testtree/folder1/sampletest1.temp.mjs' found.sampletest2 = found.sampletest2 || cli.files[i] === 'test/testtree/folder1/sampletest2.temp.mjs' found.sampletest3 = found.sampletest3 || cli.files[i] === 'test/testtree/folder2/sampletest3.temp.mjs' found.sampletest4 = found.sampletest4 || cli.files[i] === 'test/testtree/folder2/sampletest4.temp.mjs' found.sampletest5 = found.sampletest5 || cli.files[i] === 'test/testtree/folder2/sampletest5.temp.txt' found.cli = found.cli || cli.files[i] === 'test/cli.test.mjs' } assert.deepEqual(found, { sampletest1: true, sampletest2: true, sampletest3: true, sampletest4: true, sampletest5: false, cli: true }) assert.strictEqual(cli.files, files) }) t.test('should support double star pattern end', async function() { cli.targets = ['test/**'] let files = await cli.getFiles() assert.ok(cli.files.length) let found = { sampletest1: false, sampletest2: false, sampletest3: false, sampletest4: false, sampletest5: false, cli: false } for (let i = 0; i < cli.files.length; i++) { found.sampletest1 = found.sampletest1 || cli.files[i] === 'test/testtree/folder1/sampletest1.temp.mjs' found.sampletest2 = found.sampletest2 || cli.files[i] === 'test/testtree/folder1/sampletest2.temp.mjs' found.sampletest3 = found.sampletest3 || cli.files[i] === 'test/testtree/folder2/sampletest3.temp.mjs' found.sampletest4 = found.sampletest4 || cli.files[i] === 'test/testtree/folder2/sampletest4.temp.mjs' found.sampletest5 = found.sampletest5 || cli.files[i] === 'test/testtree/folder2/sampletest5.temp.txt' found.cli = found.cli || cli.files[i] === 'test/cli.test.mjs' } assert.deepEqual(found, { sampletest1: true, sampletest2: true, sampletest3: true, sampletest4: true, sampletest5: true, cli: true }) assert.strictEqual(cli.files, files) }) }) t.describe('slave', function() { let testCluster let testProcess let cli t.beforeEach(function() { testCluster = { isWorker: true } testProcess = { send: stub(), on: stub(), off: stub() } cli = createSafeCli(null, { cluster: testCluster, process: testProcess }) }) t.test('if cli is slave, ask master for list', function(done) { const assertFiles = ['file1', 'file2', 'folder1/file3'] cli.getFiles().then(done.finish(function(files) { assert.deepStrictEqual(files, assertFiles) assert.ok(testProcess.off.called) assert.strictEqual(testProcess.off.firstCall[0], 'message') assert.strictEqual(typeof(testProcess.off.firstCall[1]), 'function') assert.strictEqual(testProcess.off.firstCall[1], testProcess.on.firstCall[1]) assert.deepStrictEqual(cli.files, assertFiles) })) done.safeWrap(function() { assert.notOk(testProcess.off.called) assert.strictEqual(testProcess.on.callCount, 1) assert.strictEqual(testProcess.send.callCount, 1) assert.strictEqual(testProcess.on.firstCall[0], 'message') assert.strictEqual(typeof(testProcess.on.firstCall[1]), 'function') assert.strictEqual(testProcess.send.firstCall[0].messageType, MESSAGE_FILES_REQUEST) process.nextTick(function() { testProcess.on.firstCall[1](createMessage(MESSAGE_FILES_PAYLOAD, assertFiles)) }) }) }) }) }) /***************************************** * # loadFiles() *****************************************/ t.describe('#loadFiles()', function() { let testProcess let cli let eltro let importer t.beforeEach(function() { eltro = new t.Eltro() importer = stub() testProcess = { cwd: stub() } testProcess.cwd.returns('root') cli = createSafeCli(eltro, { process: testProcess, importer }) cli.files = ['file1.js'] }) masterSlave.forEach(child => { t.describe(child, function () { t.beforeEach(function () { cli.isWorker = child === 'worker' }) t.test('should call importer for every js or mjs file', async function() { const testFiles = ['file1.txt', 'file2.js', path.join('folder', 'file3.mjs')] cli.files = testFiles let loaded = [] let filenames = [] importer.returnWith(function(path) { filenames.push(eltro.activeGroup.name) loaded.push(path) return Promise.resolve() }) assert.strictEqual(eltro.starting, null) await cli.loadFiles() assert.ok(eltro.starting) assert.strictEqual(loaded.length, 2) assert.match(loaded[0], new RegExp(path.join('root', testFiles[1]).replace(/\\/g, '\\\\'))) assert.match(loaded[1], new RegExp(path.join('root', testFiles[2]).replace(/\\/g, '\\\\'))) assert.match(filenames[0], new RegExp(testFiles[1].replace(/\\/g, '\\\\'))) assert.match(filenames[1], new RegExp(testFiles[2].replace(/\\/g, '\\\\'))) }) t.test('on error should throw and wrap in inner error', async function() { const assertError = new Error('Ano hi no Omoide') importer.returnWith(function(path) { throw assertError }) let err = await assert.isRejected(cli.loadFiles()) assert.match(err.message, /loading/) assert.strictEqual(err.inner, assertError) }) }) }) t.describe('master in watch mode', function() { t.beforeEach(function () { cli.watch = ['folder1'] }) t.test('should not import anything and resolve successfully', async function() { importer.returnWith(function() { throw new Error('should not be called') }) assert.strictEqual(eltro.starting, null) await cli.loadFiles() assert.strictEqual(eltro.starting, null) assert.notOk(importer.called) }) }) }) /***************************************** * # beginRun() *****************************************/ t.describe('#beginRun()', function() { let testProcess let eltro let cli t.beforeEach(function() { testProcess = { send: stub(), on: stub(), off: stub() } eltro = new t.Eltro() cli = createSafeCli(eltro, { process: testProcess }) eltro.run = stub() }) masterSlave.forEach(child => { t.describe(child, function () { t.beforeEach(function () { cli.isSlave = child === 'worker' }) t.test('should pass options into eltro and run it', async function() { const assertResult = { a: 1 } const assertReporter = 'A girl of good family' const assertIgnoreOnly = 'A girls talk' const assertTimeout = 'Samurai figher' cli.reporter = assertReporter cli.ignoreOnly = assertIgnoreOnly cli.timeout = assertTimeout eltro.run.resolves(assertResult) let result = await cli.beginRun() assert.strictEqual(result, assertResult) assert.strictEqual(eltro.reporter, assertReporter) assert.strictEqual(eltro.ignoreOnly, assertIgnoreOnly) assert.strictEqual(eltro.__timeout, assertTimeout) if (child === 'worker') { assert.ok(testProcess.send.called) assert.strictEqual(testProcess.send.firstCall[0]?.messageType, MESSAGE_RUN_FINISHED) assert.strictEqual(testProcess.send.firstCall[0]?.data?.stats, assertResult) } else { assert.notOk(testProcess.send.called) } }) }) }) t.describe('master in watch mode', function() { t.beforeEach(function () { cli.watch = ['folder1'] cli.runProgram = stub() cli.watcher = { on: stub(), off: stub() } }) t.test('should wait for stats on process and return that', function(done) { const assertStats = { a: 1 } let doneWasRun = false cli.beginRun().then(done.finish(function(stats) { assert.deepStrictEqual(stats, assertStats) assert.strictEqual(cli.runProgram.callCount, 2) assert.strictEqual(testProcess.off.firstCall[0], 'message') assert.strictEqual(typeof(testProcess.off.firstCall[1]), 'function') assert.strictEqual(testProcess.off.firstCall[1], testProcess.on.firstCall[1]) assert.strictEqual(cli.watcher.off.firstCall[0], 'change') assert.strictEqual(typeof(cli.watcher.off.firstCall[1]), 'function') assert.strictEqual(cli.watcher.off.firstCall[1], cli.watcher.on.firstCall[1]) assert.strictEqual(cli.watcher.off.secondCall[0], 'changed') assert.strictEqual(typeof(cli.watcher.off.secondCall[1]), 'function') assert.strictEqual(cli.watcher.off.secondCall[1], cli.watcher.on.secondCall[1]) doneWasRun = true })) done.safeWrap(function() { assert.notOk(testProcess.off.called) assert.strictEqual(testProcess.on.callCount, 1) assert.strictEqual(testProcess.on.firstCall[0], 'message') assert.strictEqual(typeof(testProcess.on.firstCall[1]), 'function') assert.strictEqual(cli.watcher.on.callCount, 2) assert.strictEqual(cli.watcher.on.firstCall[0], 'change') assert.strictEqual(typeof(cli.watcher.on.firstCall[1]), 'function') assert.strictEqual(cli.watcher.on.secondCall[0], 'changed') assert.strictEqual(typeof(cli.watcher.on.secondCall[1]), 'function') assert.strictEqual(cli.runProgram.callCount, 1) testProcess.on.firstCall[1](createMessage(MESSAGE_RUN_FINISHED, { stats: assertStats })) assert.notOk(doneWasRun) assert.strictEqual(cli.runProgram.callCount, 1) cli.watcher.on.secondCall[1]() assert.strictEqual(cli.runProgram.callCount, 2) cli.ac.abort() }) }) t.test('should add and remove new files to files that are called in change', function() { cli.files = ['test/file1.mjs'] cli.targets = ['test/*.mjs'] cli.beginRun() assert.deepStrictEqual(cli.files, ['test/file1.mjs']) assert.strictEqual(cli.watcher.on.firstCall[0], 'change') cli.watcher.on.firstCall[1](EVENT_UPDATE, 'test/file1.mjs') assert.deepStrictEqual(cli.files, ['test/file1.mjs']) cli.watcher.on.firstCall[1](EVENT_UPDATE, 'test/bla/file1.mjs') assert.deepStrictEqual(cli.files, ['test/file1.mjs']) cli.watcher.on.firstCall[1](EVENT_UPDATE, 'test/file2.mjs') assert.deepStrictEqual(cli.files, ['test/file1.mjs', 'test/file2.mjs']) cli.watcher.on.firstCall[1](EVENT_REMOVE, 'test/file2.mjs') assert.deepStrictEqual(cli.files, ['test/file1.mjs']) cli.watcher.on.firstCall[1](EVENT_REMOVE, 'test/file1.mjs') assert.deepStrictEqual(cli.files, []) }) }) }) t.describe('#runProgram()', function() { let testProcess let testCluster let testChildProcess let testWorker let cli t.beforeEach(function() { testProcess = { stdout: { write: stub() }, stderr: { write: stub() } } testWorker = { on: stub(), once: stub(), kill: stub(), send: stub(), stderr: { on: stub() }, stdout: { on: stub() } } testCluster = { fork: stub().returns(testWorker) } testChildProcess = { spawn: stub().returns(testWorker) } cli = createSafeCli(null, { cluster: testCluster, child_process: testChildProcess, process: testProcess }) }) t.describe('in test mode', function() { t.beforeEach(function() { cli.run = 'test' }) t.test('should fork a worker', function() { assert.notOk(cli.worker) assert.notOk(testCluster.fork.called) assert.notOk(testChildProcess.spawn.called) cli.runProgram() assert.ok(testCluster.fork.called) assert.ok(cli.worker) assert.notOk(testChildProcess.spawn.called) assert.strictEqual(cli.worker, testWorker) assert.strictEqual(testWorker.on.firstCall[0], 'message') assert.strictEqual(testWorker.once.firstCall[0], 'exit') assert.notOk(cli.worker.stderr.on.called) assert.notOk(cli.worker.stdout.on.called) }) t.test('should listen on message and send files on file request', function() { const assertFiles = [{ a: 1 }] cli.files = assertFiles cli.runProgram() assert.notOk(testWorker.send.called) cli.worker.on.firstCall[1](createMessage(MESSAGE_FILES_REQUEST)) assert.ok(testWorker.send.called) assert.strictEqual(testWorker.send.firstCall[0].messageType, MESSAGE_FILES_PAYLOAD) assert.strictEqual(testWorker.send.firstCall[0].data, cli.files) }) t.test('should close worker on exit', function() { cli.runProgram() assert.ok(cli.worker) testWorker.once.firstCall[1]() assert.notOk(cli.worker) }) t.test('should not null worker on exit if different worker', function() { const assertNewWorker = { } cli.runProgram() cli.worker = assertNewWorker testWorker.once.firstCall[1]() assert.strictEqual(cli.worker, assertNewWorker) }) t.test('multiple calls should cancel', function() { cli.runProgram() assert.notOk(testWorker.kill.called) assert.ok(testCluster.fork.called) testCluster.fork.reset() cli.runProgram() assert.notOk(testCluster.fork.called) assert.notOk(testWorker.kill.called) }) }) t.describe('in npm mode', function() { t.beforeEach(function() { cli.run = 'somethingelse' }) t.test('should spawn a worker', function() { assert.notOk(cli.worker) assert.notOk(testCluster.fork.called) assert.notOk(testChildProcess.spawn.called) cli.runProgram() assert.notOk(testCluster.fork.called) assert.ok(cli.worker) assert.ok(testChildProcess.spawn.called) assert.strictEqual(testChildProcess.spawn.firstCall[0], 'npm') assert.deepStrictEqual(testChildProcess.spawn.firstCall[1], ['run', cli.run]) assert.strictEqual(cli.worker, testWorker) assert.notOk(testWorker.on.called) assert.strictEqual(testWorker.once.firstCall[0], 'exit') assert.ok(cli.worker.stderr.on.called) assert.ok(cli.worker.stdout.on.called) assert.strictEqual(cli.worker.stderr.on.firstCall[0], 'data') assert.strictEqual(cli.worker.stdout.on.firstCall[0], 'data') }) t.test('should output stderr and stdout to process', function() { const assertStdErrData = 'Kuroda Kenishi' const assertStdOutData = 'Lugh no Ketsui' cli.runProgram() assert.notOk(testProcess.stderr.write.called) assert.notOk(testProcess.stdout.write.called) cli.worker.stderr.on.firstCall[1](assertStdErrData) assert.notOk(testProcess.stdout.write.called) assert.ok(testProcess.stderr.write.called) assert.strictEqual(testProcess.stderr.write.firstCall[0], assertStdErrData) testProcess.stderr.write.reset() cli.worker.stdout.on.firstCall[1](assertStdOutData) assert.ok(testProcess.stdout.write.called) assert.notOk(testProcess.stderr.write.called) assert.strictEqual(testProcess.stdout.write.firstCall[0], assertStdOutData) }) t.test('should close worker on exit', function() { cli.runProgram() assert.ok(cli.worker) testWorker.once.firstCall[1]() assert.notOk(cli.worker) }) t.test('should not null worker on exit if different worker', function() { const assertNewWorker = { } cli.runProgram() cli.worker = assertNewWorker testWorker.once.firstCall[1]() assert.strictEqual(cli.worker, assertNewWorker) }) t.test('multiple calls should kill', function() { cli.runProgram() assert.notOk(testWorker.kill.called) assert.ok(testChildProcess.spawn.called) testChildProcess.spawn.reset().returns(testWorker) cli.runProgram() assert.ok(testChildProcess.spawn.called) assert.ok(testWorker.kill.called) }) }) }) }) t.test('#fileMatches() should support filename matching with glob pattern', async function() { assert.ok(fileMatches('bla.test.mjs', '*.mjs')) assert.ok(fileMatches('bla.test.mjs', '*test.mjs')) assert.ok(fileMatches('bla.test.mjs', 'bla*.mjs')) assert.notOk(fileMatches('bla.test.mjs', 'bla*.js')) assert.notOk(fileMatches('bla.test.mjs', '*.js')) assert.notOk(fileMatches('bla.test.mjs', 'blas*.js')) })