yeoman-test 6.2.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # yeoman-test
2
+
2
3
  [![NPM version][npm-image]][npm-url]
3
4
  [![NPM Test](https://github.com/yeoman/yeoman-test/workflows/NPM%20Test/badge.svg)](https://github.com/yeoman/yeoman-test/actions?query=workflow%3A%22NPM+Test%22)
4
5
  [![Integration Build](https://github.com/yeoman/yeoman-test/workflows/Integration%20Build/badge.svg)](https://github.com/yeoman/yeoman-test/actions?query=workflow%3A%22Integration+Build%22)
5
6
  [![Dependency Status][daviddm-image]][daviddm-url]
6
7
  [![Coverage percentage][coveralls-image]][coveralls-url]
8
+
7
9
  > Test utilities for Yeoman generators
8
10
 
9
11
  ## Installation
@@ -78,7 +80,6 @@ describe('generator test', () => {
78
80
 
79
81
  MIT © [The Yeoman Team](http://yeoman.io)
80
82
 
81
-
82
83
  [npm-image]: https://badge.fury.io/js/yeoman-test.svg
83
84
  [npm-url]: https://npmjs.org/package/yeoman-test
84
85
  [travis-image]: https://travis-ci.org/yeoman/yeoman-test.svg?branch=master
@@ -0,0 +1,24 @@
1
+ import inquirer from 'inquirer';
2
+ import type Generator from 'yeoman-generator';
3
+ import type Logger from 'yeoman-environment/lib/util/log.js';
4
+ export type DummyPromptOptions = {
5
+ callback?: (answers: Generator.Answers) => Generator.Answers;
6
+ throwOnMissingAnswer?: boolean;
7
+ };
8
+ export declare class DummyPrompt {
9
+ answers: Generator.Answers;
10
+ question: inquirer.Question;
11
+ callback: (answers: Generator.Answers) => Generator.Answers;
12
+ throwOnMissingAnswer: boolean;
13
+ constructor(question: inquirer.Question, _rl: any, answers: Generator.Answers, mockedAnswers?: Generator.Answers, options?: ((answers: Generator.Answers) => Generator.Answers) | DummyPromptOptions);
14
+ run(): Promise<inquirer.Answers>;
15
+ }
16
+ export declare class TestAdapter {
17
+ promptModule: inquirer.PromptModule;
18
+ diff: any;
19
+ log: typeof Logger;
20
+ constructor(mockedAnswers?: any);
21
+ prompt(questions: any, prefilledAnswers: any): Promise<inquirer.Answers> & {
22
+ ui: inquirer.ui.Prompt<inquirer.Answers>;
23
+ };
24
+ }
@@ -0,0 +1,104 @@
1
+ /* eslint-disable max-params */
2
+ import events from 'node:events';
3
+ import { PassThrough } from 'node:stream';
4
+ import inquirer from 'inquirer';
5
+ import { spy as sinonSpy, stub as sinonStub } from 'sinon';
6
+ export class DummyPrompt {
7
+ answers;
8
+ question;
9
+ callback;
10
+ throwOnMissingAnswer = false;
11
+ constructor(question, _rl, answers, mockedAnswers, options) {
12
+ this.answers = { ...answers, ...mockedAnswers };
13
+ this.question = question;
14
+ if (typeof options === 'function') {
15
+ this.callback = options;
16
+ }
17
+ else if (options) {
18
+ if (options.callback) {
19
+ this.callback = options.callback;
20
+ }
21
+ if (options.throwOnMissingAnswer !== undefined) {
22
+ this.throwOnMissingAnswer = options.throwOnMissingAnswer;
23
+ }
24
+ }
25
+ this.callback = this.callback || (answers => answers);
26
+ }
27
+ async run() {
28
+ let answer = this.answers[this.question.name];
29
+ let isSet;
30
+ switch (this.question.type) {
31
+ case 'list': {
32
+ // List prompt accepts any answer value including null
33
+ isSet = answer !== undefined;
34
+ break;
35
+ }
36
+ case 'confirm': {
37
+ // Ensure that we don't replace `false` with default `true`
38
+ isSet = answer || answer === false;
39
+ break;
40
+ }
41
+ default: {
42
+ // Other prompts treat all falsy values to default
43
+ isSet = Boolean(answer);
44
+ }
45
+ }
46
+ if (!isSet) {
47
+ if (answer === undefined && this.question.default === undefined) {
48
+ const missingAnswerMessage = `yeoman-test: question ${this.question.name} was asked but answer was not provided`;
49
+ console.warn(missingAnswerMessage);
50
+ if (this.throwOnMissingAnswer) {
51
+ throw new Error(missingAnswerMessage);
52
+ }
53
+ }
54
+ answer = this.question.default;
55
+ if (answer === undefined && this.question.type === 'confirm') {
56
+ answer = true;
57
+ }
58
+ }
59
+ return this.callback(answer);
60
+ }
61
+ }
62
+ export class TestAdapter {
63
+ promptModule;
64
+ diff;
65
+ log;
66
+ constructor(mockedAnswers) {
67
+ this.promptModule = inquirer.createPromptModule({
68
+ input: new PassThrough(),
69
+ output: new PassThrough(),
70
+ skipTTYChecks: true,
71
+ });
72
+ for (const promptName of Object.keys(this.promptModule.prompts)) {
73
+ this.promptModule.registerPrompt(promptName, class CustomDummyPrompt extends DummyPrompt {
74
+ constructor(question, rl, answers) {
75
+ super(question, rl, answers, mockedAnswers);
76
+ }
77
+ });
78
+ }
79
+ this.diff = sinonSpy();
80
+ this.log = sinonSpy();
81
+ Object.assign(this.log, events.EventEmitter.prototype);
82
+ // Make sure all log methods are defined
83
+ const adapterMethods = [
84
+ 'write',
85
+ 'writeln',
86
+ 'ok',
87
+ 'error',
88
+ 'skip',
89
+ 'force',
90
+ 'create',
91
+ 'invoke',
92
+ 'conflict',
93
+ 'identical',
94
+ 'info',
95
+ 'table',
96
+ ];
97
+ for (const methodName of adapterMethods) {
98
+ this.log[methodName] = sinonStub().returns(this.log);
99
+ }
100
+ }
101
+ prompt(questions, prefilledAnswers) {
102
+ return this.promptModule(questions, prefilledAnswers);
103
+ }
104
+ }
@@ -0,0 +1,151 @@
1
+ import YeomanGenerator from 'yeoman-generator';
2
+ import Environment from 'yeoman-environment';
3
+ import type { GeneratorOptions } from 'yeoman-generator';
4
+ import type { Options, createEnv } from 'yeoman-environment';
5
+ import type { SinonSpiedInstance } from 'sinon';
6
+ import { type DummyPromptOptions } from './adapter.js';
7
+ import RunContext from './run-context.js';
8
+ import type { RunContextSettings } from './run-context.js';
9
+ /**
10
+ * Dependencies can be path (autodiscovery) or an array [<generator>, <name>]
11
+ */
12
+ export type Dependency = string | Parameters<Environment['registerStub']>;
13
+ type GeneratorNew<GenParameter extends YeomanGenerator = YeomanGenerator> = new (...args: ConstructorParameters<typeof YeomanGenerator<GenParameter['options']>>) => YeomanGenerator<GenParameter['options']>;
14
+ type GeneratorBuilder<GenParameter extends YeomanGenerator = YeomanGenerator> = (...args: ConstructorParameters<typeof YeomanGenerator<GenParameter['options']>>) => YeomanGenerator<GenParameter['options']>;
15
+ export type GeneratorConstructor<GenParameter extends YeomanGenerator = YeomanGenerator> = GeneratorNew<GenParameter> | GeneratorBuilder<GenParameter>;
16
+ /**
17
+ * Collection of unit test helpers. (mostly related to Mocha syntax)
18
+ * @class YeomanTest
19
+ */
20
+ export declare class YeomanTest {
21
+ settings?: RunContextSettings;
22
+ environmentOptions?: Options;
23
+ generatorOptions?: GeneratorOptions;
24
+ /**
25
+ * @deprecated
26
+ * Create a function that will clean up the test directory,
27
+ * cd into it. Intended for use
28
+ * as a callback for the mocha `before` hook.
29
+ *
30
+ * @param dir - path to the test directory
31
+ * @returns mocha callback
32
+ */
33
+ setUpTestDirectory(dir: string): () => void;
34
+ /**
35
+ * @deprecated
36
+ * Clean-up the test directory and cd into it.
37
+ * Call given callback after entering the test directory.
38
+ * @param dir - path to the test directory
39
+ * @param cb - callback executed after setting working directory to dir
40
+ * @example
41
+ * testDirectory(path.join(__dirname, './temp'), function () {
42
+ * fs.writeFileSync('testfile', 'Roses are red.');
43
+ * });
44
+ */
45
+ testDirectory(dir: string, cb?: (error?: any) => unknown): unknown;
46
+ /**
47
+ * @deprecated
48
+ * Answer prompt questions for the passed-in generator
49
+ * @param generator - a Yeoman generator or environment
50
+ * @param answers - an object where keys are the
51
+ * generators prompt names and values are the answers to
52
+ * the prompt questions
53
+ * @param options - Options or callback
54
+ * @example
55
+ * mockPrompt(angular, {'bootstrap': 'Y', 'compassBoostrap': 'Y'});
56
+ */
57
+ mockPrompt(envOrGenerator: YeomanGenerator | Environment, mockedAnswers?: YeomanGenerator.Answers, options?: DummyPromptOptions): void;
58
+ /**
59
+ * @deprecated
60
+ * Restore defaults prompts on a generator.
61
+ * @param generator or environment
62
+ */
63
+ restorePrompt(envOrGenerator: YeomanGenerator | Environment): void;
64
+ /**
65
+ * @deprecated
66
+ * Provide mocked values to the config
67
+ * @param generator - a Yeoman generator
68
+ * @param localConfig - localConfig - should look just like if called config.getAll()
69
+ */
70
+ mockLocalConfig(generator: YeomanGenerator, localConfig: any): void;
71
+ /**
72
+ * Create a mocked generator
73
+ */
74
+ createMockedGenerator(GeneratorClass?: typeof YeomanGenerator<GeneratorOptions>): SinonSpiedInstance<typeof YeomanGenerator<GeneratorOptions>>;
75
+ /**
76
+ * Create a simple, dummy generator
77
+ */
78
+ createDummyGenerator<GenParameter extends YeomanGenerator = YeomanGenerator>(Generator?: typeof YeomanGenerator): typeof YeomanGenerator<GenParameter['options']>;
79
+ /**
80
+ * Create a generator, using the given dependencies and controller arguments
81
+ * Dependecies can be path (autodiscovery) or an array [{generator}, {name}]
82
+ *
83
+ * @param name - the name of the generator
84
+ * @param dependencies - paths to the generators dependencies
85
+ * @param args - arguments to the generator;
86
+ * if String, will be split on spaces to create an Array
87
+ * @param options - configuration for the generator
88
+ * @param localConfigOnly - passes localConfigOnly to the generators
89
+ * @example
90
+ * var deps = ['../../app',
91
+ * '../../common',
92
+ * '../../controller',
93
+ * '../../main',
94
+ * [createDummyGenerator(), 'testacular:app']
95
+ * ];
96
+ * var angular = createGenerator('angular:app', deps);
97
+ */
98
+ createGenerator<GeneratorType extends YeomanGenerator = YeomanGenerator>(name: string, dependencies: Dependency[], args?: string[], options?: YeomanGenerator.GeneratorOptions, localConfigOnly?: boolean): GeneratorType;
99
+ /**
100
+ * @deprecated
101
+ * Register a list of dependent generators into the provided env.
102
+ * Dependecies can be path (autodiscovery) or an array [{generator}, {name}]
103
+ *
104
+ * @param dependencies - paths to the generators dependencies
105
+ */
106
+ registerDependencies(env: Environment, dependencies: Dependency[]): void;
107
+ /**
108
+ * Shortcut to the Environment's createEnv.
109
+ *
110
+ * @param {...any} args - environment constructor arguments.
111
+ * @returns {Object} environment instance
112
+ *
113
+ * Use to test with specific Environment version:
114
+ * let createEnv;
115
+ * before(() => {
116
+ * createEnv = stub(helper, 'createEnv').callsFake(Environment.creatEnv);
117
+ * });
118
+ * after(() => {
119
+ * createEnv.restore();
120
+ * });
121
+ */
122
+ createEnv(...args: Parameters<typeof createEnv>): ReturnType<typeof createEnv>;
123
+ /**
124
+ * Creates a test environment.
125
+ *
126
+ * @param {Function} envContructor - environment constructor method.
127
+ * @param {Object} [options] - Options to be passed to the environment
128
+ * @returns {Object} environment instance
129
+ * const env = createTestEnv(require('yeoman-environment').createEnv);
130
+ */
131
+ createTestEnv(envContructor?: (args?: string | string[] | undefined, opts?: Environment.Options | undefined, adapter?: import("yeoman-environment/lib/adapter.js") | undefined) => Environment<Environment.Options>, options?: Environment.Options): Environment<Environment.Options>;
132
+ /**
133
+ * Get RunContext type
134
+ * @return {RunContext}
135
+ */
136
+ getRunContextType(): typeof RunContext;
137
+ /**
138
+ * Run the provided Generator
139
+ * @param GeneratorOrNamespace - Generator constructor or namespace
140
+ */
141
+ run<GeneratorType extends YeomanGenerator = YeomanGenerator>(GeneratorOrNamespace: string | GeneratorConstructor, settings?: RunContextSettings, envOptions?: Options): RunContext<GeneratorType>;
142
+ /**
143
+ * Prepare a run context
144
+ * @param {String|Function} GeneratorOrNamespace - Generator constructor or namespace
145
+ * @return {RunContext}
146
+ */
147
+ create<GeneratorType extends YeomanGenerator = YeomanGenerator>(GeneratorOrNamespace: string | GeneratorConstructor, settings?: RunContextSettings, envOptions?: Options): RunContext<GeneratorType>;
148
+ }
149
+ declare const _default: YeomanTest;
150
+ export default _default;
151
+ export declare const createHelpers: (options: any) => YeomanTest;
@@ -0,0 +1,258 @@
1
+ /* eslint-disable max-params */
2
+ import { mkdirSync, existsSync, rmSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import process from 'node:process';
5
+ import _ from 'lodash';
6
+ import { spy as sinonSpy, stub as sinonStub } from 'sinon';
7
+ import YeomanGenerator from 'yeoman-generator';
8
+ import Environment from 'yeoman-environment';
9
+ import { DummyPrompt, TestAdapter } from './adapter.js';
10
+ import RunContext from './run-context.js';
11
+ import testContext from './test-context.js';
12
+ /**
13
+ * Collection of unit test helpers. (mostly related to Mocha syntax)
14
+ * @class YeomanTest
15
+ */
16
+ export class YeomanTest {
17
+ settings;
18
+ environmentOptions;
19
+ generatorOptions;
20
+ /**
21
+ * @deprecated
22
+ * Create a function that will clean up the test directory,
23
+ * cd into it. Intended for use
24
+ * as a callback for the mocha `before` hook.
25
+ *
26
+ * @param dir - path to the test directory
27
+ * @returns mocha callback
28
+ */
29
+ setUpTestDirectory(dir) {
30
+ return () => {
31
+ this.testDirectory(dir);
32
+ };
33
+ }
34
+ /**
35
+ * @deprecated
36
+ * Clean-up the test directory and cd into it.
37
+ * Call given callback after entering the test directory.
38
+ * @param dir - path to the test directory
39
+ * @param cb - callback executed after setting working directory to dir
40
+ * @example
41
+ * testDirectory(path.join(__dirname, './temp'), function () {
42
+ * fs.writeFileSync('testfile', 'Roses are red.');
43
+ * });
44
+ */
45
+ testDirectory(dir, cb) {
46
+ if (!dir) {
47
+ throw new Error('Missing directory');
48
+ }
49
+ dir = resolve(dir);
50
+ // Make sure we're not deleting CWD by moving to top level folder. As we `cd` in the
51
+ // test dir after cleaning up, this shouldn't be perceivable.
52
+ process.chdir('/');
53
+ try {
54
+ if (existsSync(dir)) {
55
+ rmSync(dir, { recursive: true });
56
+ }
57
+ mkdirSync(dir, { recursive: true });
58
+ process.chdir(dir);
59
+ return cb?.();
60
+ }
61
+ catch (error) {
62
+ return cb?.(error);
63
+ }
64
+ }
65
+ /**
66
+ * @deprecated
67
+ * Answer prompt questions for the passed-in generator
68
+ * @param generator - a Yeoman generator or environment
69
+ * @param answers - an object where keys are the
70
+ * generators prompt names and values are the answers to
71
+ * the prompt questions
72
+ * @param options - Options or callback
73
+ * @example
74
+ * mockPrompt(angular, {'bootstrap': 'Y', 'compassBoostrap': 'Y'});
75
+ */
76
+ mockPrompt(envOrGenerator, mockedAnswers, options) {
77
+ const environment = 'env' in envOrGenerator ? envOrGenerator.env : envOrGenerator;
78
+ const { promptModule } = environment.adapter;
79
+ for (const name of Object.keys(promptModule.prompts)) {
80
+ promptModule.registerPrompt(name, class CustomDummyPrompt extends DummyPrompt {
81
+ constructor(question, rl, answers) {
82
+ super(question, rl, answers, mockedAnswers, options);
83
+ }
84
+ });
85
+ }
86
+ }
87
+ /**
88
+ * @deprecated
89
+ * Restore defaults prompts on a generator.
90
+ * @param generator or environment
91
+ */
92
+ restorePrompt(envOrGenerator) {
93
+ const environment = envOrGenerator.env ?? envOrGenerator;
94
+ environment.adapter.promptModule.restoreDefaultPrompts();
95
+ }
96
+ /**
97
+ * @deprecated
98
+ * Provide mocked values to the config
99
+ * @param generator - a Yeoman generator
100
+ * @param localConfig - localConfig - should look just like if called config.getAll()
101
+ */
102
+ mockLocalConfig(generator, localConfig) {
103
+ generator.config.defaults(localConfig);
104
+ }
105
+ /**
106
+ * Create a mocked generator
107
+ */
108
+ createMockedGenerator(GeneratorClass = class MockedGenerator extends YeomanGenerator {
109
+ }) {
110
+ const generator = sinonSpy(GeneratorClass);
111
+ for (const methodName of ['run', 'queueTasks', 'runWithOptions', 'queueOwnTasks']) {
112
+ if (GeneratorClass.prototype[methodName]) {
113
+ generator.prototype[methodName] = sinonStub();
114
+ }
115
+ }
116
+ return generator;
117
+ }
118
+ /**
119
+ * Create a simple, dummy generator
120
+ */
121
+ createDummyGenerator(Generator = YeomanGenerator) {
122
+ class DummyGenerator extends Generator {
123
+ shouldRun;
124
+ test() {
125
+ this.shouldRun = true;
126
+ }
127
+ }
128
+ return DummyGenerator;
129
+ }
130
+ /**
131
+ * Create a generator, using the given dependencies and controller arguments
132
+ * Dependecies can be path (autodiscovery) or an array [{generator}, {name}]
133
+ *
134
+ * @param name - the name of the generator
135
+ * @param dependencies - paths to the generators dependencies
136
+ * @param args - arguments to the generator;
137
+ * if String, will be split on spaces to create an Array
138
+ * @param options - configuration for the generator
139
+ * @param localConfigOnly - passes localConfigOnly to the generators
140
+ * @example
141
+ * var deps = ['../../app',
142
+ * '../../common',
143
+ * '../../controller',
144
+ * '../../main',
145
+ * [createDummyGenerator(), 'testacular:app']
146
+ * ];
147
+ * var angular = createGenerator('angular:app', deps);
148
+ */
149
+ createGenerator(name, dependencies, args, options, localConfigOnly = true) {
150
+ const env = this.createEnv([], { sharedOptions: { localConfigOnly } });
151
+ this.registerDependencies(env, dependencies);
152
+ return env.create(name, args, options);
153
+ }
154
+ /**
155
+ * @deprecated
156
+ * Register a list of dependent generators into the provided env.
157
+ * Dependecies can be path (autodiscovery) or an array [{generator}, {name}]
158
+ *
159
+ * @param dependencies - paths to the generators dependencies
160
+ */
161
+ registerDependencies(env, dependencies) {
162
+ for (const dependency of dependencies) {
163
+ if (Array.isArray(dependency)) {
164
+ env.registerStub(dependency[0], dependency[1]);
165
+ }
166
+ else {
167
+ env.register(dependency);
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Shortcut to the Environment's createEnv.
173
+ *
174
+ * @param {...any} args - environment constructor arguments.
175
+ * @returns {Object} environment instance
176
+ *
177
+ * Use to test with specific Environment version:
178
+ * let createEnv;
179
+ * before(() => {
180
+ * createEnv = stub(helper, 'createEnv').callsFake(Environment.creatEnv);
181
+ * });
182
+ * after(() => {
183
+ * createEnv.restore();
184
+ * });
185
+ */
186
+ createEnv(...args) {
187
+ return Environment.createEnv(...args);
188
+ }
189
+ /**
190
+ * Creates a test environment.
191
+ *
192
+ * @param {Function} envContructor - environment constructor method.
193
+ * @param {Object} [options] - Options to be passed to the environment
194
+ * @returns {Object} environment instance
195
+ * const env = createTestEnv(require('yeoman-environment').createEnv);
196
+ */
197
+ createTestEnv(envContructor = this.createEnv, options = { localConfigOnly: true }) {
198
+ const envOptions = _.cloneDeep(this.environmentOptions ?? {});
199
+ if (typeof options === 'boolean') {
200
+ options = {
201
+ newErrorHandler: true,
202
+ ...envOptions,
203
+ sharedOptions: {
204
+ localConfigOnly: options,
205
+ ...envOptions.sharedOptions,
206
+ },
207
+ };
208
+ }
209
+ else {
210
+ options = {
211
+ newErrorHandler: true,
212
+ ...envOptions,
213
+ ...options,
214
+ };
215
+ options.sharedOptions = {
216
+ localConfigOnly: true,
217
+ ...envOptions.sharedOptions,
218
+ ...options.sharedOptions,
219
+ };
220
+ }
221
+ return envContructor([], options, new TestAdapter());
222
+ }
223
+ /**
224
+ * Get RunContext type
225
+ * @return {RunContext}
226
+ */
227
+ getRunContextType() {
228
+ return RunContext;
229
+ }
230
+ /**
231
+ * Run the provided Generator
232
+ * @param GeneratorOrNamespace - Generator constructor or namespace
233
+ */
234
+ run(GeneratorOrNamespace, settings, envOptions) {
235
+ const contextSettings = _.cloneDeep(this.settings ?? {});
236
+ const generatorOptions = _.cloneDeep(this.generatorOptions ?? {});
237
+ const RunContext = this.getRunContextType();
238
+ const runContext = new RunContext(GeneratorOrNamespace, { ...contextSettings, ...settings }, envOptions, this).withOptions(generatorOptions);
239
+ if (settings?.autoCleanup !== false) {
240
+ testContext.startNewContext(runContext);
241
+ }
242
+ return runContext;
243
+ }
244
+ /**
245
+ * Prepare a run context
246
+ * @param {String|Function} GeneratorOrNamespace - Generator constructor or namespace
247
+ * @return {RunContext}
248
+ */
249
+ create(GeneratorOrNamespace, settings, envOptions) {
250
+ return this.run(GeneratorOrNamespace, settings, envOptions);
251
+ }
252
+ }
253
+ export default new YeomanTest();
254
+ export const createHelpers = options => {
255
+ const helpers = new YeomanTest();
256
+ Object.assign(helpers, options);
257
+ return helpers;
258
+ };
@@ -0,0 +1,4 @@
1
+ export { default, createHelpers, YeomanTest, type Dependency } from './helpers.js';
2
+ export { default as RunContext, RunContextBase, type RunContextSettings } from './run-context.js';
3
+ export { default as RunResult, type RunResultOptions } from './run-result.js';
4
+ export { DummyPrompt, TestAdapter } from './adapter.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default, createHelpers, YeomanTest } from './helpers.js';
2
+ export { default as RunContext, RunContextBase } from './run-context.js';
3
+ export { default as RunResult } from './run-result.js';
4
+ export { DummyPrompt, TestAdapter } from './adapter.js';