UNPKG

yeoman-test

Version:

Test utilities for Yeoman generators

269 lines (268 loc) 8.78 kB
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); } }