/* eslint-disable max-params */
'use strict';
const {
writeFile,
mkdirSync,
existsSync,
rmdirSync,
rmSync = rmdirSync
} = require('fs');
const _ = require('lodash');
const path = require('path');
const sinon = require('sinon');
const adapter = require('./adapter');
/**
* Collection of unit test helpers. (mostly related to Mocha syntax)
* @class YeomanTest
*/
class YeomanTest {}
/**
* 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
*/
YeomanTest.prototype.setUpTestDirectory = function (dir) {
return (done) => {
this.testDirectory(dir, () => {
this.gruntfile({dummy: true}, done);
});
};
};
/**
*
* 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>'
* }
* }));
*
*/
YeomanTest.prototype.gruntfile = function (options, done) {
let config = 'grunt.initConfig(' + JSON.stringify(options, null, 2) + ');';
config = config
.split('\n')
.map(function (line) {
return ' ' + line;
})
.join('\n');
const out = ['module.exports = function (grunt) {', config, '};'];
writeFile('Gruntfile.js', out.join('\n'), done);
};
/**
* 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.');
* });
*/
YeomanTest.prototype.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('/');
try {
if (existsSync(dir)) {
rmSync(dir, {recursive: true});
}
mkdirSync(dir, {recursive: true});
process.chdir(dir);
cb();
} catch (error) {
return cb(error);
}
};
/**
* Answer prompt questions for the passed-in generator
* @param {Generator|Environment} generator - a Yeoman generator or environment
* @param {Object} answers - an object where keys are the
* generators prompt names and values are the answers to
* the prompt questions
* @param {Function|Object} options - Options or callback
* @example
* mockPrompt(angular, {'bootstrap': 'Y', 'compassBoostrap': 'Y'});
*/
YeomanTest.prototype.mockPrompt = function (
envOrGenerator,
mockedAnswers,
options
) {
envOrGenerator = envOrGenerator.env || envOrGenerator;
const {promptModule} = envOrGenerator.adapter;
const {DummyPrompt} = adapter;
Object.keys(promptModule.prompts).forEach(function (name) {
promptModule.registerPrompt(
name,
class CustomDummyPrompt extends DummyPrompt {
constructor(question, rl, answers) {
super(mockedAnswers, options, question, rl, answers);
}
}
);
});
};
/**
* Restore defaults prompts on a generator.
* @param {Generator|Environment} generator or environment
*/
YeomanTest.prototype.restorePrompt = function (envOrGenerator) {
envOrGenerator = envOrGenerator.env || envOrGenerator;
envOrGenerator.adapter.promptModule.restoreDefaultPrompts();
};
/**
* Provide mocked values to the config
* @param {Generator} generator - a Yeoman generator
* @param {Object} localConfig - localConfig - should look just like if called config.getAll()
*/
YeomanTest.prototype.mockLocalConfig = function (generator, localConfig) {
generator.config.defaults(localConfig);
};
/**
* Create a mocked generator
*/
YeomanTest.prototype.createMockedGenerator = (
Generator = class MockedGenerator extends require('yeoman-generator') {}
) => {
const generator = sinon.spy(Generator);
['run', 'queueTasks', 'runWithOptions', 'queueOwnTasks'].forEach(
(methodName) => {
if (Generator.prototype[methodName]) {
generator.prototype[methodName] = sinon.stub();
}
}
);
return generator;
};
/**
* Create a simple, dummy generator
*/
YeomanTest.prototype.createDummyGenerator = (
Generator = require('yeoman-generator')
) =>
class extends Generator {
test() {
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
* @param {Boolean} [localConfigOnly=true] - passes localConfigOnly to the generators
* @example
* var deps = ['../../app',
* '../../common',
* '../../controller',
* '../../main',
* [createDummyGenerator(), 'testacular:app']
* ];
* var angular = createGenerator('angular:app', deps);
*/
YeomanTest.prototype.createGenerator = function (
name,
dependencies,
args,
options,
localConfigOnly = true
) {
const env = this.createEnv([], {sharedOptions: {localConfigOnly}});
this.registerDependencies(env, dependencies);
return env.create(name, {arguments: args, options});
};
/**
* 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
*/
YeomanTest.prototype.registerDependencies = function (env, dependencies) {
dependencies.forEach(function (dependency) {
if (Array.isArray(dependency)) {
env.registerStub(...dependency);
} else {
env.register(dependency);
}
});
};
/**
* Shortcut to the Environment's createEnv.
*
* @param {...any} args - environment constructor arguments.
* @returns {Object} environment instance
*
* Use to test with specific Environment version:
* let createEnv;
* before(() => {
* createEnv = stub(helper, 'createEnv').callsFake(Environment.creatEnv);
* });
* after(() => {
* createEnv.restore();
* });
*/
YeomanTest.prototype.createEnv = (...args) => {
return require('yeoman-environment').createEnv(...args);
};
/**
* Creates a test environment.
*
* @param {Function} envContructor - environment constructor method.
* @param {Object} [options] - Options to be passed to the environment
* @returns {Object} environment instance
* const env = createTestEnv(require('yeoman-environment').createEnv);
*/
YeomanTest.prototype.createTestEnv = function (
envContructor = this.createEnv,
options = {localConfigOnly: true}
) {
const envOptions = _.cloneDeep(this.environmentOptions || {});
if (typeof options === 'boolean') {
options = {
newErrorHandler: true,
...envOptions,
sharedOptions: {
localConfigOnly: options,
...envOptions.sharedOptions
}
};
} else {
options = {
newErrorHandler: true,
...envOptions,
...options
};
options.sharedOptions = {
localConfigOnly: true,
...envOptions.sharedOptions,
...options.sharedOptions
};
}
return envContructor([], options, new adapter.TestAdapter());
};
/**
* Run the provided Generator
* @param {String|Function} GeneratorOrNamespace - Generator constructor or namespace
* @return {RunContext}
*/
YeomanTest.prototype.run = function (
GeneratorOrNamespace,
settings,
envOptions
) {
const RunContext = require('./run-context');
const contextSettings = _.cloneDeep(this.settings || {});
const generatorOptions = _.cloneDeep(this.generatorOptions || {});
return new RunContext(
GeneratorOrNamespace,
{...contextSettings, ...settings},
envOptions,
this
).withOptions(generatorOptions);
};
/**
* Prepare a run context
* @param {String|Function} GeneratorOrNamespace - Generator constructor or namespace
* @return {RunContext}
*/
YeomanTest.prototype.create = function (
GeneratorOrNamespace,
settings,
envOptions
) {
return this.run(
GeneratorOrNamespace,
{...settings, runEnvironment: true},
envOptions
);
};
const helpers = new YeomanTest();
helpers.YeomanTest = YeomanTest;
module.exports = helpers;
helpers.createHelpers = (options) => {
const helpers = new YeomanTest();
Object.assign(helpers, options);
return helpers;
};