jasmine-patch.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. 'use strict';
  2. /**
  3. * @license Angular v<unknown>
  4. * (c) 2010-2022 Google LLC. https://angular.io/
  5. * License: MIT
  6. */
  7. /// <reference types="jasmine"/>
  8. Zone.__load_patch('jasmine', (global, Zone, api) => {
  9. const __extends = function (d, b) {
  10. for (const p in b)
  11. if (b.hasOwnProperty(p))
  12. d[p] = b[p];
  13. function __() {
  14. this.constructor = d;
  15. }
  16. d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
  17. };
  18. // Patch jasmine's describe/it/beforeEach/afterEach functions so test code always runs
  19. // in a testZone (ProxyZone). (See: angular/zone.js#91 & angular/angular#10503)
  20. if (!Zone)
  21. throw new Error('Missing: zone.js');
  22. if (typeof jest !== 'undefined') {
  23. // return if jasmine is a light implementation inside jest
  24. // in this case, we are running inside jest not jasmine
  25. return;
  26. }
  27. if (typeof jasmine == 'undefined' || jasmine['__zone_patch__']) {
  28. return;
  29. }
  30. jasmine['__zone_patch__'] = true;
  31. const SyncTestZoneSpec = Zone['SyncTestZoneSpec'];
  32. const ProxyZoneSpec = Zone['ProxyZoneSpec'];
  33. if (!SyncTestZoneSpec)
  34. throw new Error('Missing: SyncTestZoneSpec');
  35. if (!ProxyZoneSpec)
  36. throw new Error('Missing: ProxyZoneSpec');
  37. const ambientZone = Zone.current;
  38. const symbol = Zone.__symbol__;
  39. // whether patch jasmine clock when in fakeAsync
  40. const disablePatchingJasmineClock = global[symbol('fakeAsyncDisablePatchingClock')] === true;
  41. // the original variable name fakeAsyncPatchLock is not accurate, so the name will be
  42. // fakeAsyncAutoFakeAsyncWhenClockPatched and if this enablePatchingJasmineClock is false, we also
  43. // automatically disable the auto jump into fakeAsync feature
  44. const enableAutoFakeAsyncWhenClockPatched = !disablePatchingJasmineClock &&
  45. ((global[symbol('fakeAsyncPatchLock')] === true) ||
  46. (global[symbol('fakeAsyncAutoFakeAsyncWhenClockPatched')] === true));
  47. const ignoreUnhandledRejection = global[symbol('ignoreUnhandledRejection')] === true;
  48. if (!ignoreUnhandledRejection) {
  49. const globalErrors = jasmine.GlobalErrors;
  50. if (globalErrors && !jasmine[symbol('GlobalErrors')]) {
  51. jasmine[symbol('GlobalErrors')] = globalErrors;
  52. jasmine.GlobalErrors = function () {
  53. const instance = new globalErrors();
  54. const originalInstall = instance.install;
  55. if (originalInstall && !instance[symbol('install')]) {
  56. instance[symbol('install')] = originalInstall;
  57. instance.install = function () {
  58. const isNode = typeof process !== 'undefined' && !!process.on;
  59. // Note: Jasmine checks internally if `process` and `process.on` is defined. Otherwise,
  60. // it installs the browser rejection handler through the `global.addEventListener`.
  61. // This code may be run in the browser environment where `process` is not defined, and
  62. // this will lead to a runtime exception since Webpack 5 removed automatic Node.js
  63. // polyfills. Note, that events are named differently, it's `unhandledRejection` in
  64. // Node.js and `unhandledrejection` in the browser.
  65. const originalHandlers = isNode ? process.listeners('unhandledRejection') :
  66. global.eventListeners('unhandledrejection');
  67. const result = originalInstall.apply(this, arguments);
  68. isNode ? process.removeAllListeners('unhandledRejection') :
  69. global.removeAllListeners('unhandledrejection');
  70. if (originalHandlers) {
  71. originalHandlers.forEach(handler => {
  72. if (isNode) {
  73. process.on('unhandledRejection', handler);
  74. }
  75. else {
  76. global.addEventListener('unhandledrejection', handler);
  77. }
  78. });
  79. }
  80. return result;
  81. };
  82. }
  83. return instance;
  84. };
  85. }
  86. }
  87. // Monkey patch all of the jasmine DSL so that each function runs in appropriate zone.
  88. const jasmineEnv = jasmine.getEnv();
  89. ['describe', 'xdescribe', 'fdescribe'].forEach(methodName => {
  90. let originalJasmineFn = jasmineEnv[methodName];
  91. jasmineEnv[methodName] = function (description, specDefinitions) {
  92. return originalJasmineFn.call(this, description, wrapDescribeInZone(description, specDefinitions));
  93. };
  94. });
  95. ['it', 'xit', 'fit'].forEach(methodName => {
  96. let originalJasmineFn = jasmineEnv[methodName];
  97. jasmineEnv[symbol(methodName)] = originalJasmineFn;
  98. jasmineEnv[methodName] = function (description, specDefinitions, timeout) {
  99. arguments[1] = wrapTestInZone(specDefinitions);
  100. return originalJasmineFn.apply(this, arguments);
  101. };
  102. });
  103. ['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => {
  104. let originalJasmineFn = jasmineEnv[methodName];
  105. jasmineEnv[symbol(methodName)] = originalJasmineFn;
  106. jasmineEnv[methodName] = function (specDefinitions, timeout) {
  107. arguments[0] = wrapTestInZone(specDefinitions);
  108. return originalJasmineFn.apply(this, arguments);
  109. };
  110. });
  111. if (!disablePatchingJasmineClock) {
  112. // need to patch jasmine.clock().mockDate and jasmine.clock().tick() so
  113. // they can work properly in FakeAsyncTest
  114. const originalClockFn = (jasmine[symbol('clock')] = jasmine['clock']);
  115. jasmine['clock'] = function () {
  116. const clock = originalClockFn.apply(this, arguments);
  117. if (!clock[symbol('patched')]) {
  118. clock[symbol('patched')] = symbol('patched');
  119. const originalTick = (clock[symbol('tick')] = clock.tick);
  120. clock.tick = function () {
  121. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  122. if (fakeAsyncZoneSpec) {
  123. return fakeAsyncZoneSpec.tick.apply(fakeAsyncZoneSpec, arguments);
  124. }
  125. return originalTick.apply(this, arguments);
  126. };
  127. const originalMockDate = (clock[symbol('mockDate')] = clock.mockDate);
  128. clock.mockDate = function () {
  129. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  130. if (fakeAsyncZoneSpec) {
  131. const dateTime = arguments.length > 0 ? arguments[0] : new Date();
  132. return fakeAsyncZoneSpec.setFakeBaseSystemTime.apply(fakeAsyncZoneSpec, dateTime && typeof dateTime.getTime === 'function' ? [dateTime.getTime()] :
  133. arguments);
  134. }
  135. return originalMockDate.apply(this, arguments);
  136. };
  137. // for auto go into fakeAsync feature, we need the flag to enable it
  138. if (enableAutoFakeAsyncWhenClockPatched) {
  139. ['install', 'uninstall'].forEach(methodName => {
  140. const originalClockFn = (clock[symbol(methodName)] = clock[methodName]);
  141. clock[methodName] = function () {
  142. const FakeAsyncTestZoneSpec = Zone['FakeAsyncTestZoneSpec'];
  143. if (FakeAsyncTestZoneSpec) {
  144. jasmine[symbol('clockInstalled')] = 'install' === methodName;
  145. return;
  146. }
  147. return originalClockFn.apply(this, arguments);
  148. };
  149. });
  150. }
  151. }
  152. return clock;
  153. };
  154. }
  155. // monkey patch createSpyObj to make properties enumerable to true
  156. if (!jasmine[Zone.__symbol__('createSpyObj')]) {
  157. const originalCreateSpyObj = jasmine.createSpyObj;
  158. jasmine[Zone.__symbol__('createSpyObj')] = originalCreateSpyObj;
  159. jasmine.createSpyObj = function () {
  160. const args = Array.prototype.slice.call(arguments);
  161. const propertyNames = args.length >= 3 ? args[2] : null;
  162. let spyObj;
  163. if (propertyNames) {
  164. const defineProperty = Object.defineProperty;
  165. Object.defineProperty = function (obj, p, attributes) {
  166. return defineProperty.call(this, obj, p, { ...attributes, configurable: true, enumerable: true });
  167. };
  168. try {
  169. spyObj = originalCreateSpyObj.apply(this, args);
  170. }
  171. finally {
  172. Object.defineProperty = defineProperty;
  173. }
  174. }
  175. else {
  176. spyObj = originalCreateSpyObj.apply(this, args);
  177. }
  178. return spyObj;
  179. };
  180. }
  181. /**
  182. * Gets a function wrapping the body of a Jasmine `describe` block to execute in a
  183. * synchronous-only zone.
  184. */
  185. function wrapDescribeInZone(description, describeBody) {
  186. return function () {
  187. // Create a synchronous-only zone in which to run `describe` blocks in order to raise an
  188. // error if any asynchronous operations are attempted inside of a `describe`.
  189. const syncZone = ambientZone.fork(new SyncTestZoneSpec(`jasmine.describe#${description}`));
  190. return syncZone.run(describeBody, this, arguments);
  191. };
  192. }
  193. function runInTestZone(testBody, applyThis, queueRunner, done) {
  194. const isClockInstalled = !!jasmine[symbol('clockInstalled')];
  195. queueRunner.testProxyZoneSpec;
  196. const testProxyZone = queueRunner.testProxyZone;
  197. if (isClockInstalled && enableAutoFakeAsyncWhenClockPatched) {
  198. // auto run a fakeAsync
  199. const fakeAsyncModule = Zone[Zone.__symbol__('fakeAsyncTest')];
  200. if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') {
  201. testBody = fakeAsyncModule.fakeAsync(testBody);
  202. }
  203. }
  204. if (done) {
  205. return testProxyZone.run(testBody, applyThis, [done]);
  206. }
  207. else {
  208. return testProxyZone.run(testBody, applyThis);
  209. }
  210. }
  211. /**
  212. * Gets a function wrapping the body of a Jasmine `it/beforeEach/afterEach` block to
  213. * execute in a ProxyZone zone.
  214. * This will run in `testProxyZone`. The `testProxyZone` will be reset by the `ZoneQueueRunner`
  215. */
  216. function wrapTestInZone(testBody) {
  217. // The `done` callback is only passed through if the function expects at least one argument.
  218. // Note we have to make a function with correct number of arguments, otherwise jasmine will
  219. // think that all functions are sync or async.
  220. return (testBody && (testBody.length ? function (done) {
  221. return runInTestZone(testBody, this, this.queueRunner, done);
  222. } : function () {
  223. return runInTestZone(testBody, this, this.queueRunner);
  224. }));
  225. }
  226. const QueueRunner = jasmine.QueueRunner;
  227. jasmine.QueueRunner = (function (_super) {
  228. __extends(ZoneQueueRunner, _super);
  229. function ZoneQueueRunner(attrs) {
  230. if (attrs.onComplete) {
  231. attrs.onComplete = (fn => () => {
  232. // All functions are done, clear the test zone.
  233. this.testProxyZone = null;
  234. this.testProxyZoneSpec = null;
  235. ambientZone.scheduleMicroTask('jasmine.onComplete', fn);
  236. })(attrs.onComplete);
  237. }
  238. const nativeSetTimeout = global[Zone.__symbol__('setTimeout')];
  239. const nativeClearTimeout = global[Zone.__symbol__('clearTimeout')];
  240. if (nativeSetTimeout) {
  241. // should run setTimeout inside jasmine outside of zone
  242. attrs.timeout = {
  243. setTimeout: nativeSetTimeout ? nativeSetTimeout : global.setTimeout,
  244. clearTimeout: nativeClearTimeout ? nativeClearTimeout : global.clearTimeout
  245. };
  246. }
  247. // create a userContext to hold the queueRunner itself
  248. // so we can access the testProxy in it/xit/beforeEach ...
  249. if (jasmine.UserContext) {
  250. if (!attrs.userContext) {
  251. attrs.userContext = new jasmine.UserContext();
  252. }
  253. attrs.userContext.queueRunner = this;
  254. }
  255. else {
  256. if (!attrs.userContext) {
  257. attrs.userContext = {};
  258. }
  259. attrs.userContext.queueRunner = this;
  260. }
  261. // patch attrs.onException
  262. const onException = attrs.onException;
  263. attrs.onException = function (error) {
  264. if (error &&
  265. error.message ===
  266. 'Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.') {
  267. // jasmine timeout, we can make the error message more
  268. // reasonable to tell what tasks are pending
  269. const proxyZoneSpec = this && this.testProxyZoneSpec;
  270. if (proxyZoneSpec) {
  271. const pendingTasksInfo = proxyZoneSpec.getAndClearPendingTasksInfo();
  272. try {
  273. // try catch here in case error.message is not writable
  274. error.message += pendingTasksInfo;
  275. }
  276. catch (err) {
  277. }
  278. }
  279. }
  280. if (onException) {
  281. onException.call(this, error);
  282. }
  283. };
  284. _super.call(this, attrs);
  285. }
  286. ZoneQueueRunner.prototype.execute = function () {
  287. let zone = Zone.current;
  288. let isChildOfAmbientZone = false;
  289. while (zone) {
  290. if (zone === ambientZone) {
  291. isChildOfAmbientZone = true;
  292. break;
  293. }
  294. zone = zone.parent;
  295. }
  296. if (!isChildOfAmbientZone)
  297. throw new Error('Unexpected Zone: ' + Zone.current.name);
  298. // This is the zone which will be used for running individual tests.
  299. // It will be a proxy zone, so that the tests function can retroactively install
  300. // different zones.
  301. // Example:
  302. // - In beforeEach() do childZone = Zone.current.fork(...);
  303. // - In it() try to do fakeAsync(). The issue is that because the beforeEach forked the
  304. // zone outside of fakeAsync it will be able to escape the fakeAsync rules.
  305. // - Because ProxyZone is parent fo `childZone` fakeAsync can retroactively add
  306. // fakeAsync behavior to the childZone.
  307. this.testProxyZoneSpec = new ProxyZoneSpec();
  308. this.testProxyZone = ambientZone.fork(this.testProxyZoneSpec);
  309. if (!Zone.currentTask) {
  310. // if we are not running in a task then if someone would register a
  311. // element.addEventListener and then calling element.click() the
  312. // addEventListener callback would think that it is the top most task and would
  313. // drain the microtask queue on element.click() which would be incorrect.
  314. // For this reason we always force a task when running jasmine tests.
  315. Zone.current.scheduleMicroTask('jasmine.execute().forceTask', () => QueueRunner.prototype.execute.call(this));
  316. }
  317. else {
  318. _super.prototype.execute.call(this);
  319. }
  320. };
  321. return ZoneQueueRunner;
  322. })(QueueRunner);
  323. });