From d4e550ea085ccc8e53b6131777717859210b522e Mon Sep 17 00:00:00 2001 From: xzyfer Date: Mon, 13 Feb 2017 16:33:34 +1100 Subject: [PATCH] Watcher should track newly created files Currently newly created are added to the graph but not added the watcher. Fixes #1891 --- bin/node-sass | 41 ++++++---- package.json | 8 +- test/cli.js | 215 +++++++++++++++++++++++++++++++------------------- 3 files changed, 162 insertions(+), 102 deletions(-) diff --git a/bin/node-sass b/bin/node-sass index e94c12c77..c7825771a 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) { + emitter.emit('warn', util.format('Watching %s', 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..b5eabcda7 100644 --- a/test/cli.js +++ b/test/cli.js @@ -5,14 +5,17 @@ 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() { +describe.only('cli', function() { // For some reason we experience random timeout failures in CI // due to spawn hanging/failing silently. See #1692. - this.retries(4); + // this.retries(4); describe('node-sass < in.scss', function() { it('should read data from stdin', function(done) { @@ -226,53 +229,57 @@ 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.stderr.once('data', function (data) { + touch.sync(src); + }); + bin.stderr.on('data', function (data) { + if (data.trim() === '=> changed: ' + src) { + bin.kill(); + } + }); + bin.on('error', function(err) { + assert.fail(err); done(); }); + bin.on('exit', done); + }).timeout(5000); - setTimeout(function() { - fs.appendFileSync(src, 'body {}'); - }, 500); - }); - - 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); - }); + }).timeout(5000); - it.skip('should render all watched files', function(done) { - var src = fixture('simple/bar.scss'); - - 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', @@ -280,23 +287,26 @@ describe('cli', function() { ]); bin.stdout.setEncoding('utf8'); + // bin.stderr.on('data', function(data) { console.log('stderr', data.toString()) }) + // bin.stdout.on('data', function(data) { console.log('stdout', data.toString()) }) 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'); - - fs.writeFileSync(foo, ''); + var child = fixture('watching/white.scss'); var bin = spawn(cli, [ '--output-style', 'compressed', @@ -304,22 +314,24 @@ describe('cli', function() { ]); bin.stdout.setEncoding('utf8'); - bin.stdout.once('data', function(data) { - assert.strictEqual(data.trim(), 'body{background:blue}'); - bin.kill(); + bin.stderr.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', @@ -327,65 +339,99 @@ describe('cli', function() { ]); bin.stdout.setEncoding('utf8'); - bin.stdout.once('data', function(data) { - assert.strictEqual(data.trim(), 'body{background:red}'); - bin.kill(); + bin.stderr.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'); - fs.writeFileSync(srcFile, ''); - var bin = spawn(cli, [ '--output-style', 'compressed', '--output', destDir, '--watch', srcDir ]); - setTimeout(function() { - fs.appendFileSync(srcFile, 'a {color:green;}\n'); - setTimeout(function() { + var w = fs.watch(destDir, function() { + fs.readdir(destDir, function (err, files) { + if (err) { + assert.fail(err); + } else { + assert.deepEqual(files, ['index.css']); + } + rimraf.sync(destDir); bin.kill(); - var files = fs.readdirSync(destDir); - assert.deepEqual(files, ['index.css']); - rimraf(destDir, done); - }, 200); - }, 500); - }); + w.close(); + }); + }); + + bin.stdout.setEncoding('utf8'); + bin.stdout.once('data', function() { + assert.fail('should not emit console output when watching a directory'); + }); + bin.stderr.once('data', function () { + touch(srcFile); + }); + bin.on('error', assert.fail); + bin.on('exit', done); + }).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 ]); - setTimeout(function () { - fs.appendFileSync(srcFile, 'body{background:white}\n'); - setTimeout(function () { + var w = fs.watch(destDir, function() { + fs.readdir(destDir, function (err, files) { + if (err) { + assert.fail(err); + } else if (files.length === 2) { + assert.deepEqual(files, ['foo.css', 'index.css']); + } + rimraf.sync(destDir); + w.close(); bin.kill(); - var files = fs.readdirSync(destDir); - assert.deepEqual(files, ['foo.css', 'index.css']); - rimraf(destDir, done); - }, 200); - }, 500); + }); + }); + + bin.stdout.setEncoding('utf8'); + bin.stdout.once('data', function() { + assert.fail('should not emit console output when watching a directory'); + }); + bin.stderr.once('data', function () { + touch(srcFile); + }); + bin.on('error', assert.fail); + bin.on('exit', done); }); }); @@ -408,7 +454,10 @@ describe('cli', function() { var destMap = fixture('source-map/index.map'); var expectedCss = read(fixture('source-map/expected.css'), 'utf8').trim().replace(/\r\n/g, '\n'); var expectedMap = read(fixture('source-map/expected.map'), 'utf8').trim().replace(/\r\n/g, '\n'); - var bin = spawn(cli, [src, '--output', path.dirname(destCss), '--source-map', destMap]); + var bin = spawn(cli, [ + src, '--output', path.dirname(destCss), + '--source-map', destMap + ]); bin.once('close', function() { assert.equal(read(destCss, 'utf8').trim(), expectedCss);