UNPKG

yeoman-test

Version:

Test utilities for Yeoman generators

287 lines (286 loc) 10.1 kB
import { mkdirSync, existsSync, rmSync } from 'node:fs'; import { resolve } from 'node:path'; import process from 'node:process'; import { cloneDeep } from 'lodash-es'; import { spy as sinonSpy, stub as sinonStub } from 'sinon'; import { TestAdapter } from './adapter.js'; import RunContext, { BasicRunContext } from './run-context.js'; import testContext from './test-context.js'; import { createEnv } from './default-environment.js'; let GeneratorImplementation; try { const GeneratorImport = await import('yeoman-generator'); GeneratorImplementation = GeneratorImport.default ?? GeneratorImport; } catch { } /** * Collection of unit test helpers. (mostly related to Mocha syntax) * @class YeomanTest */ export class YeomanTest { settings; environmentOptions; generatorOptions; adapterOptions; /** * @deprecated * Create a function that will clean up the test directory, * cd into it. Intended for use * as a callback for the mocha `before` hook. * * @param dir - path to the test directory * @returns mocha callback */ setUpTestDirectory(dir) { return () => { this.testDirectory(dir); }; } /** * @deprecated * Clean-up the test directory and cd into it. * Call given callback after entering the test directory. * @param dir - path to the test directory * @param cb - callback executed after setting working directory to dir * @example * testDirectory(path.join(__dirname, './temp'), function () { * fs.writeFileSync('testfile', 'Roses are red.'); * }); */ testDirectory(dir, cb) { if (!dir) { throw new Error('Missing directory'); } dir = 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); return cb?.(); } catch (error) { return cb?.(error); } } /** * @deprecated * Answer prompt questions for the passed-in generator * @param generator - a Yeoman generator or environment * @param answers - an object where keys are the * generators prompt names and values are the answers to * the prompt questions * @param options - Options or callback * @example * mockPrompt(angular, {'bootstrap': 'Y', 'compassBoostrap': 'Y'}); */ mockPrompt(envOrGenerator, mockedAnswers, options) { const environment = 'env' in envOrGenerator ? envOrGenerator.env : envOrGenerator; if (!environment.adapter) { throw new Error('environment is not an Environment instance'); } const testAdapter = environment.adapter; const { promptModule } = testAdapter; for (const name of Object.keys(promptModule.prompts)) { testAdapter.registerDummyPrompt(name, { ...options, mockedAnswers }); } } /** * @deprecated * Restore defaults prompts on a generator. * @param generator or environment */ restorePrompt(envOrGenerator) { const environment = envOrGenerator.env ?? envOrGenerator; environment.adapter.close(); } /** * @deprecated * Provide mocked values to the config * @param generator - a Yeoman generator * @param localConfig - localConfig - should look just like if called config.getAll() */ mockLocalConfig(generator, localConfig) { generator.config.defaults(localConfig); } /** * Create a mocked generator */ createMockedGenerator(GeneratorClass = GeneratorImplementation) { class MockedGenerator extends GeneratorClass { } const generator = sinonSpy(MockedGenerator); for (const methodName of ['run', 'queueTasks', 'runWithOptions', 'queueOwnTasks']) { Object.defineProperty(MockedGenerator.prototype, methodName, { value: sinonStub(), }); } return generator; } /** * Create a simple, dummy generator */ createDummyGenerator(Generator = GeneratorImplementation, contents = { test() { this.shouldRun = true; }, }) { class DummyGenerator extends Generator { constructor(...args) { const optIndex = Array.isArray(args[0]) ? 1 : 0; args[optIndex] = args[optIndex] ?? {}; const options = args[optIndex]; options.namespace = options.namespace ?? 'dummy'; options.resolved = options.resolved ?? 'dummy'; super(...args); } } for (const [propName, propValue] of Object.entries(contents)) { Object.defineProperty(DummyGenerator.prototype, propName, { value: propValue ?? Object.create(null), writable: true, }); } return DummyGenerator; } /** * Create a generator, using the given dependencies and controller arguments * Dependecies can be path (autodiscovery) or an array [{generator}, {name}] * * @param name - the name of the generator * @param dependencies - paths to the generators dependencies * @param args - arguments to the generator; * if String, will be split on spaces to create an Array * @param options - configuration for the generator * @param localConfigOnly - passes localConfigOnly to the generators * @example * var deps = ['../../app', * '../../common', * '../../controller', * '../../main', * [createDummyGenerator(), 'testacular:app'] * ]; * var angular = createGenerator('angular:app', deps); */ async createGenerator(name, options = {}) { const { dependencies = [], localConfigOnly = true, ...instantiateOptions } = options; const env = await this.createEnv({ sharedOptions: { localConfigOnly } }); for (const dependency of dependencies) { if (typeof dependency === 'string') { env.register(dependency); } else { env.register(...dependency); } } return env.create(name, instantiateOptions); } /** * 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(); * }); */ async createEnv(options) { return createEnv(options); } /** * Creates a test environment. * * @param {Function} - environment constructor method. * @param {Object} - Options to be passed to the environment * const env = createTestEnv(require('yeoman-environment').createEnv); */ async createTestEnv(envContructor = this.createEnv, options = { localConfigOnly: true }) { let envOptions = cloneDeep(this.environmentOptions ?? {}); if (typeof options === 'boolean') { envOptions = { newErrorHandler: true, ...envOptions, sharedOptions: { localConfigOnly: options, ...envOptions.sharedOptions, }, }; } else { envOptions.sharedOptions = { localConfigOnly: true, ...envOptions.sharedOptions, ...options.sharedOptions, }; envOptions = { newErrorHandler: true, ...envOptions, ...options, }; } return envContructor({ adapter: this.createTestAdapter(), ...envOptions }); } /** * Creates a TestAdapter using helpers default options. */ createTestAdapter(options) { return new TestAdapter({ ...this.adapterOptions, ...options }); } /** * Get RunContext type * @return {RunContext} */ getRunContextType() { return RunContext; } /** * Run the provided Generator * @param GeneratorOrNamespace - Generator constructor or namespace */ run(GeneratorOrNamespace, settings, envOptions) { const contextSettings = cloneDeep(this.settings ?? {}); const generatorOptions = cloneDeep(this.generatorOptions ?? {}); const RunContext = this.getRunContextType(); const runContext = new RunContext(GeneratorOrNamespace, { ...contextSettings, ...settings }, envOptions, this).withOptions(generatorOptions); if (settings?.autoCleanup !== false) { testContext.startNewContext(runContext); } return runContext; } /** * Prepare a run context * @param {String|Function} GeneratorOrNamespace - Generator constructor or namespace * @return {RunContext} */ create(GeneratorOrNamespace, settings, envOptions) { return this.run(GeneratorOrNamespace, settings, envOptions); } /** * Prepare temporary dir without generator support. * Generator and environment will be undefined. */ prepareTemporaryDir(settings) { const context = new BasicRunContext(undefined, settings); if (settings?.autoCleanup !== false) { testContext.startNewContext(context); } return context; } } const defaultHelpers = new YeomanTest(); export default defaultHelpers; export const createHelpers = (options) => { const helpers = new YeomanTest(); Object.assign(helpers, options); return helpers; };