spyRegistry.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _CallTracker = _interopRequireDefault(require('./CallTracker'));
  7. var _SpyStrategy = _interopRequireDefault(require('./SpyStrategy'));
  8. var _createSpy = _interopRequireDefault(require('./createSpy'));
  9. function _interopRequireDefault(obj) {
  10. return obj && obj.__esModule ? obj : {default: obj};
  11. }
  12. function _defineProperty(obj, key, value) {
  13. if (key in obj) {
  14. Object.defineProperty(obj, key, {
  15. value: value,
  16. enumerable: true,
  17. configurable: true,
  18. writable: true
  19. });
  20. } else {
  21. obj[key] = value;
  22. }
  23. return obj;
  24. }
  25. const formatErrorMsg = (domain, usage) => {
  26. const usageDefinition = usage ? '\nUsage: ' + usage : '';
  27. return msg => domain + ' : ' + msg + usageDefinition;
  28. };
  29. function isSpy(putativeSpy) {
  30. if (!putativeSpy) {
  31. return false;
  32. }
  33. return (
  34. putativeSpy.and instanceof _SpyStrategy.default &&
  35. putativeSpy.calls instanceof _CallTracker.default
  36. );
  37. }
  38. const getErrorMsg = formatErrorMsg('<spyOn>', 'spyOn(<object>, <methodName>)');
  39. class SpyRegistry {
  40. constructor({currentSpies = () => []} = {}) {
  41. _defineProperty(this, 'allowRespy', void 0);
  42. _defineProperty(this, 'spyOn', void 0);
  43. _defineProperty(this, 'clearSpies', void 0);
  44. _defineProperty(this, 'respy', void 0);
  45. _defineProperty(this, '_spyOnProperty', void 0);
  46. this.allowRespy = function (allow) {
  47. this.respy = allow;
  48. };
  49. this.spyOn = (obj, methodName, accessType) => {
  50. if (accessType) {
  51. return this._spyOnProperty(obj, methodName, accessType);
  52. }
  53. if (obj === void 0) {
  54. throw new Error(
  55. getErrorMsg(
  56. 'could not find an object to spy upon for ' + methodName + '()'
  57. )
  58. );
  59. }
  60. if (methodName === void 0) {
  61. throw new Error(getErrorMsg('No method name supplied'));
  62. }
  63. if (obj[methodName] === void 0) {
  64. throw new Error(getErrorMsg(methodName + '() method does not exist'));
  65. }
  66. if (obj[methodName] && isSpy(obj[methodName])) {
  67. if (this.respy) {
  68. return obj[methodName];
  69. } else {
  70. throw new Error(
  71. getErrorMsg(methodName + ' has already been spied upon')
  72. );
  73. }
  74. }
  75. let descriptor;
  76. try {
  77. descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
  78. } catch {
  79. // IE 8 doesn't support `definePropery` on non-DOM nodes
  80. }
  81. if (descriptor && !(descriptor.writable || descriptor.set)) {
  82. throw new Error(
  83. getErrorMsg(methodName + ' is not declared writable or has no setter')
  84. );
  85. }
  86. const originalMethod = obj[methodName];
  87. const spiedMethod = (0, _createSpy.default)(methodName, originalMethod);
  88. let restoreStrategy;
  89. if (Object.prototype.hasOwnProperty.call(obj, methodName)) {
  90. restoreStrategy = function () {
  91. obj[methodName] = originalMethod;
  92. };
  93. } else {
  94. restoreStrategy = function () {
  95. if (!delete obj[methodName]) {
  96. obj[methodName] = originalMethod;
  97. }
  98. };
  99. }
  100. currentSpies().push({
  101. restoreObjectToOriginalState: restoreStrategy
  102. });
  103. obj[methodName] = spiedMethod;
  104. return spiedMethod;
  105. };
  106. this._spyOnProperty = function (obj, propertyName, accessType = 'get') {
  107. if (!obj) {
  108. throw new Error(
  109. getErrorMsg(
  110. 'could not find an object to spy upon for ' + propertyName
  111. )
  112. );
  113. }
  114. if (!propertyName) {
  115. throw new Error(getErrorMsg('No property name supplied'));
  116. }
  117. let descriptor;
  118. try {
  119. descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
  120. } catch {
  121. // IE 8 doesn't support `definePropery` on non-DOM nodes
  122. }
  123. if (!descriptor) {
  124. throw new Error(getErrorMsg(propertyName + ' property does not exist'));
  125. }
  126. if (!descriptor.configurable) {
  127. throw new Error(
  128. getErrorMsg(propertyName + ' is not declared configurable')
  129. );
  130. }
  131. if (!descriptor[accessType]) {
  132. throw new Error(
  133. getErrorMsg(
  134. 'Property ' +
  135. propertyName +
  136. ' does not have access type ' +
  137. accessType
  138. )
  139. );
  140. }
  141. if (obj[propertyName] && isSpy(obj[propertyName])) {
  142. if (this.respy) {
  143. return obj[propertyName];
  144. } else {
  145. throw new Error(
  146. getErrorMsg(propertyName + ' has already been spied upon')
  147. );
  148. }
  149. }
  150. const originalDescriptor = descriptor;
  151. const spiedProperty = (0, _createSpy.default)(
  152. propertyName,
  153. descriptor[accessType]
  154. );
  155. let restoreStrategy;
  156. if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
  157. restoreStrategy = function () {
  158. Object.defineProperty(obj, propertyName, originalDescriptor);
  159. };
  160. } else {
  161. restoreStrategy = function () {
  162. delete obj[propertyName];
  163. };
  164. }
  165. currentSpies().push({
  166. restoreObjectToOriginalState: restoreStrategy
  167. });
  168. const spiedDescriptor = {...descriptor, [accessType]: spiedProperty};
  169. Object.defineProperty(obj, propertyName, spiedDescriptor);
  170. return spiedProperty;
  171. };
  172. this.clearSpies = function () {
  173. const spies = currentSpies();
  174. for (let i = spies.length - 1; i >= 0; i--) {
  175. const spyEntry = spies[i];
  176. spyEntry.restoreObjectToOriginalState();
  177. }
  178. };
  179. }
  180. }
  181. exports.default = SpyRegistry;