diff --git a/lib/test/helpers.js b/lib/test/helpers.js index 23c967e5..cd262cf5 100644 --- a/lib/test/helpers.js +++ b/lib/test/helpers.js @@ -244,13 +244,8 @@ exports.createDummyGenerator = function () { exports.createGenerator = function (name, dependencies, args, options) { var env = generators(); - dependencies.forEach(function (d) { - if (d instanceof Array) { - env.registerStub(d[0], d[1]); - } else { - env.register(d); - } - }); + + this.registerDependencies(env, dependencies); var generator = env.create(name, { arguments: args, options: options }); @@ -267,6 +262,23 @@ exports.createGenerator = function (name, dependencies, args, options) { return generator; }; +/** + * Register a list of dependent generators into the provided env. + * Dependecies can be path (autodiscovery) or an array [, ] + * + * @param {Array} dependencies - paths to the generators dependencies + */ + +exports.registerDependencies = function (env, dependencies) { + dependencies.forEach(function (d) { + if (_.isArray(d)) { + env.registerStub(d[0], d[1]); + } else { + env.register(d); + } + }); +}; + /** * Run the provided Generator * @param {String|Function} Generator - Generator constructor or namespace diff --git a/lib/test/run-context.js b/lib/test/run-context.js index 98178fe2..f131ba58 100644 --- a/lib/test/run-context.js +++ b/lib/test/run-context.js @@ -1,4 +1,6 @@ 'use strict'; +var path = require('path'); +var assert = require('assert'); var _ = require('lodash'); var yeoman = require('../..'); var util = require('util'); @@ -16,43 +18,28 @@ var helpers = require('./helpers'); */ var RunContext = module.exports = function RunContext(Generator) { - var namespace; - this._asyncHolds = 0; this.runned = false; - this.env = yeoman(); this.args = []; this.options = {}; + this._dependencies = []; + this.Generator = Generator; - if (_.isString(Generator)) { - namespace = this.env.namespace(Generator); - this.env.register(Generator); - } else { - namespace = 'gen:test'; - this.env.registerStub(Generator, namespace); - } - - this.generator = this.env.create(namespace); - - // Mock prompt by default - this.withPrompt(); - - setTimeout(this._onReady.bind(this), 0); + setTimeout(this._run.bind(this), 0); }; util.inherits(RunContext, EventEmitter); /** * Hold the execution until the returned callback is triggered - * @private * @return {Function} Callback to notify the normal execution can resume */ -RunContext.prototype._holdExec = function () { +RunContext.prototype.async = function () { this._asyncHolds++; return function () { this._asyncHolds--; - this._onReady(); + this._run(); }.bind(this); }; @@ -61,14 +48,34 @@ RunContext.prototype._holdExec = function () { * @private */ -RunContext.prototype._onReady = function () { +RunContext.prototype._run = function () { if (this._asyncHolds !== 0 || this.runned) return; this.runned = true; + + var namespace; + this.env = yeoman(); + + helpers.registerDependencies(this.env, this._dependencies); + + if (_.isString(this.Generator)) { + namespace = this.env.namespace(this.Generator); + this.env.register(this.Generator); + } else { + namespace = 'gen:test'; + this.env.registerStub(this.Generator, namespace); + } + + this.generator = this.env.create(namespace); + helpers.mockPrompt(this.generator, this._answers); + this.generator.args = this.args; this.generator.arguments = this.args; this.generator.options = _.extend({ 'skip-install': true }, this.options); + + this.generator.once('end', this.emit.bind(this, 'end')); + this.emit('ready', this.generator); this.generator.run(); }; @@ -79,9 +86,10 @@ RunContext.prototype._onReady = function () { * @return {this} */ -RunContext.prototype.inDir = function (dirPath) { - var release = this._holdExec(); - helpers.testDirectory(dirPath, release); +RunContext.prototype.inDir = function (dirPath, cb) { + var release = this.async(); + var callBackThenRelease = _.compose(release, (cb || _.noop).bind(this, path.resolve(dirPath))); + helpers.testDirectory(dirPath, callBackThenRelease); return this; }; @@ -114,17 +122,45 @@ RunContext.prototype.withOptions = function (options) { */ RunContext.prototype.withPrompt = function (answers) { - helpers.mockPrompt(this.generator, answers); + this._answers = answers; + return this; +}; + +/** + * Provide dependent generators + * @param {Array} dependencies - paths to the generators dependencies + * @return {this} + * @example + * var deps = ['../../common', + * '../../controller', + * '../../main', + * [helpers.createDummyGenerator(), 'testacular:app'] + * ]; + * var angular = new RunContext('../../app'); + * angular.withGenerator(deps); + * angular.withPrompt({ + * compass: true, + * bootstrap: true + * }); + * angular.onEnd(function () { + * // assert something + * }); + */ + +RunContext.prototype.withGenerators = function (dependencies) { + assert(_.isArray(dependencies), 'dependencies should be an array'); + this._dependencies = this._dependencies.concat(dependencies); return this; }; /** * Add a callback to be called after the generator has ran + * @deprecated `onEnd` is deprecated, use .on('end', onEndHandler) instead. * @param {Function} callback * @return {this} */ RunContext.prototype.onEnd = function (cb) { - this.generator.once('end', cb); - return this; + console.log('`onEnd` is deprecated, use .on(\'end\', onEndHandler) instead.'); + return this.on('end', cb); }; diff --git a/test/helpers.js b/test/helpers.js index bfde6460..57a47ada 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -21,6 +21,18 @@ describe('yeoman.test', function () { util.inherits(this.StubGenerator, yeoman.Base); }); + describe('.registerDependencies()', function () { + it('accepts dependency as a path', function () { + helpers.registerDependencies(env, ['./custom-generator-simple']); + assert(env.get('simple:app')); + }); + + it('accepts dependency as array of [, ]', function () { + helpers.registerDependencies(env, [[this.StubGenerator, 'stub:app']]); + assert(env.get('stub:app')); + }); + }); + describe('.createGenerator()', function () { it('create a new generator', function () { var generator = helpers.createGenerator('unicorn:app', [ diff --git a/test/run-context.js b/test/run-context.js index 40d1d687..9c40b367 100644 --- a/test/run-context.js +++ b/test/run-context.js @@ -18,30 +18,36 @@ describe('RunContext', function () { }); describe('constructor', function () { - it('accept path parameter', function () { + it('accept path parameter', function (done) { var ctx = new RunContext(path.join(__dirname, './fixtures/custom-generator-simple')); - assert(ctx.env.get('simple:app')); + ctx.on('ready', function () { + assert(ctx.env.get('simple:app')); + done(); + }); }); - it('accept generator constructor parameter (and assign gen:test as namespace)', function () { - assert(this.ctx.env.get('gen:test')); + it('accept generator constructor parameter (and assign gen:test as namespace)', function (done) { + this.ctx.on('ready', function () { + assert(this.ctx.env.get('gen:test')); + done(); + }.bind(this)); }); it('run the generator asynchronously', function (done) { assert(this.execSpy.notCalled); - this.ctx.onEnd(function () { - assert(this.execSpy.calledOnce); + this.ctx.on('end', function () { + sinon.assert.calledOnce(this.execSpy); done(); }.bind(this)); }); it('only run a generator once', function (done) { - this.ctx.onEnd(function () { - assert(this.execSpy.calledOnce); + this.ctx.on('end', function () { + sinon.assert.calledOnce(this.execSpy); done(); }.bind(this)); - this.ctx._onReady(); - this.ctx._onReady(); + this.ctx._run(); + this.ctx._run(); }); }); @@ -61,12 +67,40 @@ describe('RunContext', function () { it('is chainable', function () { assert.equal(this.ctx.inDir(this.tmp), this.ctx); }); + + it('accepts optional `cb` to be invoked with resolved `dir`', function (done) { + var ctx = new RunContext(this.Dummy); + var cb = sinon.spy(function () { + sinon.assert.calledOnce(cb); + sinon.assert.calledOn(cb, ctx); + sinon.assert.calledWith(cb, path.resolve(this.tmp)); + done(); + }.bind(this)); + ctx.inDir(this.tmp, cb); + }); + + it('optional `cb` can use `this.async()` to delay execution', function (done) { + var ctx = new RunContext(this.Dummy); + var delayed = false; + var cb = sinon.spy(function () { + var release = this.async(); + setTimeout(function () { + delayed = true; + release(); + }.bind(this), 1); + }); + ctx.inDir(this.tmp, cb) + .on('ready', function () { + assert(delayed); + done(); + }); + }); }); describe('#withArguments()', function () { it('provide arguments to the generator when passed as Array', function (done) { this.ctx.withArguments(['one', 'two']); - this.ctx.onEnd(function () { + this.ctx.on('end', function () { assert.deepEqual(this.execSpy.firstCall.thisValue.arguments, ['one', 'two']); done(); }.bind(this)); @@ -74,7 +108,7 @@ describe('RunContext', function () { it('provide arguments to the generator when passed as String', function (done) { this.ctx.withArguments('foo bar'); - this.ctx.onEnd(function () { + this.ctx.on('end', function () { assert.deepEqual(this.execSpy.firstCall.thisValue.arguments, ['foo', 'bar']); done(); }.bind(this)); @@ -88,14 +122,14 @@ describe('RunContext', function () { describe('#withOptions()', function () { it('provide options to the generator', function (done) { this.ctx.withOptions({ foo: 'bar' }); - this.ctx.onEnd(function () { + this.ctx.on('end', function () { assert.equal(this.execSpy.firstCall.thisValue.options.foo, 'bar'); done(); }.bind(this)); }); it('set skip-install by default', function (done) { - this.ctx.onEnd(function () { + this.ctx.on('end', function () { assert.equal(this.execSpy.firstCall.thisValue.options['skip-install'], true); done(); }.bind(this)); @@ -103,7 +137,7 @@ describe('RunContext', function () { it('allow skip-install to be overriden', function (done) { this.ctx.withOptions({ 'skip-install': false }); - this.ctx.onEnd(function () { + this.ctx.on('end', function () { assert.equal(this.execSpy.firstCall.thisValue.options['skip-install'], false); done(); }.bind(this)); @@ -148,17 +182,35 @@ describe('RunContext', function () { }); }); - describe('#onEnd()', function () { - it('is called after the generator ran', function (done) { - assert(this.execSpy.notCalled); - this.ctx.onEnd(function () { - assert(this.execSpy.calledOnce); + describe('#withGenerators()', function () { + it('should register paths', function (done) { + this.ctx.withGenerators([ + path.join(__dirname, './fixtures/custom-generator-simple') + ]).on('ready', function () { + assert(this.ctx.env.get('simple:app')); done(); }.bind(this)); }); - it('is chainable', function () { - assert.equal(this.ctx.onEnd(function () {}), this.ctx); + it('should register mocked generator', function (done) { + this.ctx.withGenerators([ + [helpers.createDummyGenerator(), 'dummy:gen'] + ]).on('ready', function () { + assert(this.ctx.env.get('dummy:gen')); + done(); + }.bind(this)); + }); + + it('should accumulate generators from multiple calls', function (done) { + this.ctx.withGenerators([ + path.join(__dirname, './fixtures/custom-generator-simple') + ]).withGenerators([ + [helpers.createDummyGenerator(), 'dummy:gen'] + ]).on('ready', function () { + assert(this.ctx.env.get('dummy:gen')); + assert(this.ctx.env.get('simple:app')); + done(); + }.bind(this)); }); }); });