index.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. 'use strict';
  2. const MOCK_CONSTRUCTOR_NAME = 'mockConstructor'; /**
  3. * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  4. *
  5. * This source code is licensed under the MIT license found in the
  6. * LICENSE file in the root directory of this source tree.
  7. *
  8. *
  9. */
  10. const FUNCTION_NAME_RESERVED_PATTERN = /[\s!-\/:-@\[-`{-~]/;
  11. const FUNCTION_NAME_RESERVED_REPLACE = new RegExp(FUNCTION_NAME_RESERVED_PATTERN.source, 'g');
  12. const RESERVED_KEYWORDS = Object.assign(Object.create(null), {
  13. arguments: true,
  14. await: true,
  15. break: true,
  16. case: true,
  17. catch: true,
  18. class: true,
  19. const: true,
  20. continue: true,
  21. debugger: true,
  22. default: true,
  23. delete: true,
  24. do: true,
  25. else: true,
  26. enum: true,
  27. eval: true,
  28. export: true,
  29. extends: true,
  30. false: true,
  31. finally: true,
  32. for: true,
  33. function: true,
  34. if: true,
  35. implements: true,
  36. import: true,
  37. in: true,
  38. instanceof: true,
  39. interface: true,
  40. let: true,
  41. new: true,
  42. null: true,
  43. package: true,
  44. private: true,
  45. protected: true,
  46. public: true,
  47. return: true,
  48. static: true,
  49. super: true,
  50. switch: true,
  51. this: true,
  52. throw: true,
  53. true: true,
  54. try: true,
  55. typeof: true,
  56. var: true,
  57. void: true,
  58. while: true,
  59. with: true,
  60. yield: true
  61. });
  62. function matchArity(fn, length) {
  63. let mockConstructor;
  64. switch (length) {
  65. case 1:
  66. mockConstructor = function (a) {
  67. return fn.apply(this, arguments);
  68. };
  69. break;
  70. case 2:
  71. mockConstructor = function (a, b) {
  72. return fn.apply(this, arguments);
  73. };
  74. break;
  75. case 3:
  76. mockConstructor = function (a, b, c) {
  77. return fn.apply(this, arguments);
  78. };
  79. break;
  80. case 4:
  81. mockConstructor = function (a, b, c, d) {
  82. return fn.apply(this, arguments);
  83. };
  84. break;
  85. case 5:
  86. mockConstructor = function (a, b, c, d, e) {
  87. return fn.apply(this, arguments);
  88. };
  89. break;
  90. case 6:
  91. mockConstructor = function (a, b, c, d, e, f) {
  92. return fn.apply(this, arguments);
  93. };
  94. break;
  95. case 7:
  96. mockConstructor = function (a, b, c, d, e, f, g) {
  97. return fn.apply(this, arguments);
  98. };
  99. break;
  100. case 8:
  101. mockConstructor = function (a, b, c, d, e, f, g, h) {
  102. return fn.apply(this, arguments);
  103. };
  104. break;
  105. case 9:
  106. mockConstructor = function (a, b, c, d, e, f, g, h, i) {
  107. return fn.apply(this, arguments);
  108. };
  109. break;
  110. default:
  111. mockConstructor = function () {
  112. return fn.apply(this, arguments);
  113. };
  114. break;
  115. }
  116. return mockConstructor;
  117. }
  118. function isA(typeName, value) {
  119. return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
  120. }
  121. function getType(ref) {
  122. if (isA('Function', ref) || isA('AsyncFunction', ref)) {
  123. return 'function';
  124. } else if (Array.isArray(ref)) {
  125. return 'array';
  126. } else if (isA('Object', ref)) {
  127. return 'object';
  128. } else if (isA('Number', ref) || isA('String', ref) || isA('Boolean', ref) || isA('Symbol', ref)) {
  129. return 'constant';
  130. } else if (isA('Map', ref) || isA('WeakMap', ref) || isA('Set', ref)) {
  131. return 'collection';
  132. } else if (isA('RegExp', ref)) {
  133. return 'regexp';
  134. } else if (ref === undefined) {
  135. return 'undefined';
  136. } else if (ref === null) {
  137. return 'null';
  138. } else {
  139. return null;
  140. }
  141. }
  142. function isReadonlyProp(object, prop) {
  143. 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);
  144. }
  145. function getSlots(object) {
  146. const slots = {};
  147. if (!object) {
  148. return [];
  149. }
  150. let parent = Object.getPrototypeOf(object);
  151. do {
  152. if (object === Object.getPrototypeOf(Function)) {
  153. break;
  154. }
  155. const ownNames = Object.getOwnPropertyNames(object);
  156. for (let i = 0; i < ownNames.length; i++) {
  157. const prop = ownNames[i];
  158. if (!isReadonlyProp(object, prop)) {
  159. const propDesc = Object.getOwnPropertyDescriptor(object, prop);
  160. if (!propDesc.get || object.__esModule) {
  161. slots[prop] = true;
  162. }
  163. }
  164. }
  165. object = parent;
  166. } while (object && (parent = Object.getPrototypeOf(object)) !== null);
  167. return Object.keys(slots);
  168. }
  169. function wrapAsyncParam(fn, asyncAction) {
  170. if (asyncAction === 'reject') {
  171. return value => fn(Promise.reject(value));
  172. }
  173. return value => fn(Promise.resolve(value));
  174. }
  175. class ModuleMockerClass {
  176. /**
  177. * @see README.md
  178. * @param global Global object of the test environment, used to create
  179. * mocks
  180. */
  181. constructor(global) {
  182. this._environmentGlobal = global;
  183. this._mockState = new WeakMap();
  184. this._mockConfigRegistry = new WeakMap();
  185. this._spyState = new Set();
  186. this.ModuleMocker = ModuleMockerClass;
  187. }
  188. _ensureMockConfig(f) {
  189. let config = this._mockConfigRegistry.get(f);
  190. if (!config) {
  191. config = this._defaultMockConfig();
  192. this._mockConfigRegistry.set(f, config);
  193. }
  194. return config;
  195. }
  196. _ensureMockState(f) {
  197. let state = this._mockState.get(f);
  198. if (!state) {
  199. state = this._defaultMockState();
  200. this._mockState.set(f, state);
  201. }
  202. return state;
  203. }
  204. _defaultMockConfig() {
  205. return {
  206. defaultReturnValue: undefined,
  207. isReturnValueLastSet: false,
  208. mockImpl: undefined,
  209. mockName: 'jest.fn()',
  210. specificMockImpls: [],
  211. specificReturnValues: []
  212. };
  213. }
  214. _defaultMockState() {
  215. return {
  216. calls: [],
  217. instances: [],
  218. timestamps: []
  219. };
  220. }
  221. _makeComponent(metadata, restore) {
  222. if (metadata.type === 'object') {
  223. return new this._environmentGlobal.Object();
  224. } else if (metadata.type === 'array') {
  225. return new this._environmentGlobal.Array();
  226. } else if (metadata.type === 'regexp') {
  227. return new this._environmentGlobal.RegExp('');
  228. } else if (metadata.type === 'constant' || metadata.type === 'collection' || metadata.type === 'null' || metadata.type === 'undefined') {
  229. return metadata.value;
  230. } else if (metadata.type === 'function') {
  231. /* eslint-disable prefer-const */
  232. let f;
  233. /* eslint-enable prefer-const */
  234. const prototype = metadata.members && metadata.members.prototype && metadata.members.prototype.members || {};
  235. const prototypeSlots = getSlots(prototype);
  236. const mocker = this;
  237. const mockConstructor = matchArity(function () {
  238. const mockState = mocker._ensureMockState(f);
  239. const mockConfig = mocker._ensureMockConfig(f);
  240. mockState.instances.push(this);
  241. mockState.calls.push(Array.prototype.slice.call(arguments));
  242. mockState.timestamps.push(Date.now());
  243. if (this instanceof f) {
  244. // This is probably being called as a constructor
  245. prototypeSlots.forEach(slot => {
  246. // Copy prototype methods to the instance to make
  247. // it easier to interact with mock instance call and
  248. // return values
  249. if (prototype[slot].type === 'function') {
  250. const protoImpl = this[slot];
  251. this[slot] = mocker.generateFromMetadata(prototype[slot]);
  252. this[slot]._protoImpl = protoImpl;
  253. }
  254. });
  255. // Run the mock constructor implementation
  256. const mockImpl = mockConfig.specificMockImpls.length ? mockConfig.specificMockImpls.shift() : mockConfig.mockImpl;
  257. return mockImpl && mockImpl.apply(this, arguments);
  258. }
  259. const returnValue = mockConfig.defaultReturnValue;
  260. // If return value is last set, either specific or default, i.e.
  261. // mockReturnValueOnce()/mockReturnValue() is called and no
  262. // mockImplementationOnce()/mockImplementation() is called after that.
  263. // use the set return value.
  264. if (mockConfig.specificReturnValues.length) {
  265. return mockConfig.specificReturnValues.shift();
  266. }
  267. if (mockConfig.isReturnValueLastSet) {
  268. return mockConfig.defaultReturnValue;
  269. }
  270. // If mockImplementationOnce()/mockImplementation() is last set,
  271. // or specific return values are used up, use the mock implementation.
  272. let specificMockImpl;
  273. if (returnValue === undefined) {
  274. specificMockImpl = mockConfig.specificMockImpls.shift();
  275. if (specificMockImpl === undefined) {
  276. specificMockImpl = mockConfig.mockImpl;
  277. }
  278. if (specificMockImpl) {
  279. return specificMockImpl.apply(this, arguments);
  280. }
  281. }
  282. // Otherwise use prototype implementation
  283. if (returnValue === undefined && f._protoImpl) {
  284. return f._protoImpl.apply(this, arguments);
  285. }
  286. return returnValue;
  287. }, metadata.length || 0);
  288. f = this._createMockFunction(metadata, mockConstructor);
  289. f._isMockFunction = true;
  290. f.getMockImplementation = () => this._ensureMockConfig(f).mockImpl;
  291. if (typeof restore === 'function') {
  292. this._spyState.add(restore);
  293. }
  294. this._mockState.set(f, this._defaultMockState());
  295. this._mockConfigRegistry.set(f, this._defaultMockConfig());
  296. // $FlowFixMe - defineProperty getters not supported
  297. Object.defineProperty(f, 'mock', {
  298. configurable: false,
  299. enumerable: true,
  300. get: () => this._ensureMockState(f),
  301. set: val => this._mockState.set(f, val)
  302. });
  303. f.mockClear = () => {
  304. this._mockState.delete(f);
  305. return f;
  306. };
  307. f.mockReset = () => {
  308. this._mockState.delete(f);
  309. this._mockConfigRegistry.delete(f);
  310. return f;
  311. };
  312. f.mockReturnValueOnce = value => {
  313. // next function call will return this value or default return value
  314. const mockConfig = this._ensureMockConfig(f);
  315. mockConfig.specificReturnValues.push(value);
  316. return f;
  317. };
  318. f.mockResolvedValueOnce = wrapAsyncParam(f.mockReturnValueOnce, 'resolve');
  319. f.mockRejectedValueOnce = wrapAsyncParam(f.mockReturnValueOnce, 'reject');
  320. f.mockReturnValue = value => {
  321. // next function call will return specified return value or this one
  322. const mockConfig = this._ensureMockConfig(f);
  323. mockConfig.isReturnValueLastSet = true;
  324. mockConfig.defaultReturnValue = value;
  325. return f;
  326. };
  327. f.mockResolvedValue = wrapAsyncParam(f.mockReturnValue, 'resolve');
  328. f.mockRejectedValue = wrapAsyncParam(f.mockReturnValue, 'reject');
  329. f.mockImplementationOnce = fn => {
  330. // next function call will use this mock implementation return value
  331. // or default mock implementation return value
  332. const mockConfig = this._ensureMockConfig(f);
  333. mockConfig.isReturnValueLastSet = false;
  334. mockConfig.specificMockImpls.push(fn);
  335. return f;
  336. };
  337. f.mockImplementation = fn => {
  338. // next function call will use mock implementation return value
  339. const mockConfig = this._ensureMockConfig(f);
  340. mockConfig.isReturnValueLastSet = false;
  341. mockConfig.defaultReturnValue = undefined;
  342. mockConfig.mockImpl = fn;
  343. return f;
  344. };
  345. f.mockReturnThis = () => f.mockImplementation(function () {
  346. return this;
  347. });
  348. f.mockName = name => {
  349. if (name) {
  350. const mockConfig = this._ensureMockConfig(f);
  351. mockConfig.mockName = name;
  352. }
  353. return f;
  354. };
  355. f.getMockName = () => {
  356. const mockConfig = this._ensureMockConfig(f);
  357. return mockConfig.mockName || 'jest.fn()';
  358. };
  359. if (metadata.mockImpl) {
  360. f.mockImplementation(metadata.mockImpl);
  361. }
  362. f.mockRestore = restore ? restore : () => {};
  363. return f;
  364. } else {
  365. const unknownType = metadata.type || 'undefined type';
  366. throw new Error('Unrecognized type ' + unknownType);
  367. }
  368. }
  369. _createMockFunction(metadata, mockConstructor) {
  370. let name = metadata.name;
  371. if (!name) {
  372. return mockConstructor;
  373. }
  374. // Preserve `name` property of mocked function.
  375. const boundFunctionPrefix = 'bound ';
  376. let bindCall = '';
  377. // if-do-while for perf reasons. The common case is for the if to fail.
  378. if (name && name.startsWith(boundFunctionPrefix)) {
  379. do {
  380. name = name.substring(boundFunctionPrefix.length);
  381. // Call bind() just to alter the function name.
  382. bindCall = '.bind(null)';
  383. } while (name && name.startsWith(boundFunctionPrefix));
  384. }
  385. // Special case functions named `mockConstructor` to guard for infinite
  386. // loops.
  387. if (name === MOCK_CONSTRUCTOR_NAME) {
  388. return mockConstructor;
  389. }
  390. // It's a syntax error to define functions with a reserved keyword
  391. // as name.
  392. if (RESERVED_KEYWORDS[name]) {
  393. name = '$' + name;
  394. }
  395. // It's also a syntax error to define a function with a reserved character
  396. // as part of it's name.
  397. if (FUNCTION_NAME_RESERVED_PATTERN.test(name)) {
  398. name = name.replace(FUNCTION_NAME_RESERVED_REPLACE, '$');
  399. }
  400. const body = 'return function ' + name + '() {' + 'return ' + MOCK_CONSTRUCTOR_NAME + '.apply(this,arguments);' + '}' + bindCall;
  401. const createConstructor = new this._environmentGlobal.Function(MOCK_CONSTRUCTOR_NAME, body);
  402. return createConstructor(mockConstructor);
  403. }
  404. _generateMock(metadata, callbacks, refs) {
  405. const mock = this._makeComponent(metadata);
  406. if (metadata.refID != null) {
  407. refs[metadata.refID] = mock;
  408. }
  409. getSlots(metadata.members).forEach(slot => {
  410. const slotMetadata = metadata.members && metadata.members[slot] || {};
  411. if (slotMetadata.ref != null) {
  412. callbacks.push(() => mock[slot] = refs[slotMetadata.ref]);
  413. } else {
  414. mock[slot] = this._generateMock(slotMetadata, callbacks, refs);
  415. }
  416. });
  417. if (metadata.type !== 'undefined' && metadata.type !== 'null' && mock.prototype) {
  418. mock.prototype.constructor = mock;
  419. }
  420. return mock;
  421. }
  422. /**
  423. * @see README.md
  424. * @param metadata Metadata for the mock in the schema returned by the
  425. * getMetadata method of this module.
  426. */
  427. generateFromMetadata(_metadata) {
  428. const callbacks = [];
  429. const refs = {};
  430. const mock = this._generateMock(_metadata, callbacks, refs);
  431. callbacks.forEach(setter => setter());
  432. return mock;
  433. }
  434. /**
  435. * @see README.md
  436. * @param component The component for which to retrieve metadata.
  437. */
  438. getMetadata(component, _refs) {
  439. const refs = _refs || new Map();
  440. const ref = refs.get(component);
  441. if (ref != null) {
  442. return { ref };
  443. }
  444. const type = getType(component);
  445. if (!type) {
  446. return null;
  447. }
  448. const metadata = { type };
  449. if (type === 'constant' || type === 'collection' || type === 'undefined' || type === 'null') {
  450. metadata.value = component;
  451. return metadata;
  452. } else if (type === 'function') {
  453. metadata.name = component.name;
  454. if (component._isMockFunction) {
  455. metadata.mockImpl = component.getMockImplementation();
  456. }
  457. }
  458. metadata.refID = refs.size;
  459. refs.set(component, metadata.refID);
  460. let members = null;
  461. // Leave arrays alone
  462. if (type !== 'array') {
  463. if (type !== 'undefined') {
  464. getSlots(component).forEach(slot => {
  465. if (type === 'function' && component._isMockFunction && slot.match(/^mock/)) {
  466. return;
  467. }
  468. if (!component.hasOwnProperty && component[slot] !== undefined || component.hasOwnProperty && component.hasOwnProperty(slot) || type === 'object' && component[slot] != Object.prototype[slot]) {
  469. const slotMetadata = this.getMetadata(component[slot], refs);
  470. if (slotMetadata) {
  471. if (!members) {
  472. members = {};
  473. }
  474. members[slot] = slotMetadata;
  475. }
  476. }
  477. });
  478. }
  479. // If component is native code function, prototype might be undefined
  480. if (type === 'function' && component.prototype) {
  481. const prototype = this.getMetadata(component.prototype, refs);
  482. if (prototype && prototype.members) {
  483. if (!members) {
  484. members = {};
  485. }
  486. members.prototype = prototype;
  487. }
  488. }
  489. }
  490. if (members) {
  491. metadata.members = members;
  492. }
  493. return metadata;
  494. }
  495. isMockFunction(fn) {
  496. return !!(fn && fn._isMockFunction);
  497. }
  498. fn(implementation) {
  499. const length = implementation ? implementation.length : 0;
  500. const fn = this._makeComponent({ length, type: 'function' });
  501. if (implementation) {
  502. fn.mockImplementation(implementation);
  503. }
  504. return fn;
  505. }
  506. spyOn(object, methodName, accessType) {
  507. if (accessType) {
  508. return this._spyOnProperty(object, methodName, accessType);
  509. }
  510. if (typeof object !== 'object' && typeof object !== 'function') {
  511. throw new Error('Cannot spyOn on a primitive value; ' + this._typeOf(object) + ' given');
  512. }
  513. const original = object[methodName];
  514. if (!this.isMockFunction(original)) {
  515. if (typeof original !== 'function') {
  516. throw new Error('Cannot spy the ' + methodName + ' property because it is not a function; ' + this._typeOf(original) + ' given instead');
  517. }
  518. object[methodName] = this._makeComponent({ type: 'function' }, () => {
  519. object[methodName] = original;
  520. });
  521. object[methodName].mockImplementation(function () {
  522. return original.apply(this, arguments);
  523. });
  524. }
  525. return object[methodName];
  526. }
  527. _spyOnProperty(obj, propertyName) {
  528. let accessType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'get';
  529. if (typeof obj !== 'object' && typeof obj !== 'function') {
  530. throw new Error('Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given');
  531. }
  532. if (!obj) {
  533. throw new Error('spyOn could not find an object to spy upon for ' + propertyName + '');
  534. }
  535. if (!propertyName) {
  536. throw new Error('No property name supplied');
  537. }
  538. const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
  539. if (!descriptor) {
  540. throw new Error(propertyName + ' property does not exist');
  541. }
  542. if (!descriptor.configurable) {
  543. throw new Error(propertyName + ' is not declared configurable');
  544. }
  545. if (!descriptor[accessType]) {
  546. throw new Error('Property ' + propertyName + ' does not have access type ' + accessType);
  547. }
  548. const original = descriptor[accessType];
  549. if (!this.isMockFunction(original)) {
  550. if (typeof original !== 'function') {
  551. throw new Error('Cannot spy the ' + propertyName + ' property because it is not a function; ' + this._typeOf(original) + ' given instead');
  552. }
  553. descriptor[accessType] = this._makeComponent({ type: 'function' }, () => {
  554. descriptor[accessType] = original;
  555. Object.defineProperty(obj, propertyName, descriptor);
  556. });
  557. descriptor[accessType].mockImplementation(function () {
  558. return original.apply(this, arguments);
  559. });
  560. }
  561. Object.defineProperty(obj, propertyName, descriptor);
  562. return descriptor[accessType];
  563. }
  564. clearAllMocks() {
  565. this._mockState = new WeakMap();
  566. }
  567. resetAllMocks() {
  568. this._mockConfigRegistry = new WeakMap();
  569. this._mockState = new WeakMap();
  570. }
  571. restoreAllMocks() {
  572. this._spyState.forEach(restore => restore());
  573. this._spyState = new Set();
  574. }
  575. _typeOf(value) {
  576. return value == null ? '' + value : typeof value;
  577. }
  578. }
  579. module.exports = new ModuleMockerClass(global);