Skip to content

Commit

Permalink
Add more watcher events (broccolijs#398)
Browse files Browse the repository at this point in the history
  • Loading branch information
oligriffiths authored and thoov committed May 18, 2019
1 parent d18f1d0 commit e74e0f0
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 41 deletions.
58 changes: 40 additions & 18 deletions lib/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@ module.exports = class Watcher extends EventEmitter {
constructor(builder, options) {
super();
this.options = options || {};
if (this.options.debounce == null) this.options.debounce = 100;
if (this.options.debounce == null) {
this.options.debounce = 100;
}
this.builder = builder;
this.watcherAdapter = new WatcherAdapter(this.options.saneOptions);
this.watcherAdapter =
this.options.watcherAdapter || new WatcherAdapter(this.options.saneOptions);
this.currentBuild = null;
this._rebuildScheduled = false;
this._ready = false;
this._quitting = false;
this._quittingPromise = null;
this._lifetimeDeferred = null;
}

start() {
if (this._lifetimeDeferred != null)
if (this._lifetimeDeferred != null) {
throw new Error('Watcher.prototype.start() must not be called more than once');
}

let lifetime = (this._lifetimeDeferred = {});
lifetime.promise = new Promise((resolve, reject) => {
lifetime.resolve = resolve;
Expand Down Expand Up @@ -53,7 +58,7 @@ module.exports = class Watcher extends EventEmitter {
return this._lifetimeDeferred.promise;
}

_change() {
_change(event, filePath, root) {
if (!this._ready) {
logger.debug('change', 'ignored: before ready');
return;
Expand All @@ -62,13 +67,19 @@ module.exports = class Watcher extends EventEmitter {
logger.debug('change', 'ignored: rebuild scheduled already');
return;
}
logger.debug('change');
logger.debug('change', event, filePath, root);
this.emit('change', event, filePath, root);

this._rebuildScheduled = true;

// Wait for current build, and ignore build failure
Promise.resolve(this.currentBuild)
return Promise.resolve(this.currentBuild)
.catch(() => {})
.then(() => {
if (this._quitting) return;
if (this._quitting) {
return;
}

const buildPromise = new Promise(resolve => {
logger.debug('debounce');
this.emit('debounce');
Expand All @@ -92,11 +103,11 @@ module.exports = class Watcher extends EventEmitter {
// currentBuild, their callback will come after our events have
// triggered, because we registered our callback first.
buildPromise.then(
() => {
results => {
const hrend = process.hrtime(hrstart);
logger.debug('Build execution time: %ds %dms', hrend[0], Math.round(hrend[1] / 1e6));
logger.debug('buildSuccess');
this.emit('buildSuccess');
this.emit('buildSuccess', results);
},
err => {
logger.debug('buildFailure');
Expand All @@ -107,34 +118,45 @@ module.exports = class Watcher extends EventEmitter {
}

_error(err) {
if (this._quittingPromise) {
logger.debug('error', 'ignored: already quitting');
return this._quittingPromise;
}

logger.debug('error', err);
if (this._quitting) return;
this._quit()
this.emit('error', err);
return this._quit()
.catch(() => {})
.then(() => this._lifetimeDeferred.reject(err));
}

quit() {
if (this._quitting) {
if (this._quittingPromise) {
logger.debug('quit', 'ignored: already quitting');
return;
return this._quittingPromise;
}
this._quit().then(

return this._quit().then(
() => this._lifetimeDeferred.resolve(),
err => this._lifetimeDeferred.reject(err)
);
}

_quit() {
this._quitting = true;
logger.debug('quitStart');
this.emit('quitStart');

return promiseFinally(
this._quittingPromise = promiseFinally(
promiseFinally(Promise.resolve().then(() => this.watcherAdapter.quit()), () => {
// Wait for current build, and ignore build failure
return Promise.resolve(this.currentBuild).catch(() => {});
}),
() => logger.debug('quitEnd')
() => {
logger.debug('quitEnd');
this.emit('quitEnd');
}
);

return this._quittingPromise;
}
};
2 changes: 1 addition & 1 deletion lib/watcher_adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function bindFileEvent(adapter, watcher, node, event) {
logger.debug(event, root + '/' + filepath);
logger.debug(`revise called on node [${node.id}]`);
node.revise();
adapter.emit('change');
adapter.emit('change', event, filepath, root);
});
}

Expand Down
45 changes: 23 additions & 22 deletions test/watch_adapter_test.js → test/watcher_adapter_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const WatcherAdapter = require('../lib/watcher_adapter');
const bindFileEvent = WatcherAdapter.bindFileEvent;
const fs = require('fs');

const chai = require('chai');
const expect = chai.expect;
const sinonChai = require('sinon-chai');
Expand Down Expand Up @@ -31,37 +30,38 @@ describe('WatcherAdapter', function() {
};

it('works', function() {
const trigger = sinon.spy(adapter, 'emit');
const emitHandler = sinon.spy();
adapter.emit = emitHandler;
const on = sinon.spy(watcher, 'on');
const revise = sinon.spy(watchedNode, 'revise');

expect(on).to.have.not.been.called;
expect(trigger).to.have.not.been.called;
expect(emitHandler).to.have.not.been.called;
expect(revise).to.have.not.been.called;

bindFileEvent(adapter, watcher, watchedNode, 'change');

expect(on).to.have.been.calledOnce;
expect(trigger).to.have.been.calledOnce;
expect(emitHandler).to.have.been.calledOnce;
expect(revise).to.have.been.calledOnce;
expect(on).to.have.been.calledWith('change');
expect(trigger).to.have.been.calledWith('change');
expect(emitHandler).to.have.been.calledWith('change', 'change');

bindFileEvent(adapter, watcher, watchedNode, 'add');

expect(on).to.have.been.calledTwice;
expect(trigger).to.have.been.calledTwice;
expect(emitHandler).to.have.been.calledTwice;
expect(revise).to.have.been.calledTwice;
expect(on).to.have.been.calledWith('add');
expect(trigger).to.have.been.calledWith('change');
expect(emitHandler).to.have.been.calledWith('change', 'add');

bindFileEvent(adapter, watcher, watchedNode, 'remove');

expect(on).to.have.been.calledThrice;
expect(trigger).to.have.been.calledThrice;
expect(emitHandler).to.have.been.calledThrice;
expect(revise).to.have.been.calledThrice;
expect(on).to.have.been.calledWith('remove');
expect(trigger).to.have.been.calledWith('change');
expect(emitHandler).to.have.been.calledWith('change', 'remove');
});
});

Expand Down Expand Up @@ -98,8 +98,9 @@ describe('WatcherAdapter', function() {
describe('watch', function() {
this.timeout(20000);

const FIXTURE_BASIC = __dirname + '/fixtures/basic';
const FIXTURE_PROJECT = __dirname + '/fixtures/project';
const isWin = process.platform === 'win32';
const FIXTURE_BASIC = __dirname + (isWin ? '\\fixtures\\basic' : '/fixtures/basic');
const FIXTURE_PROJECT = __dirname + (isWin ? '\\fixtures\\project' : '/fixtures/project');
let adapter;

const watchedNodeBasic = {
Expand Down Expand Up @@ -146,10 +147,10 @@ describe('WatcherAdapter', function() {

it('actually works !!', function() {
adapter = new WatcherAdapter();
const changeHandler = sinon.spy();
adapter.on('change', changeHandler);

let trigger = sinon.spy(adapter, 'emit');

expect(trigger).to.have.callCount(0);
expect(changeHandler).to.have.callCount(0);

expect(adapter.watchers.length).to.eql(0);

Expand All @@ -160,15 +161,15 @@ describe('WatcherAdapter', function() {
return watching.then(function() {
expect(arguments.length).to.eql(1);

expect(trigger).to.have.callCount(0);
expect(changeHandler).to.have.callCount(0);
fs.utimesSync(FIXTURE_BASIC + '/foo.txt', new Date(), new Date());
fs.utimesSync(FIXTURE_PROJECT + '/Brocfile.js', new Date(), new Date());

return spin(() => expect(trigger).to.have.callCount(1), 10000).then(() => {
expect(trigger).to.have.been.calledWith('change');
return spin(() => expect(changeHandler).to.have.callCount(1), 10000).then(() => {
expect(changeHandler).to.have.been.calledWith('change', 'foo.txt', FIXTURE_BASIC);

// reset the spy
trigger.resetHistory();
changeHandler.resetHistory();

// this time also watch the FIXTURE_PROJECT
let watching = adapter.watch([watchedNodeProject]);
Expand All @@ -180,12 +181,12 @@ describe('WatcherAdapter', function() {
fs.utimesSync(FIXTURE_BASIC + '/foo.txt', new Date(), new Date());
fs.utimesSync(FIXTURE_PROJECT + '/Brocfile.js', new Date(), new Date());

return spin(() => expect(trigger).to.have.callCount(2), 10000)
return spin(() => expect(changeHandler).to.have.callCount(2), 10000)
.then(() => {
expect(trigger).to.have.been.calledWith('change');
expect(changeHandler).to.have.been.calledWith('change');
})
.then(() => {
trigger.resetHistory();
changeHandler.resetHistory();

fs.utimesSync(FIXTURE_BASIC + '/foo.txt', new Date(), new Date());
fs.utimesSync(FIXTURE_PROJECT + '/Brocfile.js', new Date(), new Date());
Expand All @@ -198,7 +199,7 @@ describe('WatcherAdapter', function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
expect(trigger).to.have.callCount(0);
expect(changeHandler).to.have.callCount(0);
resolve();
} catch (e) {
reject(e);
Expand Down
Loading

0 comments on commit e74e0f0

Please sign in to comment.