yeoman-test 7.3.0 → 7.4.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
@@ -25,6 +25,8 @@ $ npm install --save-dev yeoman-generator@xxx yeoman-environment@xxx
25
25
  Usage:
26
26
 
27
27
  ```js
28
+ import helpers from 'yeoman-test';
29
+
28
30
  describe('generator test', () => {
29
31
  describe('test', () => {
30
32
  let runResult;
@@ -36,12 +38,22 @@ describe('generator test', () => {
36
38
  {} // environment options
37
39
  )
38
40
  [.cd(dir)] // runs the test inside a non temporary dir
39
- [.doInDir(dir => {}) // prepares the test dir
41
+ [.onTargetDirectory(dir => {}) // prepares the test dir
40
42
  [.withGenerators([])] // registers additional generators
41
43
  [.withLookups({})] // runs Environment lookups
42
44
  [.withOptions({})] // passes options to the generator
43
45
  [.withLocalConfig({})] // sets the generator config as soon as it is instantiated
44
46
  [.withAnswers()] // simulates the prompt answers
47
+ [.withMockedGenerators(['namespace', ...])] // adds a mocked generator to the namespaces
48
+ [.withFiles({
49
+ 'foo.txt': 'bar',
50
+ 'test.json', { content: true },
51
+ })] // add files to mem-fs
52
+ [.withYoRc({ 'generator-foo': { bar: {} } })] // add config to .yo-rc.json
53
+ [.withYoRcConfig('generator-foo.bar', { : {} })] // same as above
54
+ [.commitFiles()] // commit mem-fs files to disk
55
+ [.onGenerator(gen => {})] // do something with the generator
56
+ [.onEnvironment(env => {})] // do something with the environment
45
57
  [.build(runContext => { // instantiates Environment/Generator
46
58
  [runContext.env...] // does something with the environment
47
59
  [runContext.generator...] // does something with the generator
@@ -51,6 +63,7 @@ describe('generator test', () => {
51
63
  );
52
64
  afterEach(() => {
53
65
  if (runResult) {
66
+ // Optional if context is executed at a temporary folder
54
67
  runResult.restore();
55
68
  }
56
69
  });
@@ -68,6 +81,46 @@ describe('generator test', () => {
68
81
  });
69
82
  ```
70
83
 
