| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- 'use strict';
- const MOCK_CONSTRUCTOR_NAME = 'mockConstructor'; /**
- * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- *
- */
- const FUNCTION_NAME_RESERVED_PATTERN = /[\s!-\/:-@\[-`{-~]/;
- const FUNCTION_NAME_RESERVED_REPLACE = new RegExp(FUNCTION_NAME_RESERVED_PATTERN.source, 'g');
- const RESERVED_KEYWORDS = Object.assign(Object.create(null), {
- arguments: true,
- await: true,
- break: true,
- case: true,
- catch: true,
- class: true,
- const: true,
- continue: true,
- debugger: true,
- default: true,
- delete: true,
- do: true,
- else: true,
- enum: true,
- eval: true,
- export: true,
- extends: true,
- false: true,
- finally: true,
- for: true,
- function: true,
- if: true,
- implements: true,
- import: true,
- in: true,
- instanceof: true,
- interface: true,
- let: true,
- new: true,
- null: true,
- package: true,
- private: true,
- protected: true,
- public: true,
- return: true,
- static: true,
- super: true,
- switch: true,
- this: true,
- throw: true,
- true: true,
- try: true,
- typeof: true,
- var: true,
- void: true,
- while: true,
- with: true,
- yield: true
- });
- function matchArity(fn, length) {
- let mockConstructor;
- switch (length) {
- case 1:
- mockConstructor = function (a) {
- return fn.apply(this, arguments);
- };
- break;
- case 2:
- mockConstructor = function (a, b) {
- return fn.apply(this, arguments);
- };
- break;
- case 3:
- mockConstructor = function (a, b, c) {
- return fn.apply(this, arguments);
- };
- break;
- case 4:
- mockConstructor = function (a, b, c, d) {
- return fn.apply(this, arguments);
- };
- break;
- case 5:
- mockConstructor = function (a, b, c, d, e) {
- return fn.apply(this, arguments);
- };
- break;
- case 6:
- mockConstructor = function (a, b, c, d, e, f) {
- return fn.apply(this, arguments);
- };
- break;
- case 7:
- mockConstructor = function (a, b, c, d, e, f, g) {
- return fn.apply(this, arguments);
- };
- break;
- case 8:
- mockConstructor = function (a, b, c, d, e, f, g, h) {
- return fn.apply(this, arguments);
- };
- break;
- case 9:
- mockConstructor = function (a, b, c, d, e, f, g, h, i) {
- return fn.apply(this, arguments);
- };
- break;
- default:
- mockConstructor = function () {
- return fn.apply(this, arguments);
- };
- break;
- }
- return mockConstructor;
- }
- function isA(typeName, value) {
- return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
- }
- function getType(ref) {
- if (isA('Function', ref) || isA('AsyncFunction', ref)) {
- return 'function';
- } else if (Array.isArray(ref)) {
- return 'array';
- } else if (isA('Object', ref)) {
- return 'object';
- } else if (isA('Number', ref) || isA('String', ref) || isA('Boolean', ref) || isA('Symbol', ref)) {
- return 'constant';
- } else if (isA('Map', ref) || isA('WeakMap', ref) || isA('Set', ref)) {
- return 'collection';
- } else if (isA('RegExp', ref)) {
- return 'regexp';
- } else if (ref === undefined) {
- return 'undefined';
- } else if (ref === null) {
- return 'null';
- } else {
- return null;
- }
- }
- function isReadonlyProp(object, prop) {
- return (prop === 'arguments' || prop === 'caller' || prop === 'callee' || prop === 'name' || prop === 'length') && (isA('Function', object) || isA('AsyncFunction', object)) || (prop === 'source' || prop === 'global' || prop === 'ignoreCase' || prop === 'multiline') && isA('RegExp', object);
- }
- function getSlots(object) {
- const slots = {};
- if (!object) {
- return [];
- }
- let parent = Object.getPrototypeOf(object);
- do {
- if (object === Object.getPrototypeOf(Function)) {
- break;
- }
- const ownNames = Object.getOwnPropertyNames(object);
- for (let i = 0; i < ownNames.length; i++) {
- const prop = ownNames[i];
- if (!isReadonlyProp(object, prop)) {
- const propDesc = Object.getOwnPropertyDescriptor(object, prop);
- if (!propDesc.get || object.__esModule) {
- slots[prop] = true;
- }
- }
- }
- object = parent;
- } while (object && (parent = Object.getPrototypeOf(object)) !== null);
- return Object.keys(slots);
- }
- function wrapAsyncParam(fn, asyncAction) {
- if (asyncAction === 'reject') {
- return value => fn(Promise.reject(value));
- }
- return value => fn(Promise.resolve(value));
- }
- class ModuleMockerClass {
- /**
- * @see README.md
- * @param global Global object of the test environment, used to create
- * mocks
- */
- constructor(global) {
- this._environmentGlobal = global;
- this._mockState = new WeakMap();
- this._mockConfigRegistry = new WeakMap();
- this._spyState = new Set();
- this.ModuleMocker = ModuleMockerClass;
- }
- _ensureMockConfig(f) {
- let config = this._mockConfigRegistry.get(f);
- if (!config) {
- config = this._defaultMockConfig();
- this._mockConfigRegistry.set(f, config);
- }
- return config;
- }
- _ensureMockState(f) {
- let state = this._mockState.get(f);
- if (!state) {
- state = this._defaultMockState();
- this._mockState.set(f, state);
- }
- return state;
- }
- _defaultMockConfig() {
- return {
- defaultReturnValue: undefined,
- isReturnValueLastSet: false,
- mockImpl: undefined,
- mockName: 'jest.fn()',
- specificMockImpls: [],
- specificReturnValues: []
- };
- }
- _defaultMockState() {
- return {
- calls: [],
- instances: [],
- timestamps: []
- };
- }
- _makeComponent(metadata, restore) {
- if (metadata.type === 'object') {
- return new this._environmentGlobal.Object();
- } else if (metadata.type === 'array') {
- return new this._environmentGlobal.Array();
- } else if (metadata.type === 'regexp') {
- return new this._environmentGlobal.RegExp('');
- } else if (metadata.type === 'constant' || metadata.type === 'collection' || metadata.type === 'null' || metadata.type === 'undefined') {
- return metadata.value;
- } else if (metadata.type === 'function') {
- /* eslint-disable prefer-const */
- let f;
- /* eslint-enable prefer-const */
- const prototype = metadata.members && metadata.members.prototype && metadata.members.prototype.members || {};
- const prototypeSlots = getSlots(prototype);
- const mocker = this;
- const mockConstructor = matchArity(function () {
- const mockState = mocker._ensureMockState(f);
- const mockConfig = mocker._ensureMockConfig(f);
- mockState.instances.push(this);
- mockState.calls.push(Array.prototype.slice.call(arguments));
- mockState.timestamps.push(Date.now());
- if (this instanceof f) {
- // This is probably being called as a constructor
- prototypeSlots.forEach(slot => {
- // Copy prototype methods to the instance to make
- // it easier to interact with mock instance call and
- // return values
- if (prototype[slot].type === 'function') {
- const protoImpl = this[slot];
- this[slot] = mocker.generateFromMetadata(prototype[slot]);
- this[slot]._protoImpl = protoImpl;
- }
- });
- // Run the mock constructor implementation
- const mockImpl = mockConfig.specificMockImpls.length ? mockConfig.specificMockImpls.shift() : mockConfig.mockImpl;
- return mockImpl && mockImpl.apply(this, arguments);
- }
- const returnValue = mockConfig.defaultReturnValue;
- // If return value is last set, either specific or default, i.e.
- // mockReturnValueOnce()/mockReturnValue() is called and no
- // mockImplementationOnce()/mockImplementation() is called after that.
- // use the set return value.
- if (mockConfig.specificReturnValues.length) {
- return mockConfig.specificReturnValues.shift();
- }
- if (mockConfig.isReturnValueLastSet) {
- return mockConfig.defaultReturnValue;
- }
- // If mockImplementationOnce()/mockImplementation() is last set,
- // or specific return values are used up, use the mock implementation.
- let specificMockImpl;
- if (returnValue === undefined) {
- specificMockImpl = mockConfig.specificMockImpls.shift();
- if (specificMockImpl === undefined) {
- specificMockImpl = mockConfig.mockImpl;
- }
- if (specificMockImpl) {
- return specificMockImpl.apply(this, arguments);
- }
- }
- // Otherwise use prototype implementation
- if (returnValue === undefined && f._protoImpl) {
- return f._protoImpl.apply(this, arguments);
- }
- return returnValue;
- }, metadata.length || 0);
- f = this._createMockFunction(metadata, mockConstructor);
- f._isMockFunction = true;
- f.getMockImplementation = () => this._ensureMockConfig(f).mockImpl;
- if (typeof restore === 'function') {
- this._spyState.add(restore);
- }
- this._mockState.set(f, this._defaultMockState());
- this._mockConfigRegistry.set(f, this._defaultMockConfig());
- // $FlowFixMe - defineProperty getters not supported
- Object.defineProperty(f, 'mock', {
- configurable: false,
- enumerable: true,
- get: () => this._ensureMockState(f),
- set: val => this._mockState.set(f, val)
- });
- f.mockClear = () => {
- this._mockState.delete(f);
- return f;
- };
- f.mockReset = () => {
- this._mockState.delete(f);
- this._mockConfigRegistry.delete(f);
- return f;
- };
- f.mockReturnValueOnce = value => {
- // next function call will return this value or default return value
- const mockConfig = this._ensureMockConfig(f);
- mockConfig.specificReturnValues.push(value);
- return f;
- };
- f.mockResolvedValueOnce = wrapAsyncParam(f.mockReturnValueOnce, 'resolve');
- f.mockRejectedValueOnce = wrapAsyncParam(f.mockReturnValueOnce, 'reject');
- f.mockReturnValue = value => {
- // next function call will return specified return value or this one
- const mockConfig = this._ensureMockConfig(f);
- mockConfig.isReturnValueLastSet = true;
- mockConfig.defaultReturnValue = value;
- return f;
- };
- f.mockResolvedValue = wrapAsyncParam(f.mockReturnValue, 'resolve');
- f.mockRejectedValue = wrapAsyncParam(f.mockReturnValue, 'reject');
- f.mockImplementationOnce = fn => {
- // next function call will use this mock implementation return value
- // or default mock implementation return value
- const mockConfig = this._ensureMockConfig(f);
- mockConfig.isReturnValueLastSet = false;
- mockConfig.specificMockImpls.push(fn);
- return f;
- };
- f.mockImplementation = fn => {
- // next function call will use mock implementation return value
- const mockConfig = this._ensureMockConfig(f);
- mockConfig.isReturnValueLastSet = false;
- mockConfig.defaultReturnValue = undefined;
- mockConfig.mockImpl = fn;
- return f;
- };
- f.mockReturnThis = () => f.mockImplementation(function () {
- return this;
- });
- f.mockName = name => {
- if (name) {
- const mockConfig = this._ensureMockConfig(f);
- mockConfig.mockName = name;
- }
- return f;
- };
- f.getMockName = () => {
- const mockConfig = this._ensureMockConfig(f);
- return mockConfig.mockName || 'jest.fn()';
- };
- if (metadata.mockImpl) {
- f.mockImplementation(metadata.mockImpl);
- }
- f.mockRestore = restore ? restore : () => {};
- return f;
- } else {
- const unknownType = metadata.type || 'undefined type';
- throw new Error('Unrecognized type ' + unknownType);
- }
- }
- _createMockFunction(metadata, mockConstructor) {
- let name = metadata.name;
- if (!name) {
- return mockConstructor;
- }
- // Preserve `name` property of mocked function.
- const boundFunctionPrefix = 'bound ';
- let bindCall = '';
- // if-do-while for perf reasons. The common case is for the if to fail.
- if (name && name.startsWith(boundFunctionPrefix)) {
- do {
- name = name.substring(boundFunctionPrefix.length);
- // Call bind() just to alter the function name.
- bindCall = '.bind(null)';
- } while (name && name.startsWith(boundFunctionPrefix));
- }
- // Special case functions named `mockConstructor` to guard for infinite
- // loops.
- if (name === MOCK_CONSTRUCTOR_NAME) {
- return mockConstructor;
- }
- // It's a syntax error to define functions with a reserved keyword
- // as name.
- if (RESERVED_KEYWORDS[name]) {
- name = '$' + name;
- }
- // It's also a syntax error to define a function with a reserved character
- // as part of it's name.
- if (FUNCTION_NAME_RESERVED_PATTERN.test(name)) {
- name = name.replace(FUNCTION_NAME_RESERVED_REPLACE, '$');
- }
- const body = 'return function ' + name + '() {' + 'return ' + MOCK_CONSTRUCTOR_NAME + '.apply(this,arguments);' + '}' + bindCall;
- const createConstructor = new this._environmentGlobal.Function(MOCK_CONSTRUCTOR_NAME, body);
- return createConstructor(mockConstructor);
- }
- _generateMock(metadata, callbacks, refs) {
- const mock = this._makeComponent(metadata);
- if (metadata.refID != null) {
- refs[metadata.refID] = mock;
- }
- getSlots(metadata.members).forEach(slot => {
- const slotMetadata = metadata.members && metadata.members[slot] || {};
- if (slotMetadata.ref != null) {
- callbacks.push(() => mock[slot] = refs[slotMetadata.ref]);
- } else {
- mock[slot] = this._generateMock(slotMetadata, callbacks, refs);
- }
- });
- if (metadata.type !== 'undefined' && metadata.type !== 'null' && mock.prototype) {
- mock.prototype.constructor = mock;
- }
- return mock;
- }
- /**
- * @see README.md
- * @param metadata Metadata for the mock in the schema returned by the
- * getMetadata method of this module.
- */
- generateFromMetadata(_metadata) {
- const callbacks = [];
- const refs = {};
- const mock = this._generateMock(_metadata, callbacks, refs);
- callbacks.forEach(setter => setter());
- return mock;
- }
- /**
- * @see README.md
- * @param component The component for which to retrieve metadata.
- */
- getMetadata(component, _refs) {
- const refs = _refs || new Map();
- const ref = refs.get(component);
- if (ref != null) {
- return { ref };
- }
- const type = getType(component);
- if (!type) {
- return null;
- }
- const metadata = { type };
- if (type === 'constant' || type === 'collection' || type === 'undefined' || type === 'null') {
- metadata.value = component;
- return metadata;
- } else if (type === 'function') {
- metadata.name = component.name;
- if (component._isMockFunction) {
- metadata.mockImpl = component.getMockImplementation();
- }
- }
- metadata.refID = refs.size;
- refs.set(component, metadata.refID);
- let members = null;
- // Leave arrays alone
- if (type !== 'array') {
- if (type !== 'undefined') {
- getSlots(component).forEach(slot => {
- if (type === 'function' && component._isMockFunction && slot.match(/^mock/)) {
- return;
- }
- if (!component.hasOwnProperty && component[slot] !== undefined || component.hasOwnProperty && component.hasOwnProperty(slot) || type === 'object' && component[slot] != Object.prototype[slot]) {
- const slotMetadata = this.getMetadata(component[slot], refs);
- if (slotMetadata) {
- if (!members) {
- members = {};
- }
- members[slot] = slotMetadata;
- }
- }
- });
- }
- // If component is native code function, prototype might be undefined
- if (type === 'function' && component.prototype) {
- const prototype = this.getMetadata(component.prototype, refs);
- if (prototype && prototype.members) {
- if (!members) {
- members = {};
- }
- members.prototype = prototype;
- }
- }
- }
- if (members) {
- metadata.members = members;
- }
- return metadata;
- }
- isMockFunction(fn) {
- return !!(fn && fn._isMockFunction);
- }
- fn(implementation) {
- const length = implementation ? implementation.length : 0;
- const fn = this._makeComponent({ length, type: 'function' });
- if (implementation) {
- fn.mockImplementation(implementation);
- }
- return fn;
- }
- spyOn(object, methodName, accessType) {
- if (accessType) {
- return this._spyOnProperty(object, methodName, accessType);
- }
- if (typeof object !== 'object' && typeof object !== 'function') {
- throw new Error('Cannot spyOn on a primitive value; ' + this._typeOf(object) + ' given');
- }
- const original = object[methodName];
- if (!this.isMockFunction(original)) {
- if (typeof original !== 'function') {
- throw new Error('Cannot spy the ' + methodName + ' property because it is not a function; ' + this._typeOf(original) + ' given instead');
- }
- object[methodName] = this._makeComponent({ type: 'function' }, () => {
- object[methodName] = original;
- });
- object[methodName].mockImplementation(function () {
- return original.apply(this, arguments);
- });
- }
- return object[methodName];
- }
- _spyOnProperty(obj, propertyName) {
- let accessType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'get';
- if (typeof obj !== 'object' && typeof obj !== 'function') {
- throw new Error('Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given');
- }
- if (!obj) {
- throw new Error('spyOn could not find an object to spy upon for ' + propertyName + '');
- }
- if (!propertyName) {
- throw new Error('No property name supplied');
- }
- const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
- if (!descriptor) {
- throw new Error(propertyName + ' property does not exist');
- }
- if (!descriptor.configurable) {
- throw new Error(propertyName + ' is not declared configurable');
- }
- if (!descriptor[accessType]) {
- throw new Error('Property ' + propertyName + ' does not have access type ' + accessType);
- }
- const original = descriptor[accessType];
- if (!this.isMockFunction(original)) {
- if (typeof original !== 'function') {
- throw new Error('Cannot spy the ' + propertyName + ' property because it is not a function; ' + this._typeOf(original) + ' given instead');
- }
- descriptor[accessType] = this._makeComponent({ type: 'function' }, () => {
- descriptor[accessType] = original;
- Object.defineProperty(obj, propertyName, descriptor);
- });
- descriptor[accessType].mockImplementation(function () {
- return original.apply(this, arguments);
- });
- }
- Object.defineProperty(obj, propertyName, descriptor);
- return descriptor[accessType];
- }
- clearAllMocks() {
- this._mockState = new WeakMap();
- }
- resetAllMocks() {
- this._mockConfigRegistry = new WeakMap();
- this._mockState = new WeakMap();
- }
- restoreAllMocks() {
- this._spyState.forEach(restore => restore());
- this._spyState = new Set();
- }
- _typeOf(value) {
- return value == null ? '' + value : typeof value;
- }
- }
- module.exports = new ModuleMockerClass(global);
|