UNPKG

yeoman-generator

Version:

Rails-inspired generator system that provides scaffolding for your apps

295 lines (251 loc) 8.11 kB
/** * Collection of unit test helpers. (mostly related to Mocha syntax) * @module test/helpers */ 'use strict'; var fs = require('fs'); var path = require('path'); var _ = require('lodash'); var rimraf = require('rimraf'); var mkdirp = require('mkdirp'); var assert = require('./assert'); var generators = require('../..'); var RunContext = require('../test/run-context'); exports.decorated = []; /** * Create a function that will clean up the test directory, * cd into it, and create a dummy gruntfile inside. Intended for use * as a callback for the mocha `before` hook. * * @param {String} dir - path to the test directory * @returns {Function} mocha callback */ exports.setUpTestDirectory = function before(dir) { return function (done) { exports.testDirectory(dir, function () { exports.gruntfile({ dummy: true }, done); }); }; }; /** * Create a function that will clean up the test directory, * cd into it, and create a dummy gruntfile inside. Intended for use * as a callback for the mocha `before` hook. * * @deprecated use helpers.setUpDirectory instead * @param {String} dir - path to the test directory * @returns {Function} mocha callback */ exports.before = function (dir) { console.log('before is deprecated. Use setUpTestDirectory instead'); return exports.setUpTestDirectory(dir); }; /** * Wrap a method with custom functionality. * * @param {Object} context - context to find the original method * @param {String} method - name of the method to wrap * @param {Function} replacement - executes before the original method * @param {Object} options - config settings */ exports.decorate = function decorate(context, method, replacement, options) { options = options || {}; replacement = replacement || function () {}; var naturalMethod = context[method]; exports.decorated.push({ context: context, method: method, naturalMethod: naturalMethod }); context[method] = function () { var rep = replacement.apply(context, arguments); if (!options.stub) { naturalMethod.apply(context, arguments); } return rep; }; }; /** * Override a method with custom functionality. * @param {Object} context - context to find the original method * @param {String} method - name of the method to wrap * @param {Function} replacement - executes before the original method */ exports.stub = function stub(context, method, replacement) { exports.decorate(context, method, replacement, { stub: true }); }; /** * Restore the original behavior of all decorated and stubbed methods */ exports.restore = function restore() { exports.decorated.forEach(function (dec) { dec.context[dec.method] = dec.naturalMethod; }); }; /** * * Generates a new Gruntfile.js in the current working directory based on * options hash passed in. * * @param {Object} options - Grunt configuration * @param {Function} done - callback to call on completion * @example * before(helpers.gruntfile({ * foo: { * bar: '<config.baz>' * } * })); * */ exports.gruntfile = function (options, done) { var config = 'grunt.initConfig(' + JSON.stringify(options, null, 2) + ');'; config = config.split('\n').map(function (line) { return ' ' + line; }).join('\n'); var out = [ 'module.exports = function (grunt) {', config, '};' ]; fs.writeFile('Gruntfile.js', out.join('\n'), done); }; // Façade assert module for backward compatibility exports.assertFile = assert.file; exports.assertNoFile = assert.noFile; exports.assertFiles = assert.files; exports.assertFileContent = assert.fileContent; exports.assertNoFileContent = assert.noFileContent; exports.assertTextEqual = assert.textEqual; exports.assertImplement = assert.implement; /** * Clean-up the test directory and cd into it. * Call given callback after entering the test directory. * @param {String} dir - path to the test directory * @param {Function} cb - callback executed after setting working directory to dir * @example * testDirectory(path.join(__dirname, './temp'), function () { * fs.writeFileSync('testfile', 'Roses are red.'); * ); */ exports.testDirectory = function (dir, cb) { if (!dir) { throw new Error('Missing directory'); } dir = path.resolve(dir); // Make sure we're not deleting CWD by moving to top level folder. As we `cd` in the // test dir after cleaning up, this shouldn't be perceivable. process.chdir('/'); rimraf(dir, function (err) { if (err) { return cb(err); } mkdirp.sync(dir); process.chdir(dir); cb(); }); }; /** * Answer prompt questions for the passed-in generator * @param {Generator} generator - a Yeoman generator * @param {Object} answers - an object where keys are the * generators prompt names and values are the answers to * the prompt questions * @example * mockPrompt(angular, {'bootstrap': 'Y', 'compassBoostrap': 'Y'}); */ exports.mockPrompt = function (generator, answers) { var origPrompt = generator.origPrompt || generator.prompt; answers = answers || {}; generator.prompt = function (prompts, done) { if (!_.isArray(prompts)) { prompts = [prompts]; } prompts.forEach(function (prompt) { if (!(prompt.name in answers)) { if (_.isFunction(prompt.default)) { answers[prompt.name] = prompt.default(answers); } else { answers[prompt.name] = prompt.default; } } if (_.isFunction(prompt.validate)) { var validation = prompt.validate(answers[prompt.name]); if (validation !== true) { if (generator.prompt.errors == null) { generator.prompt.errors = []; } generator.prompt.errors.push({ name: prompt.name, message: validation }); } } }); setTimeout(done.bind(null, answers), 0); }; generator.origPrompt = origPrompt; }; /** * Create a simple, dummy generator */ exports.createDummyGenerator = function () { return generators.Base.extend({ test: function () { this.shouldRun = true; } }); }; /** * Create a generator, using the given dependencies and controller arguments * Dependecies can be path (autodiscovery) or an array [<generator>, <name>] * * @param {String} name - the name of the generator * @param {Array} dependencies - paths to the generators dependencies * @param {Array|String} args - arguments to the generator; * if String, will be split on spaces to create an Array * @param {Object} options - configuration for the generator * @example * var deps = ['../../app', * '../../common', * '../../controller', * '../../main', * [createDummyGenerator(), 'testacular:app'] * ]; * var angular = createGenerator('angular:app', deps); */ exports.createGenerator = function (name, dependencies, args, options) { var env = generators(); this.registerDependencies(env, dependencies); var generator = env.create(name, { arguments: args, options: options }); generator.on('start', env.emit.bind(this, 'generators:start')); generator.on('start', env.emit.bind(this, name + ':start')); generator.on('method', function (method) { env.emit(name + ':' + method); }); generator.on('end', env.emit.bind(this, name + ':end')); generator.on('end', env.emit.bind(this, 'generators:end')); return generator; }; /** * Register a list of dependent generators into the provided env. * Dependecies can be path (autodiscovery) or an array [<generator>, <name>] * * @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 * @return {RunContext} */ exports.run = function (Generator) { return new RunContext(Generator); };