UNPKG

simplywatch

Version:

Watches files and upon change executes a command for each file INDIVIDUALLY with file-related params

632 lines (463 loc) 17.4 kB
global.Promise = require 'bluebird'; Promise.config longStackTraces:process.env.PROMISE_DEBUG, warnings:false fs = require 'fs-jetpack' path = require 'path' expect = require('chai').expect helpers = require './helpers' SimplyWatch = helpers.simplywatch runWatchTask = helpers.runWatchTask sample = ()-> path.join __dirname,'samples',arguments... temp = ()-> path.join __dirname,'temp',arguments... suite "SimplyWatch", ()-> suiteTeardown ()-> fs.removeAsync('test/temp') unless process.env.KEEP suiteSetup ()-> Promise.all [ fs.dirAsync('test/temp', empty:true) helpers.files(helpers.samples) ] setup ()-> Promise.delay(300) if process.env.CI suite "file handling", ()-> test "if a discovered import has no extension specified, various file extensions will be used to check for a valid file", ()-> runWatchTask( expected:2 glob: 'test/samples/sass/*' targetChange: sample('sass/nested/one.sass') sort: 'base' ).spread (results, watchTask)-> expect(results.length).to.equal(2) expect(results[0].base).to.equal "main.copy.sass" expect(results[1].base).to.equal "main.sass" watchTask.stop() test "binary files will not have their imports scanned", ()-> runWatchTask( expected: 2 # not real timeout: 500 glob: 'test/samples/binary/*' targetChange: [sample('js/sampleA.js'), sample('js/sampleB.js')] sort: 'base' ).spread (results, watchTask)-> expect(results.length).to.equal(0) watchTask.stop() test "binary files will trigger change events", ()-> runWatchTask( expected: 2 glob: 'test/samples/binary/*' targetChange: [sample('binary/.DS_Store'), sample('binary/one.zip'), sample('binary/two.mp3')] sort: 'base' ).spread (results, watchTask)-> expect(results.length).to.equal(2) watchTask.stop() test "binary files will not have their imports scanned", ()-> runWatchTask( expected: 2 # not real timeout: 500 glob: 'test/samples/img/*' targetChange: [sample('js/sampleA.js'), sample('js/sampleB.js')] sort: 'base' ).spread (results, watchTask)-> expect(results.length).to.equal(0) watchTask.stop() test "image files will trigger change events", ()-> runWatchTask( expected: 2 timeout: 500 glob: 'test/samples/img/*' targetChange: [sample('img/one.svg'), sample('img/two.png')] sort: 'base' ).spread (results, watchTask)-> expect(results.length).to.equal(2) watchTask.stop() test ".bin files will have their imports scanned", ()-> runWatchTask( expected: 2 glob: 'test/samples/bin/.bin' targetChange: [sample('bin/.bin'), sample('js/sampleB.js')] sort: 'base' ).spread (results, watchTask)-> expect(results.length).to.equal(2) watchTask.stop() test "files inside node_modules will not trigger change events", ()-> Promise.resolve() .then ()-> helpers.files temp(), 'node_modules/module-a/index.js': "module.exports = 'module-a'" 'node_modules/module-b/index.js': "module.exports = 'module-b'" 'local.js': "module.exports = 'local'" 'main.js': """ import 'module-a' import 'module-b' import './local' """ .then ()-> runWatchTask expected: 2 timeout: 800 glob: 'test/temp/*' targetChange: [temp('local.js'), [200, temp('node_modules/module-a/index.js')], [400, temp('node_modules/module-b/index.js')]] sort: 'base' .spread (results, watchTask)-> expect(results.length).to.equal(1) watchTask.stop() test "files inside node_modules will trigger change events when options.watchModules is set", ()-> Promise.resolve() .then ()-> helpers.files temp(), 'node_modules/module-a/index.js': "module.exports = 'module-a'" 'node_modules/module-b/index.js': "module.exports = 'module-b'" 'local.js': "module.exports = 'local'" 'main.js': """ import 'module-a' import 'module-b' import './local' """ .then ()-> runWatchTask expected: 3 glob: 'test/temp/*' targetChange: [temp('local.js'), [200, temp('node_modules/module-a/index.js')], [400, temp('node_modules/module-b/index.js')]] sort: 'base' opts: watchModules:true .spread (results, watchTask)-> expect(results.length).to.equal(3) watchTask.stop() suite "watching & command execution", ()-> test "will execute a given command on all matched files/dirs in a given glob upon change", ()-> runWatchTask( expected:1 glob: 'test/samples/js/*' targetChange: targetFile=helpers.randSample() ).spread (results, watchTask)-> expect(results.length).to.equal(1) expect(results[0].base).to.equal path.basename(targetFile) watchTask.stop() test "will search for imports and if an import changes only its dependents will get updated", ()-> runWatchTask( expected:2 glob: 'test/samples/js/**' targetChange: sample('js/nested/one.js') sort: 'base' ).spread (results, watchTask)-> expect(results.length).to.equal(2) expect(results[0].base).to.equal "mainCopy.js" expect(results[1].base).to.equal "mainCopy2.js" watchTask.stop() test.skip "Commands will only execute once if changed multiple times within the execDelay option", ()-> options = globs:['test/samples/js/*'], command:'echo {{name}} >> test/temp/three', execDelay:5000 Promise.resolve() .then ()-> SimplyWatch(options) .then (watcher)-> watcher.ready .then ()-> helpers.triggerFileChange('test/samples/js/mainCopy.js', 'test/temp/three') .then ()-> helpers.triggerFileChange('test/samples/js/mainCopy.js', 'test/temp/three.2', 500) .then ()-> fs.readAsync('test/temp/three') .then (result)-> expect(result).to.equal "mainCopy\n" .then ()-> fs.readAsync('test/temp/three.2') .catch (err)-> return err .then ()-> expect(err).to.be.an.error watcher.close() test "error messages from string commands will be outputted to the terminal as well", ()-> stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:1 expectedTarget: 'command' silent: false glob: targetFile = helpers.randSample() opts: command: '>&2 echo "theError" && exit 2' ).spread (results, watchTask)-> expect(stdout).to.include path.basename(targetFile) expect(stdout).to.include "theError" watchTask.stop() test "error messages from commands will be treated as stdout if the command's exit code was 0", ()-> stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:1 expectedTarget: 'command' silent: false glob: targetFile = helpers.randSample() opts: command: '>&2 echo "stdErr"' ).spread (results, watchTask)-> expect(stdout).to.include path.basename(targetFile) expect(stdout).to.include "stdErr" watchTask.stop() test "if the command exits with a non-zero status code and there isn't any stdout, the actual error message will be written to the terminal", ()-> stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:1 expectedTarget: 'command' silent: false glob: targetFile = helpers.randSample() opts: command: 'exit 1' ).spread (results, watchTask)-> expect(stdout).to.include path.basename(targetFile) expect(stdout).to.include "Command failed:" watchTask.stop() suite "placeholders", ()-> test "function commands can have placeholders in them replaced by the file's values", ()-> runWatchTask( expected:1 glob: targetFile = helpers.randSample() ).spread (results, watchTask)-> expect(results.length).to.equal 1 params = helpers.pathParams(targetFile) expect(results[0]).to.eql name: params.name ext: params.ext base: params.base reldir: '' path: params.path dir: params.dir root: '/' watchTask.stop() test "commands can have placeholders in them replaced by the file's values", ()-> runWatchTask( expected:'test/temp/four' expectedTarget:'file' glob: targetFile = helpers.randSample() opts: command: 'echo "{{name}} {{ext}} {{base}} {{reldir}} {{path}} {{dir}}" >> test/temp/four' ).spread (results, watchTask)-> results = results.split '\n' params = helpers.pathParams(targetFile) expect(results[0]).to.equal "#{params.name} #{params.ext} #{params.base} #{params.path} #{params.dir}" watchTask.stop() test "placeholders can be denoted either with dual curly braces or just a hash+single curly braces", ()-> runWatchTask( expected:'test/temp/five' expectedTarget:'file' glob: targetFile = helpers.randSample() opts: command: 'echo "#{name} #{ext} #{base} #{reldir} #{path} #{dir}" >> test/temp/five' ).spread (results, watchTask)-> results = results.split '\n' params = helpers.pathParams(targetFile) expect(results[0]).to.equal "#{params.name} #{params.ext} #{params.base} #{params.path} #{params.dir}" watchTask.stop() test "invalid placeholders will remain unreplaced", ()-> runWatchTask( expected:'test/temp/six' expectedTarget:'file' glob: targetFile = helpers.randSample() opts: command: 'echo "{{name}} {{badPlaceholder}}" >> test/temp/six' ).spread (results, watchTask)-> results = results.split '\n' params = helpers.pathParams(targetFile) expect(results[0]).to.equal "#{params.name} {{badPlaceholder}}" watchTask.stop() suite "options", ()-> test "if options.trim is set to a number, any output messages from commands will be trimmed to only the first X characters", ()-> stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:1 expectedTarget: 'command' silent: false glob: 'test/samples/js/**' targetChange: sample('js/mainCopy.js') opts: command: 'echo {{name}}' trim: 6 ).spread (results, watchTask)-> expect(stdout).to.include 'js/mainCopy.js' expect(stdout).to.include "mainC…" watchTask.stop() test "if options.ignoreGlobs is provided, any file that matches the ignore glob (even partially) will not have a command executed for it, but if it is imported by a parent file then the parent will be processed", ()-> runWatchTask( expected:3 glob: 'test/samples/js/**' timeout: 1500 targetChange: [sample('js/nested/one.js'), sample('js/nested/three.js'), sample('js/mainDiff.js')] sort: 'base' opts: # bufferTimeout: 150 ignoreGlobs: 'test/samples/js/nested' ).spread (results, watchTask)-> expect(results.length).to.equal(3) expect(results[0].base).to.equal "mainCopy.js" expect(results[1].base).to.equal "mainCopy2.js" expect(results[2].base).to.equal "mainDiff.js" watchTask.stop() test "files inside .git/ will autotomatically be ignored", ()-> Promise.all([ fs.fileAsync('test/temp2/.git/insideGit.js') fs.fileAsync('test/temp2/git/outsideGit.js') ]).then ()-> runWatchTask( expected:1 glob: 'test/temp2/**' targetChange: ['test/temp2/.git/insideGit.js', 'test/temp2/git/outsideGit.js'] ).spread (results, watchTask)-> expect(results.length).to.equal(1) expect(results[0].base).to.equal "outsideGit.js" watchTask.stop() fs.removeAsync('test/temp2') test "if a function is provided for options.finalCommand, that command will be executed after each batch of file changes has been processed", ()-> finalCommandExecuted = false runWatchTask( expected:1 expectedTarget: 'finalCommand' glob: 'test/samples/js/**' targetChange: sample('js/mainCopy.js') opts: finalCommand: ()-> finalCommandExecuted = true finalCommandDelay: 1 ).spread (results, watchTask)-> expect(finalCommandExecuted).to.be.true watchTask.stop() test "if a string is provided for options.finalCommand, that command will be executed after each batch of file changes has been processed", ()-> stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:1 expectedTarget: 'finalCommand' silent: false glob: 'test/samples/js/**' targetChange: sample('js/mainCopy.js') opts: finalCommand: 'echo "final command was executed"' finalCommandDelay: 1 ).spread (results, watchTask)-> expect(stdout).to.include 'final command was executed' watchTask.stop() test "the final command will only execute once in a given delay (options.finalCommandDelay)", ()-> finalCommandExecuted = 0 runWatchTask( expected:1 expectedTarget: 'finalCommand' glob: 'test/samples/js/**' targetChange: [sample('js/mainCopy.js'), [150, sample('js/mainCopy.js')], [350, sample('js/mainCopy.js')]] opts: finalCommand: ()-> finalCommandExecuted++ finalCommandDelay: 400 bufferTimeout: 1 ).spread (results, watchTask)-> expect(results.length).to.equal 3 expect(finalCommandExecuted).to.equal 1 watchTask.stop() test "if the final command exits with a non-zero status code the error message will be written to the terminal", ()-> Promise.resolve() .then ()-> stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:1 expectedTarget: 'finalCommand' silent: false glob: helpers.randSample() opts: finalCommand: 'echo "final command was executed" && exit 2' finalCommandDelay: 1 ).spread (results, watchTask)-> expect(stdout).to.include 'final command was executed' expect(stdout).not.to.include 'Command failed:' watchTask.stop() .then ()-> stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:1 expectedTarget: 'finalCommand' silent: false glob: helpers.randSample() opts: finalCommand: 'exit 2' finalCommandDelay: 1 ).spread (results, watchTask)-> expect(stdout).to.include 'Command failed:' watchTask.stop() test "if a command exits with a non-zero status the final command execution will be canceled", ()-> finalCommandExecuted = 0 stdout = '' helpers.customStdout.on 'data', (chunk)-> stdout+=chunk runWatchTask( expected:350 expectedTarget: 'delay' glob: 'test/samples/js/**' targetChange: sample('js/mainCopy.js') opts: command: 'echo 1; exit 1' finalCommand: ()-> finalCommandExecuted++ finalCommandDelay: 1 bufferTimeout: 1 ).spread (results, watchTask)-> expect(finalCommandExecuted).to.equal 0 expect(stdout).to.include 'Final Command' expect(stdout).to.include 'Aborted' expect(stdout).to.include '(because some tasks failed)' watchTask.stop() suite "errors", ()-> test "no globs", ()-> caught = false Promise.resolve() .then ()-> SimplyWatch({command:';'}) .catch (err)-> expect(err).to.be.an.error expect(err.message).to.equal 'No/Invalid globs were provided' caught = true .then ()-> expect(caught, 'error thrown').to.equal true test "invalid globs", ()-> caught = false Promise.resolve() .then ()-> SimplyWatch({globs:[123]}) .catch (err)-> expect(err).to.be.an.error expect(err.message).to.equal 'No/Invalid globs were provided' caught = true .then ()-> expect(caught, 'error thrown').to.equal true test "empty globs array", ()-> caught = false Promise.resolve() .then ()-> SimplyWatch({globs:[]}) .catch (err)-> expect(err).to.be.an.error expect(err.message).to.equal 'No/Invalid globs were provided' caught = true .then ()-> expect(caught, 'error thrown').to.equal true test "non-array", ()-> caught = false Promise.resolve() .then ()-> SimplyWatch({globs:'test/samples', command:';'}) .catch (err)-> throw err caught = true .then (watchTask)-> expect(caught, 'error thrown').to.equal false watchTask.stop() test "no command", ()-> caught = false Promise.resolve() .then ()-> SimplyWatch({globs:'*'}) .catch (err)-> expect(err).to.be.an.error expect(err.message).to.equal 'Execution command not provided' caught = true .then ()-> expect(caught, 'error thrown').to.equal true test "invalid command", ()-> caught = false Promise.resolve() .then ()-> SimplyWatch({globs:'*', command:14}) .catch (err)-> expect(err).to.be.an.error expect(err.message).to.equal 'Invalid execution command provided: only a string or a callback may be provided' caught = true .then ()-> expect(caught, 'error thrown').to.equal true test "invalid final command", ()-> caught = false Promise.resolve() .then ()-> SimplyWatch({globs:'*', command:';', finalCommand:14}) .catch (err)-> expect(err).to.be.an.error expect(err.message).to.equal 'Invalid final execution command provided: only a string or a callback may be provided' caught = true .then ()-> expect(caught, 'error thrown').to.equal true