yeoman-test
Version:
Test utilities for Yeoman generators
269 lines (268 loc) • 8.78 kB
JavaScript
import assert from 'node:assert';
import { existsSync, readFileSync, rmSync } from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { create as createMemFsEditor } from 'mem-fs-editor';
const isObject = object => typeof object === 'object' && object !== null && object !== undefined;
function convertArgs(args) {
if (args.length > 1) {
return [[...args]];
}
const arg = args[0];
return Array.isArray(arg) ? arg : [arg];
}
/**
* This class provides utilities for testing generated content.
*/
export default class RunResult {
env;
generator;
cwd;
oldCwd;
memFs;
fs;
mockedGenerators;
options;
spawnStub;
askedQuestions;
constructor(options) {
if (options.memFs && !options.cwd) {
throw new Error('CWD option is required for mem-fs tests');
}
this.env = options.env;
this.generator = options.generator;
this.cwd = options.cwd ?? process.cwd();
this.oldCwd = options.oldCwd;
this.memFs = options.memFs;
this.fs = this.memFs && createMemFsEditor(this.memFs);
this.mockedGenerators = options.mockedGenerators || {};
this.spawnStub = options.spawnStub;
this.askedQuestions = options.askedQuestions;
this.options = options;
}
/**
* Create another RunContext reusing the settings.
* See helpers.create api
*/
create(GeneratorOrNamespace, settings, envOptions) {
return this.options.helpers.create(GeneratorOrNamespace, {
...this.options.settings,
cwd: this.cwd,
oldCwd: this.oldCwd,
memFs: this.memFs,
...settings,
autoCleanup: false,
}, { ...this.options.envOptions, ...envOptions });
}
getSpawnArgsUsingDefaultImplementation() {
if (!this.spawnStub) {
throw new Error('Spawn stub was not found');
}
return this.spawnStub.getCalls().map(call => call.args);
}
/**
* Return an object with fs changes.
* @param {Function} filter - parameter forwarded to mem-fs-editor#dump
*/
getSnapshot(filter) {
return this.fs.dump(this.cwd, filter);
}
/**
* Return an object with filenames with state.
* @param {Function} filter - parameter forwarded to mem-fs-editor#dump
* @returns {Object}
*/
getStateSnapshot(filter) {
const snapshot = this.getSnapshot(filter);
for (const dump of Object.values(snapshot)) {
delete dump.contents;
}
return snapshot;
}
/**
* Either dumps the contents of the specified files or the name and the contents of each file to the console.
*/
dumpFiles(...files) {
if (files.length === 0) {
this.memFs.each(file => {
console.log(file.path);
if (file.contents) {
console.log(file.contents.toString('utf8'));
}
});
return this;
}
for (const file of files) {
console.log(this.fs.read(this._fileName(file)));
}
return this;
}
/**
* Dumps the name of each file to the console.
*/
dumpFilenames() {
this.memFs.each(file => {
console.log(file.path);
});
return this;
}
/**
* Reverts to old cwd.
* @returns this
*/
restore() {
process.chdir(this.oldCwd);
return this;
}
/**
* Deletes the test directory recursively.
*/
cleanup() {
process.chdir(this.oldCwd);
rmSync(this.cwd, { recursive: true });
return this;
}
_fileName(filename) {
if (path.isAbsolute(filename)) {
return filename;
}
return path.join(this.cwd, filename);
}
_readFile(filename, json) {
filename = this._fileName(filename);
const file = (this.fs ? this.fs.read(filename) : undefined) ?? readFileSync(filename, 'utf8');
return json ? JSON.parse(file) : file;
}
_exists(filename) {
filename = this._fileName(filename);
if (this.fs) {
return this.fs.exists(filename);
}
return existsSync(filename);
}
/**
* Assert that a file exists
* @param path - path to a file
* @example
* result.assertFile('templates/user.hbs');
*
* @also
*
* Assert that each files in the array exists
* @param paths - an array of paths to files
* @example
* result.assertFile(['templates/user.hbs', 'templates/user/edit.hbs']);
*/
assertFile(path) {
for (const file of convertArgs([path])) {
const here = this._exists(file);
assert.ok(here, `${file}, no such file or directory`);
}
}
/**
* Assert that a file doesn't exist
* @param file - path to a file
* @example
* result.assertNoFile('templates/user.hbs');
*
* @also
*
* Assert that each of an array of files doesn't exist
* @param pairs - an array of paths to files
* @example
* result.assertNoFile(['templates/user.hbs', 'templates/user/edit.hbs']);
*/
assertNoFile(files) {
for (const file of convertArgs([files])) {
const here = this._exists(file);
assert.ok(!here, `${file} exists`);
}
}
assertFileContent(...args) {
for (const pair of convertArgs(args)) {
const file = pair[0];
const regex = pair[1];
this.assertFile(file);
const body = this._readFile(file);
let match = false;
match = typeof regex === 'string' ? body.includes(regex) : regex.test(body);
assert(match, `${file} did not match '${regex}'. Contained:\n\n${body}`);
}
}
assertEqualsFileContent(...args) {
for (const pair of convertArgs(args)) {
const file = pair[0];
const expectedContent = pair[1];
this.assertFile(file);
this.assertTextEqual(this._readFile(file), expectedContent);
}
}
assertNoFileContent(...args) {
for (const pair of convertArgs(args)) {
const file = pair[0];
const regex = pair[1];
this.assertFile(file);
const body = this._readFile(file);
if (typeof regex === 'string') {
assert.ok(!body.includes(regex), `${file} matched '${regex}'.`);
continue;
}
assert.ok(!regex.test(body), `${file} matched '${regex}'.`);
}
}
/**
* Assert that two strings are equal after standardization of newlines
* @param value - a string
* @param expected - the expected value of the string
* @example
* result.assertTextEqual('I have a yellow cat', 'I have a yellow cat');
*/
assertTextEqual(value, expected) {
const eol = string => string.replaceAll('\r\n', '\n');
assert.equal(eol(value), eol(expected));
}
/**
* Assert an object contains the provided keys
* @param obj Object that should match the given pattern
* @param content An object of key/values the object should contains
*/
assertObjectContent(object, content) {
for (const key of Object.keys(content)) {
if (isObject(content[key])) {
this.assertObjectContent(object[key], content[key]);
continue;
}
assert.equal(object[key], content[key]);
}
}
/**
* Assert an object does not contain the provided keys
* @param obj Object that should not match the given pattern
* @param content An object of key/values the object should not contain
*/
assertNoObjectContent(object, content) {
for (const key of Object.keys(content)) {
if (isObject(content[key])) {
this.assertNoObjectContent(object[key], content[key]);
continue;
}
assert.notEqual(object[key], content[key]);
}
}
/**
* Assert a JSON file contains the provided keys
* @param filename
* @param content An object of key/values the file should contains
*/
assertJsonFileContent(filename, content) {
this.assertObjectContent(this._readFile(filename, true), content);
}
/**
* Assert a JSON file does not contain the provided keys
* @param filename
* @param content An object of key/values the file should not contain
*/
assertNoJsonFileContent(filename, content) {
this.assertNoObjectContent(this._readFile(filename, true), content);
}
}