UNPKG

fishery

Version:

A library for setting up JavaScript factories to help build objects as test data, with full TypeScript support

364 lines (349 loc) 15.5 kB
'use strict'; var mergeWith = require('lodash.mergewith'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : undefined, done: true }; } } function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var merge = mergeWith; var mergeCustomizer = function (objValue, srcVal, key, object) { if (Array.isArray(srcVal)) { return srcVal; } else if (srcVal === undefined) { object[key] = srcVal; } }; var FactoryBuilder = /** @class */ (function () { function FactoryBuilder(generator, sequence, params, transientParams, associations, afterBuilds, afterCreates, onCreate) { var _this = this; this.generator = generator; this.sequence = sequence; this.params = params; this.transientParams = transientParams; this.associations = associations; this.afterBuilds = afterBuilds; this.afterCreates = afterCreates; this.onCreate = onCreate; this.setAfterBuild = function (hook) { _this.afterBuilds = __spreadArray([hook], _this.afterBuilds, true); }; this.setAfterCreate = function (hook) { _this.afterCreates = __spreadArray([hook], _this.afterCreates, true); }; this.setOnCreate = function (hook) { if (!_this.onCreate) { _this.onCreate = hook; } }; } FactoryBuilder.prototype.build = function () { var generatorOptions = { sequence: this.sequence, afterBuild: this.setAfterBuild, afterCreate: this.setAfterCreate, onCreate: this.setOnCreate, params: this.params, associations: this.associations, transientParams: this.transientParams, }; var object = this._mergeParamsOntoObject(this.generator(generatorOptions)); this._callAfterBuilds(object); return object; }; FactoryBuilder.prototype.create = function () { return __awaiter(this, undefined, undefined, function () { var object, created; return __generator(this, function (_a) { switch (_a.label) { case 0: object = this.build(); return [4 /*yield*/, this._callOnCreate(object)]; case 1: created = _a.sent(); return [2 /*return*/, this._callAfterCreates(created)]; } }); }); }; // merge params and associations into object. The only reason 'associations' // is separated is because it is typed differently from `params` (Partial<T> // vs DeepPartial<T>) so can do the following in a factory: // `user: associations.user || userFactory.build()` FactoryBuilder.prototype._mergeParamsOntoObject = function (object) { if (typeof object !== 'object') { return object; } var targetObject = object; if (Object.getPrototypeOf(object) === Object.prototype) { targetObject = {}; } else if (Array.isArray(object)) { targetObject = []; } return merge(targetObject, object, this.params, this.associations, mergeCustomizer); }; FactoryBuilder.prototype._callAfterBuilds = function (object) { this.afterBuilds.forEach(function (afterBuild) { if (typeof afterBuild === 'function') { afterBuild(object); } else { throw new Error('"afterBuild" must be a function'); } }); }; FactoryBuilder.prototype._callOnCreate = function (object) { return __awaiter(this, undefined, undefined, function () { return __generator(this, function (_a) { if (!this.onCreate) { throw new Error('Attempted to call `create`, but no onCreate defined'); } return [2 /*return*/, this.onCreate(object)]; }); }); }; FactoryBuilder.prototype._callAfterCreates = function (object) { return __awaiter(this, undefined, undefined, function () { var created, _i, _a, afterCreate; return __generator(this, function (_b) { switch (_b.label) { case 0: created = object; _i = 0, _a = this.afterCreates; _b.label = 1; case 1: if (!(_i < _a.length)) return [3 /*break*/, 5]; afterCreate = _a[_i]; if (!(typeof afterCreate === 'function')) return [3 /*break*/, 3]; return [4 /*yield*/, afterCreate(created)]; case 2: created = _b.sent(); return [3 /*break*/, 4]; case 3: throw new Error('"afterCreate" must be a function'); case 4: _i++; return [3 /*break*/, 1]; case 5: return [2 /*return*/, created]; } }); }); }; return FactoryBuilder; }()); var SEQUENCE_START_VALUE = 1; var Factory = /** @class */ (function () { function Factory(generator) { this.generator = generator; // id is an object so it is shared between extended factories this.id = { value: SEQUENCE_START_VALUE }; this._afterBuilds = []; this._afterCreates = []; } /** * Define a factory. * @template T The object the factory builds * @template I The transient parameters that your factory supports * @template C The class of the factory object being created. * @param generator - your factory function */ Factory.define = function (generator) { return new this(generator); }; /** * Build an object using your factory * @param params * @param options */ Factory.prototype.build = function (params, options) { if (options === undefined) { options = {}; } return this.builder(params, options).build(); }; Factory.prototype.buildList = function (number, params, options) { if (options === undefined) { options = {}; } var list = []; for (var i = 0; i < number; i++) { list.push(this.build(params, options)); } return list; }; /** * Asynchronously create an object using your factory. * @param params * @param options */ Factory.prototype.create = function (params_1) { return __awaiter(this, arguments, undefined, function (params, options) { if (options === undefined) { options = {}; } return __generator(this, function (_a) { return [2 /*return*/, this.builder(params, options).create()]; }); }); }; Factory.prototype.createList = function (number_1, params_1) { return __awaiter(this, arguments, undefined, function (number, params, options) { var list, i; if (options === undefined) { options = {}; } return __generator(this, function (_a) { list = []; for (i = 0; i < number; i++) { list.push(this.create(params, options)); } return [2 /*return*/, Promise.all(list)]; }); }); }; /** * Extend the factory by adding a function to be called after an object is built. * @param afterBuildFn - the function to call. It accepts your object of type T. The value this function returns gets returned from "build" * @returns a new factory */ Factory.prototype.afterBuild = function (afterBuildFn) { var factory = this.clone(); factory._afterBuilds.push(afterBuildFn); return factory; }; /** * Define a transform that occurs when `create` is called on the factory. Specifying an `onCreate` overrides any previous `onCreate`s. * To return a different type from `build`, specify a third type argument when defining the factory. * @param onCreateFn - The function to call. IT accepts your object of type T. * The value this function returns gets returned from "create" after any * `afterCreate`s are run * @return a new factory */ Factory.prototype.onCreate = function (onCreateFn) { var factory = this.clone(); factory._onCreate = onCreateFn; return factory; }; /** * Extend the factory by adding a function to be called after creation. This is called after `onCreate` but before the object is returned from `create`. * If multiple are defined, they are chained. * @param afterCreateFn * @return a new factory */ Factory.prototype.afterCreate = function (afterCreateFn) { var factory = this.clone(); factory._afterCreates.push(afterCreateFn); return factory; }; /** * Extend the factory by adding default associations to be passed to the factory when "build" is called * @param associations * @returns a new factory */ Factory.prototype.associations = function (associations) { var factory = this.clone(); factory._associations = __assign(__assign({}, this._associations), associations); return factory; }; /** * Extend the factory by adding default parameters to be passed to the factory when "build" is called * @param params * @returns a new factory */ Factory.prototype.params = function (params) { var factory = this.clone(); factory._params = merge({}, this._params, params, mergeCustomizer); return factory; }; /** * Extend the factory by adding default transient parameters to be passed to the factory when "build" is called * @param transient - transient params * @returns a new factory */ Factory.prototype.transient = function (transient) { var factory = this.clone(); factory._transient = __assign(__assign({}, this._transient), transient); return factory; }; /** * Sets sequence back to its default value */ Factory.prototype.rewindSequence = function () { this.id.value = SEQUENCE_START_VALUE; }; Factory.prototype.clone = function () { var copy = new this.constructor(this.generator); Object.assign(copy, this); copy._afterCreates = __spreadArray([], this._afterCreates, true); copy._afterBuilds = __spreadArray([], this._afterBuilds, true); return copy; }; Factory.prototype.sequence = function () { return this.id.value++; }; Factory.prototype.builder = function (params, options) { if (options === undefined) { options = {}; } return new FactoryBuilder(this.generator, this.sequence(), merge({}, this._params, params, mergeCustomizer), __assign(__assign({}, this._transient), options.transient), __assign(__assign({}, this._associations), options.associations), this._afterBuilds, this._afterCreates, this._onCreate); }; return Factory; }()); exports.Factory = Factory;