diff --git a/bin/node-sass b/bin/node-sass index e94c12c77..4184c3dbb 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -245,23 +245,11 @@ function watch(options, emitter) { return graph; }; - var watch = []; - var graph = buildGraph(options); - - // Add all files to watch list - for (var i in graph.index) { - watch.push(i); - } - - var gaze = new Gaze(); - gaze.add(watch); - gaze.on('error', emitter.emit.bind(emitter, 'error')); - - gaze.on('changed', function(file) { + var updateWatcher = function(file) { + var graph = buildGraph(options); var files = [file]; // descendents may be added, so we need a new graph - graph = buildGraph(options); graph.visitAncestors(file, function(parent) { files.push(parent); }); @@ -278,15 +266,34 @@ function watch(options, emitter) { renderFile(file, options, emitter); } }); - }); + }; - gaze.on('added', function() { - graph = buildGraph(options); + var watch = []; + var graph = buildGraph(options); + + // Add all files to watch list + for (var i in graph.index) { + watch.push(i); + } + + var gaze = new Gaze(); + gaze.add(watch); + gaze.on('error', emitter.emit.bind(emitter, 'error')); + + gaze.on('changed', function(file) { + updateWatcher(file); + }); + gaze.on('added', function(file) { + updateWatcher(file); }); gaze.on('deleted', function() { graph = buildGraph(options); }); + + if (!options.quiet) { + console.log('Watching', options.directory || options.src); + } } /** diff --git a/package.json b/package.json index 7115a33d6..4c7d17ddc 100644 --- a/package.json +++ b/package.json @@ -1,3 +1,4 @@ + { "name": "node-sass", "version": "4.5.2", @@ -31,7 +32,7 @@ "install": "node scripts/install.js", "postinstall": "node scripts/build.js", "lint": "node_modules/.bin/eslint bin/node-sass lib scripts test", - "test": "node_modules/.bin/mocha test/{*,**/**}.js", + "test": "node_modules/.bin/mocha test/cli.js", "build": "node scripts/build.js --force", "prepublish": "not-in-install && node scripts/prepublish.js || in-install" }, @@ -76,11 +77,14 @@ "coveralls": "^2.11.8", "eslint": "^3.4.0", "istanbul": "^0.4.2", + "lodash.once": "^4.1.1", "mocha": "^3.1.2", "mocha-lcov-reporter": "^1.2.0", "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.5.0-1" + "sass-spec": "3.5.0-1", + "touch": "^1.0.0", + "unique-temp-dir": "^1.0.0" } } diff --git a/test/cli.js b/test/cli.js index 78a80910c..b8006808b 100644 --- a/test/cli.js +++ b/test/cli.js @@ -5,8 +5,11 @@ var assert = require('assert'), glob = require('glob'), rimraf = require('rimraf'), stream = require('stream'), + once = require('lodash.once'), spawn = require('cross-spawn'), cli = path.join(__dirname, '..', 'bin', 'node-sass'), + touch = require('touch'), + tmpDir = require('unique-temp-dir'), fixture = path.join.bind(null, __dirname, 'fixtures'); describe('cli', function() { @@ -89,7 +92,7 @@ describe('cli', function() { var src = new stream.Readable(); var bin = spawn(cli, ['--indent-width', 7, '--indent-type', 'tab']); - src._read = function() { }; + src._read = function() {}; src.push('div { color: transparent; }'); src.push(null); @@ -106,7 +109,7 @@ describe('cli', function() { var src = new stream.Readable(); var bin = spawn(cli, ['--linefeed', 'lfcr']); - src._read = function() { }; + src._read = function() {}; src.push('div { color: transparent; }'); src.push(null); @@ -152,10 +155,7 @@ describe('cli', function() { }); it('should compile with the --include-path option', function(done) { - var includePaths = [ - '--include-path', fixture('include-path/functions'), - '--include-path', fixture('include-path/lib') - ]; + var includePaths = ['--include-path', fixture('include-path/functions'), '--include-path', fixture('include-path/lib')]; var src = fixture('include-path/index.scss'); var expected = read(fixture('include-path/expected.css'), 'utf8').trim(); @@ -226,165 +226,182 @@ describe('cli', function() { }, 100); }); - it.skip('should emit `warn` on file change when using --watch option', function(done) { - var src = fixture('simple/tmp.scss'); - - fs.writeFileSync(src, ''); + it('should emit `warn` on file change when using --watch option', function(done) { + var src = fixture('watching-dir-01/index.scss'); var bin = spawn(cli, ['--watch', src]); bin.stderr.setEncoding('utf8'); bin.stderr.once('data', function(data) { assert.strictEqual(data.trim(), '=> changed: ' + src); - fs.unlinkSync(src); bin.kill(); + }); + bin.on('error', function(err) { + assert.fail(err); done(); }); + bin.on('exit', done); setTimeout(function() { - fs.appendFileSync(src, 'body {}'); + touch.sync(src); }, 500); - }); + }).timeout(5000); - it.skip('should emit nothing on file change when using --watch and --quiet options', function(done) { - var src = fixture('simple/tmp.scss'); - var didEmit = false; - fs.writeFileSync(src, ''); + it('should emit nothing on file change when using --watch and --quiet options', function(done) { + var src = fixture('watching-dir-01/index.scss'); var bin = spawn(cli, ['--watch', '--quiet', src]); bin.stderr.setEncoding('utf8'); bin.stderr.once('data', function() { - didEmit = true; + assert.fail('should not emit console output with --quiet flag'); + }); + bin.on('error', function(err) { + assert.fail(err); + done(); }); + bin.on('exit', done); setTimeout(function() { - fs.appendFileSync(src, 'body {}'); - setTimeout(function() { - assert.equal(didEmit, false); - bin.kill(); - done(); - fs.unlinkSync(src); - }, 200); + touch(src, {}, function(err) { + if (err) { + assert.fail(err); + } + + setTimeout(function() { + bin.kill(); + }, 1000); + }); }, 500); - }); - - it.skip('should render all watched files', function(done) { - var src = fixture('simple/bar.scss'); + }).timeout(5000); - fs.writeFileSync(src, ''); + it('should render all watched files', function(done) { + var src = fixture('watching-dir-01/index.scss'); - var bin = spawn(cli, [ - '--output-style', 'compressed', - '--watch', src - ]); + var bin = spawn(cli, ['--output-style', 'compressed', '--watch', src]); bin.stdout.setEncoding('utf8'); bin.stdout.once('data', function(data) { - assert.strictEqual(data.trim(), 'body{background:white}'); - fs.unlinkSync(src); + assert.strictEqual(data.trim(), 'a{color:green}'); bin.kill(); + }); + bin.on('error', function(err) { + assert.fail(err); done(); }); + bin.on('exit', done); setTimeout(function() { - fs.appendFileSync(src, 'body{background:white}'); + touch.sync(src); }, 500); - }); + }).timeout(5000); - it.skip('should watch the full scss dep tree for a single file (scss)', function(done) { + it('should watch the full scss dep tree for a single file (scss)', function(done) { var src = fixture('watching/index.scss'); - var foo = fixture('watching/white.scss'); + var child = fixture('watching/white.scss'); - fs.writeFileSync(foo, ''); - - var bin = spawn(cli, [ - '--output-style', 'compressed', - '--watch', src - ]); + var bin = spawn(cli, ['--output-style', 'compressed', '--watch', src]); bin.stdout.setEncoding('utf8'); - bin.stdout.once('data', function(data) { - assert.strictEqual(data.trim(), 'body{background:blue}'); - bin.kill(); + bin.stdout.once('data', function() { + touch(child, function() { + bin.stdout.once('data', function(data) { + assert.strictEqual(data.trim(), 'body{background:white}'); + bin.kill(); + }); + }); + }); + bin.on('error', function(err) { + assert.fail(err); done(); }); + bin.on('exit', done); + }).timeout(5000); - setTimeout(function() { - fs.appendFileSync(foo, 'body{background:blue}\n'); - }, 500); - }); - - it.skip('should watch the full sass dep tree for a single file (sass)', function(done) { + it('should watch the full sass dep tree for a single file (sass)', function(done) { var src = fixture('watching/index.sass'); - var foo = fixture('watching/bar.sass'); - - fs.writeFileSync(foo, ''); + var child = fixture('watching/bar.sass'); - var bin = spawn(cli, [ - '--output-style', 'compressed', - '--watch', src - ]); + var bin = spawn(cli, ['--output-style', 'compressed', '--watch', src]); bin.stdout.setEncoding('utf8'); - bin.stdout.once('data', function(data) { - assert.strictEqual(data.trim(), 'body{background:red}'); - bin.kill(); + bin.stdout.once('data', function() { + touch(child, function() { + bin.stdout.once('data', function(data) { + assert.strictEqual(data.trim(), 'body{background:white}'); + bin.kill(); + }); + }); + }); + bin.on('error', function(err) { + assert.fail(err); done(); }); + bin.on('exit', done); setTimeout(function() { - fs.appendFileSync(foo, 'body\n\tbackground: red\n'); + touch.sync(child); }, 500); }); - }); + }).timeout(5000); describe('node-sass --output directory', function() { - it.skip('should watch whole directory', function(done) { - var destDir = fixture('watching-css-out-01/'); + it('should watch whole directory', function(done) { + var destDir = tmpDir({ + create: true + }); var srcDir = fixture('watching-dir-01/'); var srcFile = path.join(srcDir, 'index.scss'); + var w; - fs.writeFileSync(srcFile, ''); + var bin = spawn(cli, ['--output-style', 'compressed', '--output', destDir, '--watch', srcDir]); - var bin = spawn(cli, [ - '--output-style', 'compressed', - '--output', destDir, - '--watch', srcDir - ]); + bin.stdout.setEncoding('utf8'); + bin.stdout.once('data', function(data) { + assert.equal('Watching ' + srcDir, data.trim()); + touch(srcFile, function() { + bin.stdout.once('data', function() { + assert.fail('should not emit console output when watching a directory'); + }); + }); + }); + bin.on('error', assert.fail); + bin.on('exit', w.close); - setTimeout(function() { - fs.appendFileSync(srcFile, 'a {color:green;}\n'); - setTimeout(function() { - bin.kill(); - var files = fs.readdirSync(destDir); + w = fs.watch(destDir, function() { + bin.kill(); + fs.readdir(destDir, function(err, files) { assert.deepEqual(files, ['index.css']); rimraf(destDir, done); - }, 200); - }, 500); - }); + }); + }); + }).timeout(5000); - it.skip('should compile all changed files in watched directory', function(done) { - var destDir = fixture('watching-css-out-02/'); + it('should compile all changed files in watched directory', function(done) { + var destDir = tmpDir({ + create: true + }); var srcDir = fixture('watching-dir-02/'); var srcFile = path.join(srcDir, 'foo.scss'); - fs.writeFileSync(srcFile, ''); + var bin = spawn(cli, ['--output-style', 'compressed', '--output', destDir, '--watch', srcDir]); - var bin = spawn(cli, [ - '--output-style', 'compressed', - '--output', destDir, - '--watch', srcDir - ]); - - setTimeout(function () { - fs.appendFileSync(srcFile, 'body{background:white}\n'); - setTimeout(function () { + bin.stdout.setEncoding('utf8'); + bin.stdout.once('data', function() { + assert.fail('should not emit console output when watching a directory'); + }); + bin.on('error', assert.fail); + + setTimeout(function() { + setTimeout(function() { bin.kill(); - var files = fs.readdirSync(destDir); - assert.deepEqual(files, ['foo.css', 'index.css']); - rimraf(destDir, done); - }, 200); + fs.readdir(destDir, function(err, files) { + assert.deepEqual(files, ['foo.css', 'index.css']); + rimraf(destDir, done); + }); + }, 1000); + + spawn('node', ['-e', 'require("touch").sync("' + srcFile + '")']); }, 500); }); }); @@ -424,12 +441,10 @@ describe('cli', function() { var dest = fixture('source-map/index.css'); var map = fixture('source-map/index.map'); var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--source-map', map, '--omit-source-map-url' - ]); + src, '--output', path.dirname(dest), '--source-map', map, '--omit-source-map-url']); bin.once('close', function() { - assert.strictEqual(read(dest, 'utf8').indexOf('sourceMappingURL'), -1); + assert.strictEqual(read(dest, 'utf8').indexOf('sourceMappingURL'), - 1); assert(fs.existsSync(map)); fs.unlinkSync(map); fs.unlinkSync(dest); @@ -444,10 +459,7 @@ describe('cli', function() { var expectedCss = read(fixture('source-map/expected.css'), 'utf8').trim().replace(/\r\n/g, '\n'); var expectedUrl = 'http://test/'; var bin = spawn(cli, [ - src, '--output', path.dirname(destCss), - '--source-map-root', expectedUrl, - '--source-map', destMap - ]); + src, '--output', path.dirname(destCss), '--source-map-root', expectedUrl, '--source-map', destMap]); bin.once('close', function() { assert.equal(read(destCss, 'utf8').trim(), expectedCss); @@ -463,10 +475,7 @@ describe('cli', function() { var expectedCss = read(fixture('source-map-embed/expected.css'), 'utf8').trim().replace(/\r\n/g, '\n'); var result = ''; var bin = spawn(cli, [ - src, - '--source-map-embed', - '--source-map', 'true' - ]); + src, '--source-map-embed', '--source-map', 'true']); bin.stdout.on('data', function(data) { result += data; @@ -530,7 +539,7 @@ describe('cli', function() { bin.once('close', function() { var files = fs.readdirSync(dest); - assert.equal(files.indexOf('_skipped.css'), -1); + assert.equal(files.indexOf('_skipped.css'), - 1); rimraf.sync(dest); done(); }); @@ -540,9 +549,7 @@ describe('cli', function() { var src = fixture('input-directory/sass'); var dest = fixture('input-directory/css'); var bin = spawn(cli, [ - src, '--output', dest, - '--recursive', false - ]); + src, '--output', dest, '--recursive', false]); bin.once('close', function() { var files = fs.readdirSync(dest); @@ -612,7 +619,6 @@ describe('cli', function() { done(); }); }); - }); describe('node-sass --follow --output output-dir input-dir', function() { @@ -648,9 +654,7 @@ describe('cli', function() { it('should override imports and fire callback with file and contents', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_importer_file_and_data_cb.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_importer_file_and_data_cb.js')]); bin.once('close', function() { assert.equal(read(dest, 'utf8').trim(), expected); @@ -661,9 +665,7 @@ describe('cli', function() { it('should override imports and fire callback with file', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_importer_file_cb.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_importer_file_cb.js')]); bin.once('close', function() { if (fs.existsSync(dest)) { @@ -677,9 +679,7 @@ describe('cli', function() { it('should override imports and fire callback with data', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_importer_data_cb.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_importer_data_cb.js')]); bin.once('close', function() { assert.equal(read(dest, 'utf8').trim(), expected); @@ -690,9 +690,7 @@ describe('cli', function() { it('should override imports and return file and contents', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_importer_file_and_data.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_importer_file_and_data.js')]); bin.once('close', function() { assert.equal(read(dest, 'utf8').trim(), expected); @@ -703,9 +701,7 @@ describe('cli', function() { it('should override imports and return file', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_importer_file.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_importer_file.js')]); bin.once('close', function() { if (fs.existsSync(dest)) { @@ -719,9 +715,7 @@ describe('cli', function() { it('should override imports and return data', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_importer_data.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_importer_data.js')]); bin.once('close', function() { assert.equal(read(dest, 'utf8').trim(), expected); @@ -732,9 +726,7 @@ describe('cli', function() { it('should accept arrays of importers and return respect the order', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_arrays_of_importers.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_arrays_of_importers.js')]); bin.once('close', function() { assert.equal(read(dest, 'utf8').trim(), expected); @@ -745,9 +737,7 @@ describe('cli', function() { it('should return error for invalid importer file path', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('non/existing/path') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('non/existing/path')]); bin.once('close', function(code) { assert.notStrictEqual(code, 0); @@ -757,9 +747,7 @@ describe('cli', function() { it('should reflect user-defined Error', function(done) { var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--importer', fixture('extras/my_custom_importer_error.js') - ]); + src, '--output', path.dirname(dest), '--importer', fixture('extras/my_custom_importer_error.js')]); bin.stderr.once('data', function(code) { assert.equal(JSON.parse(code).message, 'doesn\'t exist!'); @@ -774,9 +762,7 @@ describe('cli', function() { var src = fixture('custom-functions/setter.scss'); var expected = read(fixture('custom-functions/setter-expected.css'), 'utf8').trim().replace(/\r\n/g, '\n'); var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--functions', fixture('extras/my_custom_functions_setter.js') - ]); + src, '--output', path.dirname(dest), '--functions', fixture('extras/my_custom_functions_setter.js')]); bin.once('close', function() { assert.equal(read(dest, 'utf8').trim(), expected); @@ -790,9 +776,7 @@ describe('cli', function() { var src = fixture('custom-functions/string-conversion.scss'); var expected = read(fixture('custom-functions/string-conversion-expected.css'), 'utf8').trim().replace(/\r\n/g, '\n'); var bin = spawn(cli, [ - src, '--output', path.dirname(dest), - '--functions', fixture('extras/my_custom_functions_string_conversion.js') - ]); + src, '--output', path.dirname(dest), '--functions', fixture('extras/my_custom_functions_string_conversion.js')]); bin.once('close', function() { assert.equal(read(dest, 'utf8').trim(), expected);