approvals
Version:
Approval Tests Library - Capturing Human Intelligence
320 lines (289 loc) • 11.1 kB
text/typescript
/*jshint freeze:false */
/**
* Approvals module.
* @module approvals
*/
import callsite from "callsite";
import path from "path";
import { fileURLToPath } from "url";
import * as cfg from "./config";
import { BinaryWriter } from "./Writers/BinaryWriter";
import { FileApprover } from "./FileApprover";
import { FinalMessages } from "./FinalMessages";
import { ManualNamer } from "./ManualNamer";
import { Namer } from "./Core/Namer";
import { ReporterFactory, ReporterLoader } from "./Reporting/ReporterFactory";
import { Scrubber, Scrubbers } from "./Scrubbers/Scrubbers";
import { StringWriter } from "./StringWriter";
import { Writer } from "./Core/Writer";
import { postRunCleanup } from "./postRunCleanup";
import { stringifyKeysInOrder } from "./AUtils";
// if someone tries to call 'require("approvals")...' without calling ".mocha(...) or
// they won't get a helpful error. So we put this in there - just in case"
import { MochaNamer } from "./Providers/Mocha/MochaNamer";
import { beforeEachVerifierBase } from "./Providers/BeforeEachVerifierBase";
/**
* Contains some helpful and util scrubbers that can be used for scrubbing data before saving to a received file.
*/
export { Scrubbers as scrubbers };
if (typeof beforeEach === "function") {
beforeEach(function (this: any): void {
if (!this) {
return;
}
this.verify = function () {
throw new Error("You must call either .mocha()");
};
});
}
// keep track of approved files we run into with tests
const listOfApprovedFiles: string[] = [];
process.on("approvalFileApproved", function (fileName): void {
if (listOfApprovedFiles.indexOf(fileName) === -1) {
listOfApprovedFiles.push(fileName);
}
});
process.on("exit", function (): void {
const options = cfg.currentConfig();
postRunCleanup(options, listOfApprovedFiles);
const finalMessages = FinalMessages.getMessages();
if (finalMessages.length) {
console.log("******************************");
console.log("* Approvals Report: WARNING!");
finalMessages.forEach((message: string) => {
console.log(`* - ${message}`);
});
console.log("******************************");
}
});
/**
* Allows you to provide overrides to the default configuration.
*
* @example
* const approvals = require('approvals');
* approvals.configure({
* reporters: ['p4merge']
* });
*
* @param {*} overrideOptions
*/
export function configure(overrideOptions: cfg.Config): typeof module.exports {
cfg.configure(overrideOptions);
return module.exports;
}
/**
* Allows the creation of an approvals configuration object using any passed in options to override the defaults.
* @param {Object} overrideOptions
* @returns {Object} approvals config object with any options overridden.
*/
export function getConfig(overrideOptions?: cfg.Config): cfg.Config {
return cfg.getConfig(overrideOptions);
}
/**
* Configure approvals to hook into Mocha tests.
* @param {*} optionalBaseDir - An optional folder to save approval files to.
*/
export function mocha(optionalBaseDir?: string): typeof module.exports {
// if not providing a base dir, fallback to the current calling code's directory
if (!optionalBaseDir) {
let fileName = callsite()[1].getFileName();
if (fileName.startsWith("file://")) {
fileName = fileURLToPath(fileName);
}
optionalBaseDir = path.dirname(fileName);
}
beforeEachVerifierBase(
MochaNamer,
"require('Approvals').mocha();",
optionalBaseDir,
);
return module.exports;
}
/**
* `reporters` gives access to the `MultiReporter`
*
* @example
* const MultiReporter = approvals.reporters.MultiReporter
*/
export const reporters: { MultiReporter: any } = {
/**
* This allows access to the MultiReporter constructor.
* You can use this to run multiple reporters at the same time.
*
* @example
* approvals.verify(__dirname, "multi-reporter-example", "some data", {
* reporters: [
* new MultiReporter('p4merge', 'copycommand')
* ]
* });
*/
MultiReporter: require("./Reporting/Reporters/multiReporter").default,
};
/**
* Use this to apply the scrubber function to any data before running verify.
*
* @example
* // basic approval test with a custom scrubber
* const approvals = require('approvals');
* const scrubber = approvals.scrubbers.multiScrubber([
* function (data) {
* return (data || '').replace("some text", "some other text");
* },
* approvals.scrubbers.guidScrubber // to remove guids from the received data
* });
* approvals.verifyAndScrub(__dirname, 'sample-approval-test', "some text to verify", scrubber);
*
* @param {string} dirName - Typically `__dirname` but could be the base-directory (anywhere) to store both approved and received files.
* @param {string} testName - A file name safe string to call the file associated with this test.
* @param {(string|Buffer)} data - Either the string to save as a text file or a Buffer that represents an image
* @param {*} scrubber - A function that takes a string and returns a string. Approvals will call this if it exists to scrub the "data" before writing to any files.
* @param {*} optionsOverride - An object that can contain configurational overrides as defined in the approvals configuration object.
*/
function verifyAndScrub(
dirName: string,
testName: string,
data: BinaryWriter | string,
scrubber: Scrubber,
optionsOverride: any,
): void {
scrubber = scrubber || Scrubbers.noScrubber;
// backwards compatible option
if (Array.isArray(optionsOverride)) {
optionsOverride = {
reporters: optionsOverride,
};
const stringReporters = JSON.stringify(optionsOverride.reporters);
console.error(
'For the last arg of the approvals.verify(...) function, if you passed in an array of reporters, something like ["opendiff","nodediff"], this has been deprecated and replaced by passing in a config object. Please re-evaluate if you can remove this parameter alltogether and use individual user configs in ~/.approvalConfig, if not then replace this: ' +
stringReporters +
" with this { reporters: " +
stringReporters +
"}.",
);
}
const newOptions = cfg.getConfig(optionsOverride);
const namer = new ManualNamer(dirName, testName);
let writer: Writer;
if (data instanceof Buffer) {
writer = new BinaryWriter(data);
} else {
data = scrubber(data as string);
writer = new StringWriter(newOptions, data as string);
}
verifyWithControl(namer, writer, null, newOptions);
}
/**
*
* @example
* // basic approval test
* const approvals = require('approvals');
* approvals.verify(__dirname, 'sample-approval-test', "some text to verify");
*
* @example
* // basic approval test providing an option to override configuration
* const approvals = require('approvals');
* approvals.verify(__dirname, 'sample-approval-test', "some text to verify", { normalizeLineEndingsTo: true });
*
* @param {string} dirName - Typically `__dirname` but could be the base-directory (anywhere) to store both approved and received files.
* @param {string} testName - A file name save string to call the file associated with this test.
* @param {(string|Buffer)} data - Either the string to save as a text file or a Buffer that represents an image
* @param {*} optionsOverride - An object that can contain configurational overrides as defined in the approvals configuration object.
*/
export function verify(
dirName: string,
testName: string,
data: any,
optionsOverride?: any,
): void {
return verifyAndScrub(
dirName,
testName,
data,
Scrubbers.noScrubber,
optionsOverride,
);
}
/**
* You can pass as "data" any javascript object to be JSON.stringified and run verify against.
*
* @example
* const approvals = require('approvals');
* approvals.verifyAndScrub(__dirname, 'sample-approval-test', { a: "some text in an object" });
*
* @param {string} dirName - Typically `__dirname` but could be the base-directory (anywhere) to store both approved and received files.
* @param {string} testName - A file name safe string to call the file associated with this test.
* @param {(string|Buffer)} data - This can be any JavaScript object/array that will be JSON.stringified before running verify
* @param {*} optionsOverride - An object that can contain configurational overrides as defined in the approvals configuration object.
*/
export function verifyAsJSON(
dirName: string,
testName: string,
data: any,
optionsOverride: any,
) {
return verifyAsJSONAndScrub(
dirName,
testName,
data,
Scrubbers.noScrubber,
optionsOverride,
);
}
/**
* You can pass as "data" any javascript object to be JSON.stringified. Before we run verify the scrubber will be run against the complete string before running verify against it.
* @example
* // basic approval test with a custom scrubber
* const approvals = require('approvals');
* const scrubber = approvals.scrubbers.multiScrubber([
* function (data) {
* return (data || '').replace("some text", "some other text");
* },
* approvals.scrubbers.guidScrubber // to remove guids from the received data
* });
* approvals.verifyAndScrub(__dirname, 'sample-approval-test', { a: "some text in an object" }, scrubber);
*
* @param {string} dirName - Typically `__dirname` but could be the base-directory (anywhere) to store both approved and received files.
* @param {string} testName - A file name safe string to call the file associated with this test.
* @param {(string|Buffer)} data - This can be any JavaScript object/array that will be JSON.stringified before running verify
* @param {*} scrubber - A function that takes a string and returns a string. Approvals will call this if it exists to scrub the "data" before writing to any files.
* @param {*} optionsOverride - An object that can contain configurational overrides as defined in the approvals configuration object.
*/
export function verifyAsJSONAndScrub(
dirName: string,
testName: string,
data: any,
scrubber: Scrubber,
optionsOverride: any,
): void {
return verifyAndScrub(
dirName,
testName,
stringifyKeysInOrder(data),
scrubber,
optionsOverride,
);
}
/**
* This allows you to take full control of naming and writing files before verifying.
*
* For an example that we use to generate the docs within the readme, check out the [test/readmeTests.mts](test/readmeTests.mts) in this project.
*
* @param {Object} namer
* @param {Object} writer
* @param {Function} [reporterFactory]
* @param {Object} [optionsOverride]
*/
export function verifyWithControl(
namer: Namer,
writer: Writer,
reporterFactory?: ReporterLoader | null,
optionsOverride?: Partial<cfg.Config>,
) {
const newOptions = cfg.getConfig(optionsOverride);
const loader =
(reporterFactory as ReporterLoader) ||
function () {
return [ReporterFactory.loadReporter(newOptions.reporters)];
};
FileApprover.verify(namer, writer, loader, newOptions);
}