84
+ Convenience last RunResult instance:
85
+
86
+ ```js
87
+ import helpers, { result } from 'yeoman-test';
88
+
89
+ describe('generator test', () => {
90
+ before(() => helpers.run('namespace'));
91
+ it('test', () => {
92
+ result.assert...;
93
+ });
94
+ });
95
+ ```
96
+
97
+ Generator compose:
98
+
99
+ ```js
100
+ import assert from 'assert';
101
+ import helpers, { result } from 'yeoman-test';
102
+
103
+ describe('my-gen', () => {
104
+ before(() => helpers.run('my-gen').withMockedGenerator(['composed-gen']));
105
+ it('should compose with composed-gen', () => {
106
+ assert(result.mockedGenerators['composed-gen'].calledOnce);
107
+ });
108
+ });
109
+ ```
110
+
111
+ Generic test folder:
112
+
113
+ ```js
114
+ import helpers, { result } from 'yeoman-test';
115
+
116
+ describe('generic test', () => {
117
+ before(() => helpers.prepareTemporaryDir());
118
+ it('test', () => {
119
+ result.assert...;
120
+ });
121
+ });
122
+ ```
123
+
71
124
  [See our api documentation](https://yeoman.github.io/yeoman-test) for latest yeoman-test release.
72
125
 
73
126
  [See our api documentation](https://yeoman.github.io/yeoman-test/5.0.1) for yeoman-test 5.0.1. Use 5.x for yeoman-environment 2.x support.
package/dist/helpers.d.ts CHANGED
@@ -4,8 +4,7 @@ import type { GeneratorOptions } from 'yeoman-generator';
4
4
  import type { Options, createEnv } from 'yeoman-environment';
5
5
  import type { SinonSpiedInstance } from 'sinon';
6
6
  import { type DummyPromptOptions } from './adapter.js';
7
- import RunContext from './run-context.js';
8
- import type { RunContextSettings } from './run-context.js';
7
+ import RunContext, { BasicRunContext, type RunContextSettings } from './run-context.js';
9
8
  /**
10
9
  * Dependencies can be path (autodiscovery) or an array [<generator>, <name>]
11
10
  */
@@ -144,6 +143,11 @@ export declare class YeomanTest {
144
143
  * @return {RunContext}
145
144
  */
146
145
  create<GeneratorType extends YeomanGenerator = YeomanGenerator>(GeneratorOrNamespace: string | GeneratorConstructor<GeneratorType>, settings?: RunContextSettings, envOptions?: Options): RunContext<GeneratorType>;
146
+ /**
147
+ * Prepare temporary dir without generator support.
148
+ * Generator and environment will be undefined.
149
+ */
150
+ prepareTemporaryDir(settings?: RunContextSettings): BasicRunContext;
147
151
  }
148
152
  declare const _default: YeomanTest;
149
153
  export default _default;
package/dist/helpers.js CHANGED
@@ -7,7 +7,7 @@ import { spy as sinonSpy, stub as sinonStub } from 'sinon';
7
7
  import YeomanGenerator from 'yeoman-generator';
8
8
  import Environment from 'yeoman-environment';
9
9
  import { DummyPrompt, TestAdapter } from './adapter.js';
10
- import RunContext from './run-context.js';
10
+ import RunContext, { BasicRunContext } from './run-context.js';
11
11
  import testContext from './test-context.js';
12
12
  const { cloneDeep } = _;
13
13
  /**
@@ -261,6 +261,17 @@ export class YeomanTest {
261
261
  create(GeneratorOrNamespace, settings, envOptions) {
262
262
  return this.run(GeneratorOrNamespace, settings, envOptions);
263
263
  }
264
+ /**
265
+ * Prepare temporary dir without generator support.
266
+ * Generator and environment will be undefined.
267
+ */
268
+ prepareTemporaryDir(settings) {
269
+ const context = new BasicRunContext(undefined, settings);
270
+ if (settings?.autoCleanup !== false) {
271
+ testContext.startNewContext(context);
272
+ }
273
+ return context;
274
+ }
264
275
  }
265
276
  export default new YeomanTest();
266
277
  export const createHelpers = options => {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { default, createHelpers, YeomanTest, type Dependency } from './helpers.js';
2
2
  export { default as RunContext, RunContextBase, type RunContextSettings } from './run-context.js';
3
3
  export { default as RunResult, type RunResultOptions } from './run-result.js';
4
- export { default as context } from './test-context.js';
4
+ export { default as context, result } from './test-context.js';
5
5
  export { DummyPrompt, TestAdapter } from './adapter.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { default, createHelpers, YeomanTest } from './helpers.js';
2
2
  export { default as RunContext, RunContextBase } from './run-context.js';
3
3
  export { default as RunResult } from './run-result.js';
4
- export { default as context } from './test-context.js';
4
+ export { default as context, result } from './test-context.js';
5
5
  export { DummyPrompt, TestAdapter } from './adapter.js';
@@ -4,7 +4,8 @@ import type Generator from 'yeoman-generator';
4
4
  import type Environment from 'yeoman-environment';
5
5
  import { type LookupOptions, type Options } from 'yeoman-environment';
6
6
  import MemFsEditor from 'mem-fs-editor';
7
- import RunResult from './run-result.js';
7
+ import MemFs from 'mem-fs';
8
+ import RunResult, { type RunResultOptions } from './run-result.js';
8
9
  import { type GeneratorConstructor, type Dependency, type YeomanTest } from './helpers.js';
9
10
  import { type DummyPromptOptions } from './adapter.js';
10
11
  /**
@@ -20,6 +21,7 @@ export type RunContextSettings = {
20
21
  oldCwd?: string;
21
22
  forwardCwd?: boolean;
22
23
  autoCleanup?: boolean;
24
+ memFs?: MemFs.Store;
23
25
  /**
24
26
  * File path to the generator (only used if Generator is a constructor)
25
27
  */
@@ -31,6 +33,7 @@ export type RunContextSettings = {
31
33
  namespace?: string;
32
34
  };
33
35
  type PromiseRunResult<GeneratorType extends Generator> = Promise<RunResult<GeneratorType>>;
36
+ type MockedGeneratorFactory = (GeneratorClass?: typeof Generator) => typeof Generator;
34
37
  export declare class RunContextBase<GeneratorType extends Generator = Generator> extends EventEmitter {
35
38
  readonly mockedGenerators: Record<string, Generator>;
36
39
  env: Environment;
@@ -40,15 +43,18 @@ export declare class RunContextBase<GeneratorType extends Generator = Generator>
40
43
  completed: boolean;
41
44
  targetDirectory?: string;
42
45
  editor: MemFsEditor.Editor;
46
+ memFs: MemFs.Store;
47
+ mockedGeneratorFactory: MockedGeneratorFactory;
43
48
  protected environmentPromise?: PromiseRunResult<GeneratorType>;
44
49
  private args;
45
50
  private options;
46
51
  private answers?;
52
+ private keepFsState?;
47
53
  private readonly onGeneratorCallbacks;
48
54
  private readonly onTargetDirectoryCallbacks;
49
55
  private readonly onEnvironmentCallbacks;
50
56
  private readonly inDirCallbacks;
51
- private readonly Generator;
57
+ private readonly Generator?;
52
58
  private readonly helpers;
53
59
  private readonly temporaryDir;
54
60
  private oldCwd?;
@@ -67,7 +73,7 @@ export declare class RunContextBase<GeneratorType extends Generator = Generator>
67
73
  * @param settings
68
74
  * @return {this}
69
75
  */
70
- constructor(generatorType: string | GeneratorConstructor<GeneratorType> | typeof Generator, settings?: RunContextSettings, envOptions?: Options, helpers?: YeomanTest);
76
+ constructor(generatorType?: string | GeneratorConstructor<GeneratorType> | typeof Generator, settings?: RunContextSettings, envOptions?: Options, helpers?: YeomanTest);
71
77
  /**
72
78
  * Run the generator on the environment and promises a RunResult instance.
73
79
  * @return {PromiseRunResult} Promise a RunResult instance.
@@ -188,6 +194,7 @@ export declare class RunContextBase<GeneratorType extends Generator = Generator>
188
194
  * });
189
195
  */
190
196
  withGenerators(dependencies: Dependency[]): this;
197
+ withMockedGeneratorFactory(mockedGeneratorFactory: MockedGeneratorFactory): this;
191
198
  /**
192
199
  * Create mocked generators
193
200
  * @param namespaces - namespaces of mocked generators
@@ -210,6 +217,10 @@ export declare class RunContextBase<GeneratorType extends Generator = Generator>
210
217
  * @param localConfig - should look just like if called config.getAll()
211
218
  */
212
219
  withLocalConfig(localConfig: Record<string, unknown>): this;
220
+ /**
221
+ * Don't reset mem-fs state cleared to aggregate snapshots from multiple runs.
222
+ */
223
+ withKeepFsState(): this;
213
224
  /**
214
225
  * Add files to mem-fs.
215
226
  * Files will be resolved relative to targetDir.
@@ -252,6 +263,7 @@ export declare class RunContextBase<GeneratorType extends Generator = Generator>
252
263
  * @returns
253
264
  */
254
265
  onEnvironment(callback: (this: this, env: Environment) => any): this;
266
+ prepare(): Promise<void>;
255
267
  protected assertNotBuild(): void;
256
268
  /**
257
269
  * Build the generator and the environment.
@@ -263,6 +275,7 @@ export declare class RunContextBase<GeneratorType extends Generator = Generator>
263
275
  * @return Promise resolved on end or rejected on error
264
276
  */
265
277
  protected toPromise(): PromiseRunResult<GeneratorType>;
278
+ protected _createRunResultOptions(): RunResultOptions<GeneratorType>;
266
279
  /**
267
280
  * Keeps compatibility with events
268
281
  */
@@ -275,7 +288,6 @@ export declare class RunContextBase<GeneratorType extends Generator = Generator>
275
288
  * @return {this} run context instance
276
289
  */
277
290
  private setDir;
278
- private _createRunResultOptions;
279
291
  }
280
292
  export default class RunContext<GeneratorType extends Generator = Generator> extends RunContextBase<GeneratorType> implements Promise<RunResult<GeneratorType>> {
281
293
  then<TResult1 = RunResult<GeneratorType>, TResult2 = never>(onfulfilled?: ((value: RunResult<GeneratorType>) => TResult1 | PromiseLike<TResult1>) | undefined | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | undefined): Promise<TResult1 | TResult2>;
@@ -283,4 +295,7 @@ export default class RunContext<GeneratorType extends Generator = Generator> ext
283
295
  finally(onfinally?: (() => void) | undefined | undefined): Promise<RunResult<GeneratorType>>;
284
296
  get [Symbol.toStringTag](): string;
285
297
  }
298
+ export declare class BasicRunContext extends RunContext {
299
+ run(): PromiseRunResult<any>;
300
+ }
286
301
  export {};
@@ -7,8 +7,11 @@ import process from 'node:process';
7
7
  import _ from 'lodash';
8
8
  import tempDirectory from 'temp-dir';
9
9
  import MemFsEditor from 'mem-fs-editor';
10
+ import MemFsEditorState from 'mem-fs-editor/lib/state.js';
11
+ import MemFs from 'mem-fs';
10
12
  import RunResult from './run-result.js';
11
13
  import defaultHelpers from './helpers.js';
14
+ import testContext from './test-context.js';
12
15
  const { camelCase, kebabCase, merge: lodashMerge, set: lodashSet } = _;
13
16
  export class RunContextBase extends EventEmitter {
14
17
  mockedGenerators = {};
@@ -19,10 +22,13 @@ export class RunContextBase extends EventEmitter {
19
22
  completed = false;
20
23
  targetDirectory;
21
24
  editor;
25
+ memFs;
26
+ mockedGeneratorFactory;
22
27
  environmentPromise;
23
28
  args = [];
24
29
  options = {};
25
30
  answers;
31
+ keepFsState;
26
32
  onGeneratorCallbacks = [];
27
33
  onTargetDirectoryCallbacks = [];
28
34
  onEnvironmentCallbacks = [];
@@ -63,6 +69,8 @@ export class RunContextBase extends EventEmitter {
63
69
  this.cd(this.settings.cwd);
64
70
  }
65
71
  this.helpers = helpers;
72
+ this.memFs = settings?.memFs ?? MemFs.create();
73
+ this.mockedGeneratorFactory = this.helpers.createMockedGenerator;
66
74
  }
67
75
  /**
68
76
  * Run the generator on the environment and promises a RunResult instance.
@@ -80,7 +88,9 @@ export class RunContextBase extends EventEmitter {
80
88
  this.helpers.restorePrompt(this.env);
81
89
  this.completed = true;
82
90
  }
83
- return new RunResult(this._createRunResultOptions());
91
+ const runResult = new RunResult(this._createRunResultOptions());
92
+ testContext.runResult = runResult;
93
+ return runResult;
84
94
  }
85
95
  // If any event listeners is added, setup event listeners emitters
86
96
  on(eventName, listener) {
@@ -304,6 +314,10 @@ export class RunContextBase extends EventEmitter {
304
314
  }
305
315
  });
306
316
  }
317
+ withMockedGeneratorFactory(mockedGeneratorFactory) {
318
+ this.mockedGeneratorFactory = mockedGeneratorFactory;
319
+ return this;
320
+ }
307
321
  /**
308
322
  * Create mocked generators
309
323
  * @param namespaces - namespaces of mocked generators
@@ -322,7 +336,7 @@ export class RunContextBase extends EventEmitter {
322
336
  */
323
337
  withMockedGenerators(namespaces) {
324
338
  assert(Array.isArray(namespaces), 'namespaces should be an array');
325
- const dependencies = namespaces.map(namespace => [this.helpers.createMockedGenerator(), namespace]);
339
+ const dependencies = namespaces.map(namespace => [this.mockedGeneratorFactory(), namespace]);
326
340
  const entries = dependencies.map(([generator, namespace]) => [namespace, generator]);
327
341
  Object.assign(this.mockedGenerators, Object.fromEntries(entries));
328
342
  return this.withGenerators(dependencies);
@@ -335,6 +349,13 @@ export class RunContextBase extends EventEmitter {
335
349
  assert(typeof localConfig === 'object', 'config should be an object');
336
350
  return this.onGenerator(generator => generator.config.defaults(localConfig));
337
351
  }
352
+ /**
353
+ * Don't reset mem-fs state cleared to aggregate snapshots from multiple runs.
354
+ */
355
+ withKeepFsState() {
356
+ this.keepFsState = true;
357
+ return this;
358
+ }
338
359
  withFiles(relativePath, files) {
339
360
  return this.onTargetDirectory(function () {
340
361
  const targetDirectory = typeof relativePath === 'string' ? pathJoin(this.targetDirectory, relativePath) : this.targetDirectory;
@@ -409,16 +430,7 @@ export class RunContextBase extends EventEmitter {
409
430
  this.onEnvironmentCallbacks.push(callback);
410
431
  return this;
411
432
  }
412
- assertNotBuild() {
413
- if (this.built || this.completed) {
414
- throw new Error('The context is already built');
415
- }
416
- }
417
- /**
418
- * Build the generator and the environment.
419
- * @return {RunContext|false} this
420
- */
421
- async build() {
433
+ async prepare() {
422
434
  this.assertNotBuild();
423
435
  this.built = true;
424
436
  if (!this.targetDirectory && this.settings.tmpdir !== false) {
@@ -437,17 +449,36 @@ export class RunContextBase extends EventEmitter {
437
449
  if (!this.targetDirectory) {
438
450
  throw new Error('targetDirectory is required');
439
451
  }
452
+ if (!this.keepFsState) {
453
+ this.memFs.each(file => {
454
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
455
+ delete file[MemFsEditorState.STATE_CLEARED];
456
+ });
457
+ }
458
+ this.editor = MemFsEditor.create(this.memFs);
459
+ for (const onTargetDirectory of this.onTargetDirectoryCallbacks) {
460
+ // eslint-disable-next-line no-await-in-loop
461
+ await onTargetDirectory.call(this, this.targetDirectory);
462
+ }
463
+ }
464
+ assertNotBuild() {
465
+ if (this.built || this.completed) {
466
+ throw new Error('The context is already built');
467
+ }
468
+ }
469
+ /**
470
+ * Build the generator and the environment.
471
+ * @return {RunContext|false} this
472
+ */
473
+ async build() {
474
+ await this.prepare();
440
475
  const testEnv = await this.helpers.createTestEnv(this.envOptions.createEnv, {
441
476
  cwd: this.settings.forwardCwd ? this.targetDirectory : undefined,
477
+ sharedFs: this.memFs,
442
478
  ...this.options,
443
479
  ...this.envOptions,
444
480
  });
445
481
  this.env = this.envCB ? (await this.envCB(testEnv)) ?? testEnv : testEnv;
446
- this.editor = MemFsEditor.create(this.env.sharedFs);
447
- for (const onTargetDirectory of this.onTargetDirectoryCallbacks) {
448
- // eslint-disable-next-line no-await-in-loop
449
- await onTargetDirectory.call(this, this.targetDirectory);
450
- }
451
482
  for (const onEnvironmentCallback of this.onEnvironmentCallbacks) {
452
483
  // eslint-disable-next-line no-await-in-loop
453
484
  await onEnvironmentCallback.call(this, this.env);
@@ -475,6 +506,21 @@ export class RunContextBase extends EventEmitter {
475
506
  async toPromise() {
476
507
  return this.environmentPromise ?? this.run();
477
508
  }
509
+ _createRunResultOptions() {
510
+ return {
511
+ env: this.env,
512
+ generator: this.generator,
513
+ memFs: this.env?.sharedFs ?? this.memFs,
514
+ settings: {
515
+ ...this.settings,
516
+ },
517
+ oldCwd: this.oldCwd,
518
+ cwd: this.targetDirectory,
519
+ envOptions: this.envOptions,
520
+ mockedGenerators: this.mockedGenerators,
521
+ helpers: this.helpers,
522
+ };
523
+ }
478
524
  /**
479
525
  * Keeps compatibility with events
480
526
  */
@@ -522,21 +568,6 @@ export class RunContextBase extends EventEmitter {
522
568
  this.targetDirectory = dirPath;
523
569
  return this;
524
570
  }
525
- _createRunResultOptions() {
526
- return {
527
- env: this.env,
528
- generator: this.generator,
529
- memFs: this.env.sharedFs,
530
- settings: {
531
- ...this.settings,
532
- },
533
- oldCwd: this.oldCwd,
534
- cwd: this.targetDirectory,
535
- envOptions: this.envOptions,
536
- mockedGenerators: this.mockedGenerators,
537
- helpers: this.helpers,
538
- };
539
- }
540
571
  }
541
572
  export default class RunContext extends RunContextBase {
542
573
  // eslint-disable-next-line unicorn/no-thenable
@@ -553,3 +584,11 @@ export default class RunContext extends RunContextBase {
553
584
  return `RunContext`;
554
585
  }
555
586
  }
587
+ export class BasicRunContext extends RunContext {
588
+ async run() {
589
+ await this.prepare();
590
+ const runResult = new RunResult(this._createRunResultOptions());
591
+ testContext.runResult = runResult;
592
+ return runResult;
593
+ }
594
+ }
@@ -45,9 +45,10 @@ export default class RunResult {
45
45
  ...this.options.settings,
46
46
  cwd: this.cwd,
47
47
  oldCwd: this.oldCwd,
48
+ memFs: this.memFs,
48
49
  ...settings,
49
50
  autoCleanup: false,
50
- }, { ...this.options.envOptions, memFs: this.memFs, ...envOptions });
51
+ }, { ...this.options.envOptions, ...envOptions });
51
52
  }
52
53
  /**
53
54
  * Return an object with fs changes.
@@ -1,7 +1,13 @@
1
1
  import type RunContext from './run-context.js';
2
+ import type RunResult from './run-result.js';
2
3
  declare class TestContext {
4
+ runResult?: RunResult;
3
5
  private runContext?;
4
6
  startNewContext(runContext: RunContext<any>): void;
5
7
  }
6
- declare const _default: TestContext;
7
- export default _default;
8
+ declare const testContext: TestContext;
9
+ export default testContext;
10
+ /**
11
+ * Provides a proxy for last executed context result.
12
+ */
13
+ export declare const result: RunResult;
@@ -1,8 +1,23 @@
1
1
  class TestContext {
2
+ runResult;
2
3
  runContext;
3
4
  startNewContext(runContext) {
4
5
  this.runContext?.cleanupTemporaryDir();
5
6
  this.runContext = runContext;
7
+ this.runResult = undefined;
6
8
  }
7
9
  }
8
- export default new TestContext();
10
+ const testContext = new TestContext();
11
+ export default testContext;
12
+ const handler2 = {
13
+ get(_target, prop, receiver) {
14
+ if (testContext.runResult === undefined) {
15
+ throw new Error('Last result is missing.');
16
+ }
17
+ return Reflect.get(testContext.runResult, prop, receiver);
18
+ },
19
+ };
20
+ /**
21
+ * Provides a proxy for last executed context result.
22
+ */
23
+ export const result = new Proxy({}, handler2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yeoman-test",
3
- "version": "7.3.0",
3
+ "version": "7.4.0",
4
4
  "description": "Test utilities for Yeoman generators",
5
5
  "homepage": "http://yeoman.io/authoring/testing.html",
6
6
  "author": "The Yeoman Team",
@@ -51,7 +51,7 @@
51
51
  "@types/yeoman-generator": "^5.2.11",
52
52
  "inquirer": "^8.2.5",
53
53
  "lodash": "^4.17.21",
54
- "mem-fs-editor": "^9.5.0",
54
+ "mem-fs-editor": "^9.7.0",
55
55
  "sinon": "^14.0.2",
56
56
  "temp-dir": "^3.0.0"
57
57
  },