zone-testing.js 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116
  1. 'use strict';
  2. /**
  3. * @license Angular v<unknown>
  4. * (c) 2010-2022 Google LLC. https://angular.io/
  5. * License: MIT
  6. */
  7. /**
  8. * @fileoverview
  9. * @suppress {globalThis}
  10. */
  11. const NEWLINE = '\n';
  12. const IGNORE_FRAMES = {};
  13. const creationTrace = '__creationTrace__';
  14. const ERROR_TAG = 'STACKTRACE TRACKING';
  15. const SEP_TAG = '__SEP_TAG__';
  16. let sepTemplate = SEP_TAG + '@[native]';
  17. class LongStackTrace {
  18. constructor() {
  19. this.error = getStacktrace();
  20. this.timestamp = new Date();
  21. }
  22. }
  23. function getStacktraceWithUncaughtError() {
  24. return new Error(ERROR_TAG);
  25. }
  26. function getStacktraceWithCaughtError() {
  27. try {
  28. throw getStacktraceWithUncaughtError();
  29. }
  30. catch (err) {
  31. return err;
  32. }
  33. }
  34. // Some implementations of exception handling don't create a stack trace if the exception
  35. // isn't thrown, however it's faster not to actually throw the exception.
  36. const error = getStacktraceWithUncaughtError();
  37. const caughtError = getStacktraceWithCaughtError();
  38. const getStacktrace = error.stack ?
  39. getStacktraceWithUncaughtError :
  40. (caughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError);
  41. function getFrames(error) {
  42. return error.stack ? error.stack.split(NEWLINE) : [];
  43. }
  44. function addErrorStack(lines, error) {
  45. let trace = getFrames(error);
  46. for (let i = 0; i < trace.length; i++) {
  47. const frame = trace[i];
  48. // Filter out the Frames which are part of stack capturing.
  49. if (!IGNORE_FRAMES.hasOwnProperty(frame)) {
  50. lines.push(trace[i]);
  51. }
  52. }
  53. }
  54. function renderLongStackTrace(frames, stack) {
  55. const longTrace = [stack ? stack.trim() : ''];
  56. if (frames) {
  57. let timestamp = new Date().getTime();
  58. for (let i = 0; i < frames.length; i++) {
  59. const traceFrames = frames[i];
  60. const lastTime = traceFrames.timestamp;
  61. let separator = `____________________Elapsed ${timestamp - lastTime.getTime()} ms; At: ${lastTime}`;
  62. separator = separator.replace(/[^\w\d]/g, '_');
  63. longTrace.push(sepTemplate.replace(SEP_TAG, separator));
  64. addErrorStack(longTrace, traceFrames.error);
  65. timestamp = lastTime.getTime();
  66. }
  67. }
  68. return longTrace.join(NEWLINE);
  69. }
  70. // if Error.stackTraceLimit is 0, means stack trace
  71. // is disabled, so we don't need to generate long stack trace
  72. // this will improve performance in some test(some test will
  73. // set stackTraceLimit to 0, https://github.com/angular/zone.js/issues/698
  74. function stackTracesEnabled() {
  75. // Cast through any since this property only exists on Error in the nodejs
  76. // typings.
  77. return Error.stackTraceLimit > 0;
  78. }
  79. Zone['longStackTraceZoneSpec'] = {
  80. name: 'long-stack-trace',
  81. longStackTraceLimit: 10,
  82. // add a getLongStackTrace method in spec to
  83. // handle handled reject promise error.
  84. getLongStackTrace: function (error) {
  85. if (!error) {
  86. return undefined;
  87. }
  88. const trace = error[Zone.__symbol__('currentTaskTrace')];
  89. if (!trace) {
  90. return error.stack;
  91. }
  92. return renderLongStackTrace(trace, error.stack);
  93. },
  94. onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) {
  95. if (stackTracesEnabled()) {
  96. const currentTask = Zone.currentTask;
  97. let trace = currentTask && currentTask.data && currentTask.data[creationTrace] || [];
  98. trace = [new LongStackTrace()].concat(trace);
  99. if (trace.length > this.longStackTraceLimit) {
  100. trace.length = this.longStackTraceLimit;
  101. }
  102. if (!task.data)
  103. task.data = {};
  104. if (task.type === 'eventTask') {
  105. // Fix issue https://github.com/angular/zone.js/issues/1195,
  106. // For event task of browser, by default, all task will share a
  107. // singleton instance of data object, we should create a new one here
  108. // The cast to `any` is required to workaround a closure bug which wrongly applies
  109. // URL sanitization rules to .data access.
  110. task.data = { ...task.data };
  111. }
  112. task.data[creationTrace] = trace;
  113. }
  114. return parentZoneDelegate.scheduleTask(targetZone, task);
  115. },
  116. onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) {
  117. if (stackTracesEnabled()) {
  118. const parentTask = Zone.currentTask || error.task;
  119. if (error instanceof Error && parentTask) {
  120. const longStack = renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack);
  121. try {
  122. error.stack = error.longStack = longStack;
  123. }
  124. catch (err) {
  125. }
  126. }
  127. }
  128. return parentZoneDelegate.handleError(targetZone, error);
  129. }
  130. };
  131. function captureStackTraces(stackTraces, count) {
  132. if (count > 0) {
  133. stackTraces.push(getFrames((new LongStackTrace()).error));
  134. captureStackTraces(stackTraces, count - 1);
  135. }
  136. }
  137. function computeIgnoreFrames() {
  138. if (!stackTracesEnabled()) {
  139. return;
  140. }
  141. const frames = [];
  142. captureStackTraces(frames, 2);
  143. const frames1 = frames[0];
  144. const frames2 = frames[1];
  145. for (let i = 0; i < frames1.length; i++) {
  146. const frame1 = frames1[i];
  147. if (frame1.indexOf(ERROR_TAG) == -1) {
  148. let match = frame1.match(/^\s*at\s+/);
  149. if (match) {
  150. sepTemplate = match[0] + SEP_TAG + ' (http://localhost)';
  151. break;
  152. }
  153. }
  154. }
  155. for (let i = 0; i < frames1.length; i++) {
  156. const frame1 = frames1[i];
  157. const frame2 = frames2[i];
  158. if (frame1 === frame2) {
  159. IGNORE_FRAMES[frame1] = true;
  160. }
  161. else {
  162. break;
  163. }
  164. }
  165. }
  166. computeIgnoreFrames();
  167. class ProxyZoneSpec {
  168. static get() {
  169. return Zone.current.get('ProxyZoneSpec');
  170. }
  171. static isLoaded() {
  172. return ProxyZoneSpec.get() instanceof ProxyZoneSpec;
  173. }
  174. static assertPresent() {
  175. if (!ProxyZoneSpec.isLoaded()) {
  176. throw new Error(`Expected to be running in 'ProxyZone', but it was not found.`);
  177. }
  178. return ProxyZoneSpec.get();
  179. }
  180. constructor(defaultSpecDelegate = null) {
  181. this.defaultSpecDelegate = defaultSpecDelegate;
  182. this.name = 'ProxyZone';
  183. this._delegateSpec = null;
  184. this.properties = { 'ProxyZoneSpec': this };
  185. this.propertyKeys = null;
  186. this.lastTaskState = null;
  187. this.isNeedToTriggerHasTask = false;
  188. this.tasks = [];
  189. this.setDelegate(defaultSpecDelegate);
  190. }
  191. setDelegate(delegateSpec) {
  192. const isNewDelegate = this._delegateSpec !== delegateSpec;
  193. this._delegateSpec = delegateSpec;
  194. this.propertyKeys && this.propertyKeys.forEach((key) => delete this.properties[key]);
  195. this.propertyKeys = null;
  196. if (delegateSpec && delegateSpec.properties) {
  197. this.propertyKeys = Object.keys(delegateSpec.properties);
  198. this.propertyKeys.forEach((k) => this.properties[k] = delegateSpec.properties[k]);
  199. }
  200. // if a new delegateSpec was set, check if we need to trigger hasTask
  201. if (isNewDelegate && this.lastTaskState &&
  202. (this.lastTaskState.macroTask || this.lastTaskState.microTask)) {
  203. this.isNeedToTriggerHasTask = true;
  204. }
  205. }
  206. getDelegate() {
  207. return this._delegateSpec;
  208. }
  209. resetDelegate() {
  210. this.getDelegate();
  211. this.setDelegate(this.defaultSpecDelegate);
  212. }
  213. tryTriggerHasTask(parentZoneDelegate, currentZone, targetZone) {
  214. if (this.isNeedToTriggerHasTask && this.lastTaskState) {
  215. // last delegateSpec has microTask or macroTask
  216. // should call onHasTask in current delegateSpec
  217. this.isNeedToTriggerHasTask = false;
  218. this.onHasTask(parentZoneDelegate, currentZone, targetZone, this.lastTaskState);
  219. }
  220. }
  221. removeFromTasks(task) {
  222. if (!this.tasks) {
  223. return;
  224. }
  225. for (let i = 0; i < this.tasks.length; i++) {
  226. if (this.tasks[i] === task) {
  227. this.tasks.splice(i, 1);
  228. return;
  229. }
  230. }
  231. }
  232. getAndClearPendingTasksInfo() {
  233. if (this.tasks.length === 0) {
  234. return '';
  235. }
  236. const taskInfo = this.tasks.map((task) => {
  237. const dataInfo = task.data &&
  238. Object.keys(task.data)
  239. .map((key) => {
  240. return key + ':' + task.data[key];
  241. })
  242. .join(',');
  243. return `type: ${task.type}, source: ${task.source}, args: {${dataInfo}}`;
  244. });
  245. const pendingTasksInfo = '--Pending async tasks are: [' + taskInfo + ']';
  246. // clear tasks
  247. this.tasks = [];
  248. return pendingTasksInfo;
  249. }
  250. onFork(parentZoneDelegate, currentZone, targetZone, zoneSpec) {
  251. if (this._delegateSpec && this._delegateSpec.onFork) {
  252. return this._delegateSpec.onFork(parentZoneDelegate, currentZone, targetZone, zoneSpec);
  253. }
  254. else {
  255. return parentZoneDelegate.fork(targetZone, zoneSpec);
  256. }
  257. }
  258. onIntercept(parentZoneDelegate, currentZone, targetZone, delegate, source) {
  259. if (this._delegateSpec && this._delegateSpec.onIntercept) {
  260. return this._delegateSpec.onIntercept(parentZoneDelegate, currentZone, targetZone, delegate, source);
  261. }
  262. else {
  263. return parentZoneDelegate.intercept(targetZone, delegate, source);
  264. }
  265. }
  266. onInvoke(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) {
  267. this.tryTriggerHasTask(parentZoneDelegate, currentZone, targetZone);
  268. if (this._delegateSpec && this._delegateSpec.onInvoke) {
  269. return this._delegateSpec.onInvoke(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source);
  270. }
  271. else {
  272. return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
  273. }
  274. }
  275. onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
  276. if (this._delegateSpec && this._delegateSpec.onHandleError) {
  277. return this._delegateSpec.onHandleError(parentZoneDelegate, currentZone, targetZone, error);
  278. }
  279. else {
  280. return parentZoneDelegate.handleError(targetZone, error);
  281. }
  282. }
  283. onScheduleTask(parentZoneDelegate, currentZone, targetZone, task) {
  284. if (task.type !== 'eventTask') {
  285. this.tasks.push(task);
  286. }
  287. if (this._delegateSpec && this._delegateSpec.onScheduleTask) {
  288. return this._delegateSpec.onScheduleTask(parentZoneDelegate, currentZone, targetZone, task);
  289. }
  290. else {
  291. return parentZoneDelegate.scheduleTask(targetZone, task);
  292. }
  293. }
  294. onInvokeTask(parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) {
  295. if (task.type !== 'eventTask') {
  296. this.removeFromTasks(task);
  297. }
  298. this.tryTriggerHasTask(parentZoneDelegate, currentZone, targetZone);
  299. if (this._delegateSpec && this._delegateSpec.onInvokeTask) {
  300. return this._delegateSpec.onInvokeTask(parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs);
  301. }
  302. else {
  303. return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
  304. }
  305. }
  306. onCancelTask(parentZoneDelegate, currentZone, targetZone, task) {
  307. if (task.type !== 'eventTask') {
  308. this.removeFromTasks(task);
  309. }
  310. this.tryTriggerHasTask(parentZoneDelegate, currentZone, targetZone);
  311. if (this._delegateSpec && this._delegateSpec.onCancelTask) {
  312. return this._delegateSpec.onCancelTask(parentZoneDelegate, currentZone, targetZone, task);
  313. }
  314. else {
  315. return parentZoneDelegate.cancelTask(targetZone, task);
  316. }
  317. }
  318. onHasTask(delegate, current, target, hasTaskState) {
  319. this.lastTaskState = hasTaskState;
  320. if (this._delegateSpec && this._delegateSpec.onHasTask) {
  321. this._delegateSpec.onHasTask(delegate, current, target, hasTaskState);
  322. }
  323. else {
  324. delegate.hasTask(target, hasTaskState);
  325. }
  326. }
  327. }
  328. // Export the class so that new instances can be created with proper
  329. // constructor params.
  330. Zone['ProxyZoneSpec'] = ProxyZoneSpec;
  331. class SyncTestZoneSpec {
  332. constructor(namePrefix) {
  333. this.runZone = Zone.current;
  334. this.name = 'syncTestZone for ' + namePrefix;
  335. }
  336. onScheduleTask(delegate, current, target, task) {
  337. switch (task.type) {
  338. case 'microTask':
  339. case 'macroTask':
  340. throw new Error(`Cannot call ${task.source} from within a sync test (${this.name}).`);
  341. case 'eventTask':
  342. task = delegate.scheduleTask(target, task);
  343. break;
  344. }
  345. return task;
  346. }
  347. }
  348. // Export the class so that new instances can be created with proper
  349. // constructor params.
  350. Zone['SyncTestZoneSpec'] = SyncTestZoneSpec;
  351. /// <reference types="jasmine"/>
  352. Zone.__load_patch('jasmine', (global, Zone, api) => {
  353. const __extends = function (d, b) {
  354. for (const p in b)
  355. if (b.hasOwnProperty(p))
  356. d[p] = b[p];
  357. function __() {
  358. this.constructor = d;
  359. }
  360. d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
  361. };
  362. // Patch jasmine's describe/it/beforeEach/afterEach functions so test code always runs
  363. // in a testZone (ProxyZone). (See: angular/zone.js#91 & angular/angular#10503)
  364. if (!Zone)
  365. throw new Error('Missing: zone.js');
  366. if (typeof jest !== 'undefined') {
  367. // return if jasmine is a light implementation inside jest
  368. // in this case, we are running inside jest not jasmine
  369. return;
  370. }
  371. if (typeof jasmine == 'undefined' || jasmine['__zone_patch__']) {
  372. return;
  373. }
  374. jasmine['__zone_patch__'] = true;
  375. const SyncTestZoneSpec = Zone['SyncTestZoneSpec'];
  376. const ProxyZoneSpec = Zone['ProxyZoneSpec'];
  377. if (!SyncTestZoneSpec)
  378. throw new Error('Missing: SyncTestZoneSpec');
  379. if (!ProxyZoneSpec)
  380. throw new Error('Missing: ProxyZoneSpec');
  381. const ambientZone = Zone.current;
  382. const symbol = Zone.__symbol__;
  383. // whether patch jasmine clock when in fakeAsync
  384. const disablePatchingJasmineClock = global[symbol('fakeAsyncDisablePatchingClock')] === true;
  385. // the original variable name fakeAsyncPatchLock is not accurate, so the name will be
  386. // fakeAsyncAutoFakeAsyncWhenClockPatched and if this enablePatchingJasmineClock is false, we also
  387. // automatically disable the auto jump into fakeAsync feature
  388. const enableAutoFakeAsyncWhenClockPatched = !disablePatchingJasmineClock &&
  389. ((global[symbol('fakeAsyncPatchLock')] === true) ||
  390. (global[symbol('fakeAsyncAutoFakeAsyncWhenClockPatched')] === true));
  391. const ignoreUnhandledRejection = global[symbol('ignoreUnhandledRejection')] === true;
  392. if (!ignoreUnhandledRejection) {
  393. const globalErrors = jasmine.GlobalErrors;
  394. if (globalErrors && !jasmine[symbol('GlobalErrors')]) {
  395. jasmine[symbol('GlobalErrors')] = globalErrors;
  396. jasmine.GlobalErrors = function () {
  397. const instance = new globalErrors();
  398. const originalInstall = instance.install;
  399. if (originalInstall && !instance[symbol('install')]) {
  400. instance[symbol('install')] = originalInstall;
  401. instance.install = function () {
  402. const isNode = typeof process !== 'undefined' && !!process.on;
  403. // Note: Jasmine checks internally if `process` and `process.on` is defined. Otherwise,
  404. // it installs the browser rejection handler through the `global.addEventListener`.
  405. // This code may be run in the browser environment where `process` is not defined, and
  406. // this will lead to a runtime exception since Webpack 5 removed automatic Node.js
  407. // polyfills. Note, that events are named differently, it's `unhandledRejection` in
  408. // Node.js and `unhandledrejection` in the browser.
  409. const originalHandlers = isNode ? process.listeners('unhandledRejection') :
  410. global.eventListeners('unhandledrejection');
  411. const result = originalInstall.apply(this, arguments);
  412. isNode ? process.removeAllListeners('unhandledRejection') :
  413. global.removeAllListeners('unhandledrejection');
  414. if (originalHandlers) {
  415. originalHandlers.forEach(handler => {
  416. if (isNode) {
  417. process.on('unhandledRejection', handler);
  418. }
  419. else {
  420. global.addEventListener('unhandledrejection', handler);
  421. }
  422. });
  423. }
  424. return result;
  425. };
  426. }
  427. return instance;
  428. };
  429. }
  430. }
  431. // Monkey patch all of the jasmine DSL so that each function runs in appropriate zone.
  432. const jasmineEnv = jasmine.getEnv();
  433. ['describe', 'xdescribe', 'fdescribe'].forEach(methodName => {
  434. let originalJasmineFn = jasmineEnv[methodName];
  435. jasmineEnv[methodName] = function (description, specDefinitions) {
  436. return originalJasmineFn.call(this, description, wrapDescribeInZone(description, specDefinitions));
  437. };
  438. });
  439. ['it', 'xit', 'fit'].forEach(methodName => {
  440. let originalJasmineFn = jasmineEnv[methodName];
  441. jasmineEnv[symbol(methodName)] = originalJasmineFn;
  442. jasmineEnv[methodName] = function (description, specDefinitions, timeout) {
  443. arguments[1] = wrapTestInZone(specDefinitions);
  444. return originalJasmineFn.apply(this, arguments);
  445. };
  446. });
  447. ['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => {
  448. let originalJasmineFn = jasmineEnv[methodName];
  449. jasmineEnv[symbol(methodName)] = originalJasmineFn;
  450. jasmineEnv[methodName] = function (specDefinitions, timeout) {
  451. arguments[0] = wrapTestInZone(specDefinitions);
  452. return originalJasmineFn.apply(this, arguments);
  453. };
  454. });
  455. if (!disablePatchingJasmineClock) {
  456. // need to patch jasmine.clock().mockDate and jasmine.clock().tick() so
  457. // they can work properly in FakeAsyncTest
  458. const originalClockFn = (jasmine[symbol('clock')] = jasmine['clock']);
  459. jasmine['clock'] = function () {
  460. const clock = originalClockFn.apply(this, arguments);
  461. if (!clock[symbol('patched')]) {
  462. clock[symbol('patched')] = symbol('patched');
  463. const originalTick = (clock[symbol('tick')] = clock.tick);
  464. clock.tick = function () {
  465. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  466. if (fakeAsyncZoneSpec) {
  467. return fakeAsyncZoneSpec.tick.apply(fakeAsyncZoneSpec, arguments);
  468. }
  469. return originalTick.apply(this, arguments);
  470. };
  471. const originalMockDate = (clock[symbol('mockDate')] = clock.mockDate);
  472. clock.mockDate = function () {
  473. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  474. if (fakeAsyncZoneSpec) {
  475. const dateTime = arguments.length > 0 ? arguments[0] : new Date();
  476. return fakeAsyncZoneSpec.setFakeBaseSystemTime.apply(fakeAsyncZoneSpec, dateTime && typeof dateTime.getTime === 'function' ? [dateTime.getTime()] :
  477. arguments);
  478. }
  479. return originalMockDate.apply(this, arguments);
  480. };
  481. // for auto go into fakeAsync feature, we need the flag to enable it
  482. if (enableAutoFakeAsyncWhenClockPatched) {
  483. ['install', 'uninstall'].forEach(methodName => {
  484. const originalClockFn = (clock[symbol(methodName)] = clock[methodName]);
  485. clock[methodName] = function () {
  486. const FakeAsyncTestZoneSpec = Zone['FakeAsyncTestZoneSpec'];
  487. if (FakeAsyncTestZoneSpec) {
  488. jasmine[symbol('clockInstalled')] = 'install' === methodName;
  489. return;
  490. }
  491. return originalClockFn.apply(this, arguments);
  492. };
  493. });
  494. }
  495. }
  496. return clock;
  497. };
  498. }
  499. // monkey patch createSpyObj to make properties enumerable to true
  500. if (!jasmine[Zone.__symbol__('createSpyObj')]) {
  501. const originalCreateSpyObj = jasmine.createSpyObj;
  502. jasmine[Zone.__symbol__('createSpyObj')] = originalCreateSpyObj;
  503. jasmine.createSpyObj = function () {
  504. const args = Array.prototype.slice.call(arguments);
  505. const propertyNames = args.length >= 3 ? args[2] : null;
  506. let spyObj;
  507. if (propertyNames) {
  508. const defineProperty = Object.defineProperty;
  509. Object.defineProperty = function (obj, p, attributes) {
  510. return defineProperty.call(this, obj, p, { ...attributes, configurable: true, enumerable: true });
  511. };
  512. try {
  513. spyObj = originalCreateSpyObj.apply(this, args);
  514. }
  515. finally {
  516. Object.defineProperty = defineProperty;
  517. }
  518. }
  519. else {
  520. spyObj = originalCreateSpyObj.apply(this, args);
  521. }
  522. return spyObj;
  523. };
  524. }
  525. /**
  526. * Gets a function wrapping the body of a Jasmine `describe` block to execute in a
  527. * synchronous-only zone.
  528. */
  529. function wrapDescribeInZone(description, describeBody) {
  530. return function () {
  531. // Create a synchronous-only zone in which to run `describe` blocks in order to raise an
  532. // error if any asynchronous operations are attempted inside of a `describe`.
  533. const syncZone = ambientZone.fork(new SyncTestZoneSpec(`jasmine.describe#${description}`));
  534. return syncZone.run(describeBody, this, arguments);
  535. };
  536. }
  537. function runInTestZone(testBody, applyThis, queueRunner, done) {
  538. const isClockInstalled = !!jasmine[symbol('clockInstalled')];
  539. queueRunner.testProxyZoneSpec;
  540. const testProxyZone = queueRunner.testProxyZone;
  541. if (isClockInstalled && enableAutoFakeAsyncWhenClockPatched) {
  542. // auto run a fakeAsync
  543. const fakeAsyncModule = Zone[Zone.__symbol__('fakeAsyncTest')];
  544. if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') {
  545. testBody = fakeAsyncModule.fakeAsync(testBody);
  546. }
  547. }
  548. if (done) {
  549. return testProxyZone.run(testBody, applyThis, [done]);
  550. }
  551. else {
  552. return testProxyZone.run(testBody, applyThis);
  553. }
  554. }
  555. /**
  556. * Gets a function wrapping the body of a Jasmine `it/beforeEach/afterEach` block to
  557. * execute in a ProxyZone zone.
  558. * This will run in `testProxyZone`. The `testProxyZone` will be reset by the `ZoneQueueRunner`
  559. */
  560. function wrapTestInZone(testBody) {
  561. // The `done` callback is only passed through if the function expects at least one argument.
  562. // Note we have to make a function with correct number of arguments, otherwise jasmine will
  563. // think that all functions are sync or async.
  564. return (testBody && (testBody.length ? function (done) {
  565. return runInTestZone(testBody, this, this.queueRunner, done);
  566. } : function () {
  567. return runInTestZone(testBody, this, this.queueRunner);
  568. }));
  569. }
  570. const QueueRunner = jasmine.QueueRunner;
  571. jasmine.QueueRunner = (function (_super) {
  572. __extends(ZoneQueueRunner, _super);
  573. function ZoneQueueRunner(attrs) {
  574. if (attrs.onComplete) {
  575. attrs.onComplete = (fn => () => {
  576. // All functions are done, clear the test zone.
  577. this.testProxyZone = null;
  578. this.testProxyZoneSpec = null;
  579. ambientZone.scheduleMicroTask('jasmine.onComplete', fn);
  580. })(attrs.onComplete);
  581. }
  582. const nativeSetTimeout = global[Zone.__symbol__('setTimeout')];
  583. const nativeClearTimeout = global[Zone.__symbol__('clearTimeout')];
  584. if (nativeSetTimeout) {
  585. // should run setTimeout inside jasmine outside of zone
  586. attrs.timeout = {
  587. setTimeout: nativeSetTimeout ? nativeSetTimeout : global.setTimeout,
  588. clearTimeout: nativeClearTimeout ? nativeClearTimeout : global.clearTimeout
  589. };
  590. }
  591. // create a userContext to hold the queueRunner itself
  592. // so we can access the testProxy in it/xit/beforeEach ...
  593. if (jasmine.UserContext) {
  594. if (!attrs.userContext) {
  595. attrs.userContext = new jasmine.UserContext();
  596. }
  597. attrs.userContext.queueRunner = this;
  598. }
  599. else {
  600. if (!attrs.userContext) {
  601. attrs.userContext = {};
  602. }
  603. attrs.userContext.queueRunner = this;
  604. }
  605. // patch attrs.onException
  606. const onException = attrs.onException;
  607. attrs.onException = function (error) {
  608. if (error &&
  609. error.message ===
  610. 'Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.') {
  611. // jasmine timeout, we can make the error message more
  612. // reasonable to tell what tasks are pending
  613. const proxyZoneSpec = this && this.testProxyZoneSpec;
  614. if (proxyZoneSpec) {
  615. const pendingTasksInfo = proxyZoneSpec.getAndClearPendingTasksInfo();
  616. try {
  617. // try catch here in case error.message is not writable
  618. error.message += pendingTasksInfo;
  619. }
  620. catch (err) {
  621. }
  622. }
  623. }
  624. if (onException) {
  625. onException.call(this, error);
  626. }
  627. };
  628. _super.call(this, attrs);
  629. }
  630. ZoneQueueRunner.prototype.execute = function () {
  631. let zone = Zone.current;
  632. let isChildOfAmbientZone = false;
  633. while (zone) {
  634. if (zone === ambientZone) {
  635. isChildOfAmbientZone = true;
  636. break;
  637. }
  638. zone = zone.parent;
  639. }
  640. if (!isChildOfAmbientZone)
  641. throw new Error('Unexpected Zone: ' + Zone.current.name);
  642. // This is the zone which will be used for running individual tests.
  643. // It will be a proxy zone, so that the tests function can retroactively install
  644. // different zones.
  645. // Example:
  646. // - In beforeEach() do childZone = Zone.current.fork(...);
  647. // - In it() try to do fakeAsync(). The issue is that because the beforeEach forked the
  648. // zone outside of fakeAsync it will be able to escape the fakeAsync rules.
  649. // - Because ProxyZone is parent fo `childZone` fakeAsync can retroactively add
  650. // fakeAsync behavior to the childZone.
  651. this.testProxyZoneSpec = new ProxyZoneSpec();
  652. this.testProxyZone = ambientZone.fork(this.testProxyZoneSpec);
  653. if (!Zone.currentTask) {
  654. // if we are not running in a task then if someone would register a
  655. // element.addEventListener and then calling element.click() the
  656. // addEventListener callback would think that it is the top most task and would
  657. // drain the microtask queue on element.click() which would be incorrect.
  658. // For this reason we always force a task when running jasmine tests.
  659. Zone.current.scheduleMicroTask('jasmine.execute().forceTask', () => QueueRunner.prototype.execute.call(this));
  660. }
  661. else {
  662. _super.prototype.execute.call(this);
  663. }
  664. };
  665. return ZoneQueueRunner;
  666. })(QueueRunner);
  667. });
  668. Zone.__load_patch('jest', (context, Zone, api) => {
  669. if (typeof jest === 'undefined' || jest['__zone_patch__']) {
  670. return;
  671. }
  672. jest['__zone_patch__'] = true;
  673. const ProxyZoneSpec = Zone['ProxyZoneSpec'];
  674. const SyncTestZoneSpec = Zone['SyncTestZoneSpec'];
  675. if (!ProxyZoneSpec) {
  676. throw new Error('Missing ProxyZoneSpec');
  677. }
  678. const rootZone = Zone.current;
  679. const syncZone = rootZone.fork(new SyncTestZoneSpec('jest.describe'));
  680. const proxyZoneSpec = new ProxyZoneSpec();
  681. const proxyZone = rootZone.fork(proxyZoneSpec);
  682. function wrapDescribeFactoryInZone(originalJestFn) {
  683. return function (...tableArgs) {
  684. const originalDescribeFn = originalJestFn.apply(this, tableArgs);
  685. return function (...args) {
  686. args[1] = wrapDescribeInZone(args[1]);
  687. return originalDescribeFn.apply(this, args);
  688. };
  689. };
  690. }
  691. function wrapTestFactoryInZone(originalJestFn) {
  692. return function (...tableArgs) {
  693. return function (...args) {
  694. args[1] = wrapTestInZone(args[1]);
  695. return originalJestFn.apply(this, tableArgs).apply(this, args);
  696. };
  697. };
  698. }
  699. /**
  700. * Gets a function wrapping the body of a jest `describe` block to execute in a
  701. * synchronous-only zone.
  702. */
  703. function wrapDescribeInZone(describeBody) {
  704. return function (...args) {
  705. return syncZone.run(describeBody, this, args);
  706. };
  707. }
  708. /**
  709. * Gets a function wrapping the body of a jest `it/beforeEach/afterEach` block to
  710. * execute in a ProxyZone zone.
  711. * This will run in the `proxyZone`.
  712. */
  713. function wrapTestInZone(testBody, isTestFunc = false) {
  714. if (typeof testBody !== 'function') {
  715. return testBody;
  716. }
  717. const wrappedFunc = function () {
  718. if (Zone[api.symbol('useFakeTimersCalled')] === true && testBody &&
  719. !testBody.isFakeAsync) {
  720. // jest.useFakeTimers is called, run into fakeAsyncTest automatically.
  721. const fakeAsyncModule = Zone[Zone.__symbol__('fakeAsyncTest')];
  722. if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') {
  723. testBody = fakeAsyncModule.fakeAsync(testBody);
  724. }
  725. }
  726. proxyZoneSpec.isTestFunc = isTestFunc;
  727. return proxyZone.run(testBody, null, arguments);
  728. };
  729. // Update the length of wrappedFunc to be the same as the length of the testBody
  730. // So jest core can handle whether the test function has `done()` or not correctly
  731. Object.defineProperty(wrappedFunc, 'length', { configurable: true, writable: true, enumerable: false });
  732. wrappedFunc.length = testBody.length;
  733. return wrappedFunc;
  734. }
  735. ['describe', 'xdescribe', 'fdescribe'].forEach(methodName => {
  736. let originalJestFn = context[methodName];
  737. if (context[Zone.__symbol__(methodName)]) {
  738. return;
  739. }
  740. context[Zone.__symbol__(methodName)] = originalJestFn;
  741. context[methodName] = function (...args) {
  742. args[1] = wrapDescribeInZone(args[1]);
  743. return originalJestFn.apply(this, args);
  744. };
  745. context[methodName].each = wrapDescribeFactoryInZone(originalJestFn.each);
  746. });
  747. context.describe.only = context.fdescribe;
  748. context.describe.skip = context.xdescribe;
  749. ['it', 'xit', 'fit', 'test', 'xtest'].forEach(methodName => {
  750. let originalJestFn = context[methodName];
  751. if (context[Zone.__symbol__(methodName)]) {
  752. return;
  753. }
  754. context[Zone.__symbol__(methodName)] = originalJestFn;
  755. context[methodName] = function (...args) {
  756. args[1] = wrapTestInZone(args[1], true);
  757. return originalJestFn.apply(this, args);
  758. };
  759. context[methodName].each = wrapTestFactoryInZone(originalJestFn.each);
  760. context[methodName].todo = originalJestFn.todo;
  761. });
  762. context.it.only = context.fit;
  763. context.it.skip = context.xit;
  764. context.test.only = context.fit;
  765. context.test.skip = context.xit;
  766. ['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => {
  767. let originalJestFn = context[methodName];
  768. if (context[Zone.__symbol__(methodName)]) {
  769. return;
  770. }
  771. context[Zone.__symbol__(methodName)] = originalJestFn;
  772. context[methodName] = function (...args) {
  773. args[0] = wrapTestInZone(args[0]);
  774. return originalJestFn.apply(this, args);
  775. };
  776. });
  777. Zone.patchJestObject = function patchJestObject(Timer, isModern = false) {
  778. // check whether currently the test is inside fakeAsync()
  779. function isPatchingFakeTimer() {
  780. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  781. return !!fakeAsyncZoneSpec;
  782. }
  783. // check whether the current function is inside `test/it` or other methods
  784. // such as `describe/beforeEach`
  785. function isInTestFunc() {
  786. const proxyZoneSpec = Zone.current.get('ProxyZoneSpec');
  787. return proxyZoneSpec && proxyZoneSpec.isTestFunc;
  788. }
  789. if (Timer[api.symbol('fakeTimers')]) {
  790. return;
  791. }
  792. Timer[api.symbol('fakeTimers')] = true;
  793. // patch jest fakeTimer internal method to make sure no console.warn print out
  794. api.patchMethod(Timer, '_checkFakeTimers', delegate => {
  795. return function (self, args) {
  796. if (isPatchingFakeTimer()) {
  797. return true;
  798. }
  799. else {
  800. return delegate.apply(self, args);
  801. }
  802. };
  803. });
  804. // patch useFakeTimers(), set useFakeTimersCalled flag, and make test auto run into fakeAsync
  805. api.patchMethod(Timer, 'useFakeTimers', delegate => {
  806. return function (self, args) {
  807. Zone[api.symbol('useFakeTimersCalled')] = true;
  808. if (isModern || isInTestFunc()) {
  809. return delegate.apply(self, args);
  810. }
  811. return self;
  812. };
  813. });
  814. // patch useRealTimers(), unset useFakeTimers flag
  815. api.patchMethod(Timer, 'useRealTimers', delegate => {
  816. return function (self, args) {
  817. Zone[api.symbol('useFakeTimersCalled')] = false;
  818. if (isModern || isInTestFunc()) {
  819. return delegate.apply(self, args);
  820. }
  821. return self;
  822. };
  823. });
  824. // patch setSystemTime(), call setCurrentRealTime() in the fakeAsyncTest
  825. api.patchMethod(Timer, 'setSystemTime', delegate => {
  826. return function (self, args) {
  827. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  828. if (fakeAsyncZoneSpec && isPatchingFakeTimer()) {
  829. fakeAsyncZoneSpec.setFakeBaseSystemTime(args[0]);
  830. }
  831. else {
  832. return delegate.apply(self, args);
  833. }
  834. };
  835. });
  836. // patch getSystemTime(), call getCurrentRealTime() in the fakeAsyncTest
  837. api.patchMethod(Timer, 'getRealSystemTime', delegate => {
  838. return function (self, args) {
  839. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  840. if (fakeAsyncZoneSpec && isPatchingFakeTimer()) {
  841. return fakeAsyncZoneSpec.getRealSystemTime();
  842. }
  843. else {
  844. return delegate.apply(self, args);
  845. }
  846. };
  847. });
  848. // patch runAllTicks(), run all microTasks inside fakeAsync
  849. api.patchMethod(Timer, 'runAllTicks', delegate => {
  850. return function (self, args) {
  851. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  852. if (fakeAsyncZoneSpec) {
  853. fakeAsyncZoneSpec.flushMicrotasks();
  854. }
  855. else {
  856. return delegate.apply(self, args);
  857. }
  858. };
  859. });
  860. // patch runAllTimers(), run all macroTasks inside fakeAsync
  861. api.patchMethod(Timer, 'runAllTimers', delegate => {
  862. return function (self, args) {
  863. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  864. if (fakeAsyncZoneSpec) {
  865. fakeAsyncZoneSpec.flush(100, true);
  866. }
  867. else {
  868. return delegate.apply(self, args);
  869. }
  870. };
  871. });
  872. // patch advanceTimersByTime(), call tick() in the fakeAsyncTest
  873. api.patchMethod(Timer, 'advanceTimersByTime', delegate => {
  874. return function (self, args) {
  875. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  876. if (fakeAsyncZoneSpec) {
  877. fakeAsyncZoneSpec.tick(args[0]);
  878. }
  879. else {
  880. return delegate.apply(self, args);
  881. }
  882. };
  883. });
  884. // patch runOnlyPendingTimers(), call flushOnlyPendingTimers() in the fakeAsyncTest
  885. api.patchMethod(Timer, 'runOnlyPendingTimers', delegate => {
  886. return function (self, args) {
  887. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  888. if (fakeAsyncZoneSpec) {
  889. fakeAsyncZoneSpec.flushOnlyPendingTimers();
  890. }
  891. else {
  892. return delegate.apply(self, args);
  893. }
  894. };
  895. });
  896. // patch advanceTimersToNextTimer(), call tickToNext() in the fakeAsyncTest
  897. api.patchMethod(Timer, 'advanceTimersToNextTimer', delegate => {
  898. return function (self, args) {
  899. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  900. if (fakeAsyncZoneSpec) {
  901. fakeAsyncZoneSpec.tickToNext(args[0]);
  902. }
  903. else {
  904. return delegate.apply(self, args);
  905. }
  906. };
  907. });
  908. // patch clearAllTimers(), call removeAllTimers() in the fakeAsyncTest
  909. api.patchMethod(Timer, 'clearAllTimers', delegate => {
  910. return function (self, args) {
  911. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  912. if (fakeAsyncZoneSpec) {
  913. fakeAsyncZoneSpec.removeAllTimers();
  914. }
  915. else {
  916. return delegate.apply(self, args);
  917. }
  918. };
  919. });
  920. // patch getTimerCount(), call getTimerCount() in the fakeAsyncTest
  921. api.patchMethod(Timer, 'getTimerCount', delegate => {
  922. return function (self, args) {
  923. const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  924. if (fakeAsyncZoneSpec) {
  925. return fakeAsyncZoneSpec.getTimerCount();
  926. }
  927. else {
  928. return delegate.apply(self, args);
  929. }
  930. };
  931. });
  932. };
  933. });
  934. Zone.__load_patch('mocha', (global, Zone) => {
  935. const Mocha = global.Mocha;
  936. if (typeof Mocha === 'undefined') {
  937. // return if Mocha is not available, because now zone-testing
  938. // will load mocha patch with jasmine/jest patch
  939. return;
  940. }
  941. if (typeof Zone === 'undefined') {
  942. throw new Error('Missing Zone.js');
  943. }
  944. const ProxyZoneSpec = Zone['ProxyZoneSpec'];
  945. const SyncTestZoneSpec = Zone['SyncTestZoneSpec'];
  946. if (!ProxyZoneSpec) {
  947. throw new Error('Missing ProxyZoneSpec');
  948. }
  949. if (Mocha['__zone_patch__']) {
  950. throw new Error('"Mocha" has already been patched with "Zone".');
  951. }
  952. Mocha['__zone_patch__'] = true;
  953. const rootZone = Zone.current;
  954. const syncZone = rootZone.fork(new SyncTestZoneSpec('Mocha.describe'));
  955. let testZone = null;
  956. const suiteZone = rootZone.fork(new ProxyZoneSpec());
  957. const mochaOriginal = {
  958. after: global.after,
  959. afterEach: global.afterEach,
  960. before: global.before,
  961. beforeEach: global.beforeEach,
  962. describe: global.describe,
  963. it: global.it
  964. };
  965. function modifyArguments(args, syncTest, asyncTest) {
  966. for (let i = 0; i < args.length; i++) {
  967. let arg = args[i];
  968. if (typeof arg === 'function') {
  969. // The `done` callback is only passed through if the function expects at
  970. // least one argument.
  971. // Note we have to make a function with correct number of arguments,
  972. // otherwise mocha will
  973. // think that all functions are sync or async.
  974. args[i] = (arg.length === 0) ? syncTest(arg) : asyncTest(arg);
  975. // Mocha uses toString to view the test body in the result list, make sure we return the
  976. // correct function body
  977. args[i].toString = function () {
  978. return arg.toString();
  979. };
  980. }
  981. }
  982. return args;
  983. }
  984. function wrapDescribeInZone(args) {
  985. const syncTest = function (fn) {
  986. return function () {
  987. return syncZone.run(fn, this, arguments);
  988. };
  989. };
  990. return modifyArguments(args, syncTest);
  991. }
  992. function wrapTestInZone(args) {
  993. const asyncTest = function (fn) {
  994. return function (done) {
  995. return testZone.run(fn, this, [done]);
  996. };
  997. };
  998. const syncTest = function (fn) {
  999. return function () {
  1000. return testZone.run(fn, this);
  1001. };
  1002. };
  1003. return modifyArguments(args, syncTest, asyncTest);
  1004. }
  1005. function wrapSuiteInZone(args) {
  1006. const asyncTest = function (fn) {
  1007. return function (done) {
  1008. return suiteZone.run(fn, this, [done]);
  1009. };
  1010. };
  1011. const syncTest = function (fn) {
  1012. return function () {
  1013. return suiteZone.run(fn, this);
  1014. };
  1015. };
  1016. return modifyArguments(args, syncTest, asyncTest);
  1017. }
  1018. global.describe = global.suite = function () {
  1019. return mochaOriginal.describe.apply(this, wrapDescribeInZone(arguments));
  1020. };
  1021. global.xdescribe = global.suite.skip = function () {
  1022. return mochaOriginal.describe.skip.apply(this, wrapDescribeInZone(arguments));
  1023. };
  1024. global.describe.only = global.suite.only = function () {
  1025. return mochaOriginal.describe.only.apply(this, wrapDescribeInZone(arguments));
  1026. };
  1027. global.it = global.specify = global.test = function () {
  1028. return mochaOriginal.it.apply(this, wrapTestInZone(arguments));
  1029. };
  1030. global.xit = global.xspecify = function () {
  1031. return mochaOriginal.it.skip.apply(this, wrapTestInZone(arguments));
  1032. };
  1033. global.it.only = global.test.only = function () {
  1034. return mochaOriginal.it.only.apply(this, wrapTestInZone(arguments));
  1035. };
  1036. global.after = global.suiteTeardown = function () {
  1037. return mochaOriginal.after.apply(this, wrapSuiteInZone(arguments));
  1038. };
  1039. global.afterEach = global.teardown = function () {
  1040. return mochaOriginal.afterEach.apply(this, wrapTestInZone(arguments));
  1041. };
  1042. global.before = global.suiteSetup = function () {
  1043. return mochaOriginal.before.apply(this, wrapSuiteInZone(arguments));
  1044. };
  1045. global.beforeEach = global.setup = function () {
  1046. return mochaOriginal.beforeEach.apply(this, wrapTestInZone(arguments));
  1047. };
  1048. ((originalRunTest, originalRun) => {
  1049. Mocha.Runner.prototype.runTest = function (fn) {
  1050. Zone.current.scheduleMicroTask('mocha.forceTask', () => {
  1051. originalRunTest.call(this, fn);
  1052. });
  1053. };
  1054. Mocha.Runner.prototype.run = function (fn) {
  1055. this.on('test', (e) => {
  1056. testZone = rootZone.fork(new ProxyZoneSpec());
  1057. });
  1058. this.on('fail', (test, err) => {
  1059. const proxyZoneSpec = testZone && testZone.get('ProxyZoneSpec');
  1060. if (proxyZoneSpec && err) {
  1061. try {
  1062. // try catch here in case err.message is not writable
  1063. err.message += proxyZoneSpec.getAndClearPendingTasksInfo();
  1064. }
  1065. catch (error) {
  1066. }
  1067. }
  1068. });
  1069. return originalRun.call(this, fn);
  1070. };
  1071. })(Mocha.Runner.prototype.runTest, Mocha.Runner.prototype.run);
  1072. });
  1073. (function (_global) {
  1074. class AsyncTestZoneSpec {
  1075. constructor(finishCallback, failCallback, namePrefix) {
  1076. this.finishCallback = finishCallback;
  1077. this.failCallback = failCallback;
  1078. this._pendingMicroTasks = false;
  1079. this._pendingMacroTasks = false;
  1080. this._alreadyErrored = false;
  1081. this._isSync = false;
  1082. this._existingFinishTimer = null;
  1083. this.entryFunction = null;
  1084. this.runZone = Zone.current;
  1085. this.unresolvedChainedPromiseCount = 0;
  1086. this.supportWaitUnresolvedChainedPromise = false;
  1087. this.name = 'asyncTestZone for ' + namePrefix;
  1088. this.properties = { 'AsyncTestZoneSpec': this };
  1089. this.supportWaitUnresolvedChainedPromise =
  1090. _global[Zone.__symbol__('supportWaitUnResolvedChainedPromise')] === true;
  1091. }
  1092. isUnresolvedChainedPromisePending() {
  1093. return this.unresolvedChainedPromiseCount > 0;
  1094. }
  1095. _finishCallbackIfDone() {
  1096. // NOTE: Technically the `onHasTask` could fire together with the initial synchronous
  1097. // completion in `onInvoke`. `onHasTask` might call this method when it captured e.g.
  1098. // microtasks in the proxy zone that now complete as part of this async zone run.
  1099. // Consider the following scenario:
  1100. // 1. A test `beforeEach` schedules a microtask in the ProxyZone.
  1101. // 2. An actual empty `it` spec executes in the AsyncTestZone` (using e.g. `waitForAsync`).
  1102. // 3. The `onInvoke` invokes `_finishCallbackIfDone` because the spec runs synchronously.
  1103. // 4. We wait the scheduled timeout (see below) to account for unhandled promises.
  1104. // 5. The microtask from (1) finishes and `onHasTask` is invoked.
  1105. // --> We register a second `_finishCallbackIfDone` even though we have scheduled a timeout.
  1106. // If the finish timeout from below is already scheduled, terminate the existing scheduled
  1107. // finish invocation, avoiding calling `jasmine` `done` multiple times. *Note* that we would
  1108. // want to schedule a new finish callback in case the task state changes again.
  1109. if (this._existingFinishTimer !== null) {
  1110. clearTimeout(this._existingFinishTimer);
  1111. this._existingFinishTimer = null;
  1112. }
  1113. if (!(this._pendingMicroTasks || this._pendingMacroTasks ||
  1114. (this.supportWaitUnresolvedChainedPromise && this.isUnresolvedChainedPromisePending()))) {
  1115. // We wait until the next tick because we would like to catch unhandled promises which could
  1116. // cause test logic to be executed. In such cases we cannot finish with tasks pending then.
  1117. this.runZone.run(() => {
  1118. this._existingFinishTimer = setTimeout(() => {
  1119. if (!this._alreadyErrored && !(this._pendingMicroTasks || this._pendingMacroTasks)) {
  1120. this.finishCallback();
  1121. }
  1122. }, 0);
  1123. });
  1124. }
  1125. }
  1126. patchPromiseForTest() {
  1127. if (!this.supportWaitUnresolvedChainedPromise) {
  1128. return;
  1129. }
  1130. const patchPromiseForTest = Promise[Zone.__symbol__('patchPromiseForTest')];
  1131. if (patchPromiseForTest) {
  1132. patchPromiseForTest();
  1133. }
  1134. }
  1135. unPatchPromiseForTest() {
  1136. if (!this.supportWaitUnresolvedChainedPromise) {
  1137. return;
  1138. }
  1139. const unPatchPromiseForTest = Promise[Zone.__symbol__('unPatchPromiseForTest')];
  1140. if (unPatchPromiseForTest) {
  1141. unPatchPromiseForTest();
  1142. }
  1143. }
  1144. onScheduleTask(delegate, current, target, task) {
  1145. if (task.type !== 'eventTask') {
  1146. this._isSync = false;
  1147. }
  1148. if (task.type === 'microTask' && task.data && task.data instanceof Promise) {
  1149. // check whether the promise is a chained promise
  1150. if (task.data[AsyncTestZoneSpec.symbolParentUnresolved] === true) {
  1151. // chained promise is being scheduled
  1152. this.unresolvedChainedPromiseCount--;
  1153. }
  1154. }
  1155. return delegate.scheduleTask(target, task);
  1156. }
  1157. onInvokeTask(delegate, current, target, task, applyThis, applyArgs) {
  1158. if (task.type !== 'eventTask') {
  1159. this._isSync = false;
  1160. }
  1161. return delegate.invokeTask(target, task, applyThis, applyArgs);
  1162. }
  1163. onCancelTask(delegate, current, target, task) {
  1164. if (task.type !== 'eventTask') {
  1165. this._isSync = false;
  1166. }
  1167. return delegate.cancelTask(target, task);
  1168. }
  1169. // Note - we need to use onInvoke at the moment to call finish when a test is
  1170. // fully synchronous. TODO(juliemr): remove this when the logic for
  1171. // onHasTask changes and it calls whenever the task queues are dirty.
  1172. // updated by(JiaLiPassion), only call finish callback when no task
  1173. // was scheduled/invoked/canceled.
  1174. onInvoke(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) {
  1175. if (!this.entryFunction) {
  1176. this.entryFunction = delegate;
  1177. }
  1178. try {
  1179. this._isSync = true;
  1180. return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
  1181. }
  1182. finally {
  1183. // We need to check the delegate is the same as entryFunction or not.
  1184. // Consider the following case.
  1185. //
  1186. // asyncTestZone.run(() => { // Here the delegate will be the entryFunction
  1187. // Zone.current.run(() => { // Here the delegate will not be the entryFunction
  1188. // });
  1189. // });
  1190. //
  1191. // We only want to check whether there are async tasks scheduled
  1192. // for the entry function.
  1193. if (this._isSync && this.entryFunction === delegate) {
  1194. this._finishCallbackIfDone();
  1195. }
  1196. }
  1197. }
  1198. onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
  1199. // Let the parent try to handle the error.
  1200. const result = parentZoneDelegate.handleError(targetZone, error);
  1201. if (result) {
  1202. this.failCallback(error);
  1203. this._alreadyErrored = true;
  1204. }
  1205. return false;
  1206. }
  1207. onHasTask(delegate, current, target, hasTaskState) {
  1208. delegate.hasTask(target, hasTaskState);
  1209. // We should only trigger finishCallback when the target zone is the AsyncTestZone
  1210. // Consider the following cases.
  1211. //
  1212. // const childZone = asyncTestZone.fork({
  1213. // name: 'child',
  1214. // onHasTask: ...
  1215. // });
  1216. //
  1217. // So we have nested zones declared the onHasTask hook, in this case,
  1218. // the onHasTask will be triggered twice, and cause the finishCallbackIfDone()
  1219. // is also be invoked twice. So we need to only trigger the finishCallbackIfDone()
  1220. // when the current zone is the same as the target zone.
  1221. if (current !== target) {
  1222. return;
  1223. }
  1224. if (hasTaskState.change == 'microTask') {
  1225. this._pendingMicroTasks = hasTaskState.microTask;
  1226. this._finishCallbackIfDone();
  1227. }
  1228. else if (hasTaskState.change == 'macroTask') {
  1229. this._pendingMacroTasks = hasTaskState.macroTask;
  1230. this._finishCallbackIfDone();
  1231. }
  1232. }
  1233. }
  1234. AsyncTestZoneSpec.symbolParentUnresolved = Zone.__symbol__('parentUnresolved');
  1235. // Export the class so that new instances can be created with proper
  1236. // constructor params.
  1237. Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec;
  1238. })(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global);
  1239. Zone.__load_patch('asynctest', (global, Zone, api) => {
  1240. /**
  1241. * Wraps a test function in an asynchronous test zone. The test will automatically
  1242. * complete when all asynchronous calls within this zone are done.
  1243. */
  1244. Zone[api.symbol('asyncTest')] = function asyncTest(fn) {
  1245. // If we're running using the Jasmine test framework, adapt to call the 'done'
  1246. // function when asynchronous activity is finished.
  1247. if (global.jasmine) {
  1248. // Not using an arrow function to preserve context passed from call site
  1249. return function (done) {
  1250. if (!done) {
  1251. // if we run beforeEach in @angular/core/testing/testing_internal then we get no done
  1252. // fake it here and assume sync.
  1253. done = function () { };
  1254. done.fail = function (e) {
  1255. throw e;
  1256. };
  1257. }
  1258. runInTestZone(fn, this, done, (err) => {
  1259. if (typeof err === 'string') {
  1260. return done.fail(new Error(err));
  1261. }
  1262. else {
  1263. done.fail(err);
  1264. }
  1265. });
  1266. };
  1267. }
  1268. // Otherwise, return a promise which will resolve when asynchronous activity
  1269. // is finished. This will be correctly consumed by the Mocha framework with
  1270. // it('...', async(myFn)); or can be used in a custom framework.
  1271. // Not using an arrow function to preserve context passed from call site
  1272. return function () {
  1273. return new Promise((finishCallback, failCallback) => {
  1274. runInTestZone(fn, this, finishCallback, failCallback);
  1275. });
  1276. };
  1277. };
  1278. function runInTestZone(fn, context, finishCallback, failCallback) {
  1279. const currentZone = Zone.current;
  1280. const AsyncTestZoneSpec = Zone['AsyncTestZoneSpec'];
  1281. if (AsyncTestZoneSpec === undefined) {
  1282. throw new Error('AsyncTestZoneSpec is needed for the async() test helper but could not be found. ' +
  1283. 'Please make sure that your environment includes zone.js/plugins/async-test');
  1284. }
  1285. const ProxyZoneSpec = Zone['ProxyZoneSpec'];
  1286. if (!ProxyZoneSpec) {
  1287. throw new Error('ProxyZoneSpec is needed for the async() test helper but could not be found. ' +
  1288. 'Please make sure that your environment includes zone.js/plugins/proxy');
  1289. }
  1290. const proxyZoneSpec = ProxyZoneSpec.get();
  1291. ProxyZoneSpec.assertPresent();
  1292. // We need to create the AsyncTestZoneSpec outside the ProxyZone.
  1293. // If we do it in ProxyZone then we will get to infinite recursion.
  1294. const proxyZone = Zone.current.getZoneWith('ProxyZoneSpec');
  1295. const previousDelegate = proxyZoneSpec.getDelegate();
  1296. proxyZone.parent.run(() => {
  1297. const testZoneSpec = new AsyncTestZoneSpec(() => {
  1298. // Need to restore the original zone.
  1299. if (proxyZoneSpec.getDelegate() == testZoneSpec) {
  1300. // Only reset the zone spec if it's
  1301. // still this one. Otherwise, assume
  1302. // it's OK.
  1303. proxyZoneSpec.setDelegate(previousDelegate);
  1304. }
  1305. testZoneSpec.unPatchPromiseForTest();
  1306. currentZone.run(() => {
  1307. finishCallback();
  1308. });
  1309. }, (error) => {
  1310. // Need to restore the original zone.
  1311. if (proxyZoneSpec.getDelegate() == testZoneSpec) {
  1312. // Only reset the zone spec if it's sill this one. Otherwise, assume it's OK.
  1313. proxyZoneSpec.setDelegate(previousDelegate);
  1314. }
  1315. testZoneSpec.unPatchPromiseForTest();
  1316. currentZone.run(() => {
  1317. failCallback(error);
  1318. });
  1319. }, 'test');
  1320. proxyZoneSpec.setDelegate(testZoneSpec);
  1321. testZoneSpec.patchPromiseForTest();
  1322. });
  1323. return Zone.current.runGuarded(fn, context);
  1324. }
  1325. });
  1326. (function (global) {
  1327. const OriginalDate = global.Date;
  1328. // Since when we compile this file to `es2015`, and if we define
  1329. // this `FakeDate` as `class FakeDate`, and then set `FakeDate.prototype`
  1330. // there will be an error which is `Cannot assign to read only property 'prototype'`
  1331. // so we need to use function implementation here.
  1332. function FakeDate() {
  1333. if (arguments.length === 0) {
  1334. const d = new OriginalDate();
  1335. d.setTime(FakeDate.now());
  1336. return d;
  1337. }
  1338. else {
  1339. const args = Array.prototype.slice.call(arguments);
  1340. return new OriginalDate(...args);
  1341. }
  1342. }
  1343. FakeDate.now = function () {
  1344. const fakeAsyncTestZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  1345. if (fakeAsyncTestZoneSpec) {
  1346. return fakeAsyncTestZoneSpec.getFakeSystemTime();
  1347. }
  1348. return OriginalDate.now.apply(this, arguments);
  1349. };
  1350. FakeDate.UTC = OriginalDate.UTC;
  1351. FakeDate.parse = OriginalDate.parse;
  1352. // keep a reference for zone patched timer function
  1353. const timers = {
  1354. setTimeout: global.setTimeout,
  1355. setInterval: global.setInterval,
  1356. clearTimeout: global.clearTimeout,
  1357. clearInterval: global.clearInterval
  1358. };
  1359. class Scheduler {
  1360. constructor() {
  1361. // Scheduler queue with the tuple of end time and callback function - sorted by end time.
  1362. this._schedulerQueue = [];
  1363. // Current simulated time in millis.
  1364. this._currentTickTime = 0;
  1365. // Current fake system base time in millis.
  1366. this._currentFakeBaseSystemTime = OriginalDate.now();
  1367. // track requeuePeriodicTimer
  1368. this._currentTickRequeuePeriodicEntries = [];
  1369. }
  1370. getCurrentTickTime() {
  1371. return this._currentTickTime;
  1372. }
  1373. getFakeSystemTime() {
  1374. return this._currentFakeBaseSystemTime + this._currentTickTime;
  1375. }
  1376. setFakeBaseSystemTime(fakeBaseSystemTime) {
  1377. this._currentFakeBaseSystemTime = fakeBaseSystemTime;
  1378. }
  1379. getRealSystemTime() {
  1380. return OriginalDate.now();
  1381. }
  1382. scheduleFunction(cb, delay, options) {
  1383. options = {
  1384. ...{
  1385. args: [],
  1386. isPeriodic: false,
  1387. isRequestAnimationFrame: false,
  1388. id: -1,
  1389. isRequeuePeriodic: false
  1390. },
  1391. ...options
  1392. };
  1393. let currentId = options.id < 0 ? Scheduler.nextId++ : options.id;
  1394. let endTime = this._currentTickTime + delay;
  1395. // Insert so that scheduler queue remains sorted by end time.
  1396. let newEntry = {
  1397. endTime: endTime,
  1398. id: currentId,
  1399. func: cb,
  1400. args: options.args,
  1401. delay: delay,
  1402. isPeriodic: options.isPeriodic,
  1403. isRequestAnimationFrame: options.isRequestAnimationFrame
  1404. };
  1405. if (options.isRequeuePeriodic) {
  1406. this._currentTickRequeuePeriodicEntries.push(newEntry);
  1407. }
  1408. let i = 0;
  1409. for (; i < this._schedulerQueue.length; i++) {
  1410. let currentEntry = this._schedulerQueue[i];
  1411. if (newEntry.endTime < currentEntry.endTime) {
  1412. break;
  1413. }
  1414. }
  1415. this._schedulerQueue.splice(i, 0, newEntry);
  1416. return currentId;
  1417. }
  1418. removeScheduledFunctionWithId(id) {
  1419. for (let i = 0; i < this._schedulerQueue.length; i++) {
  1420. if (this._schedulerQueue[i].id == id) {
  1421. this._schedulerQueue.splice(i, 1);
  1422. break;
  1423. }
  1424. }
  1425. }
  1426. removeAll() {
  1427. this._schedulerQueue = [];
  1428. }
  1429. getTimerCount() {
  1430. return this._schedulerQueue.length;
  1431. }
  1432. tickToNext(step = 1, doTick, tickOptions) {
  1433. if (this._schedulerQueue.length < step) {
  1434. return;
  1435. }
  1436. // Find the last task currently queued in the scheduler queue and tick
  1437. // till that time.
  1438. const startTime = this._currentTickTime;
  1439. const targetTask = this._schedulerQueue[step - 1];
  1440. this.tick(targetTask.endTime - startTime, doTick, tickOptions);
  1441. }
  1442. tick(millis = 0, doTick, tickOptions) {
  1443. let finalTime = this._currentTickTime + millis;
  1444. let lastCurrentTime = 0;
  1445. tickOptions = Object.assign({ processNewMacroTasksSynchronously: true }, tickOptions);
  1446. // we need to copy the schedulerQueue so nested timeout
  1447. // will not be wrongly called in the current tick
  1448. // https://github.com/angular/angular/issues/33799
  1449. const schedulerQueue = tickOptions.processNewMacroTasksSynchronously ?
  1450. this._schedulerQueue :
  1451. this._schedulerQueue.slice();
  1452. if (schedulerQueue.length === 0 && doTick) {
  1453. doTick(millis);
  1454. return;
  1455. }
  1456. while (schedulerQueue.length > 0) {
  1457. // clear requeueEntries before each loop
  1458. this._currentTickRequeuePeriodicEntries = [];
  1459. let current = schedulerQueue[0];
  1460. if (finalTime < current.endTime) {
  1461. // Done processing the queue since it's sorted by endTime.
  1462. break;
  1463. }
  1464. else {
  1465. // Time to run scheduled function. Remove it from the head of queue.
  1466. let current = schedulerQueue.shift();
  1467. if (!tickOptions.processNewMacroTasksSynchronously) {
  1468. const idx = this._schedulerQueue.indexOf(current);
  1469. if (idx >= 0) {
  1470. this._schedulerQueue.splice(idx, 1);
  1471. }
  1472. }
  1473. lastCurrentTime = this._currentTickTime;
  1474. this._currentTickTime = current.endTime;
  1475. if (doTick) {
  1476. doTick(this._currentTickTime - lastCurrentTime);
  1477. }
  1478. let retval = current.func.apply(global, current.isRequestAnimationFrame ? [this._currentTickTime] : current.args);
  1479. if (!retval) {
  1480. // Uncaught exception in the current scheduled function. Stop processing the queue.
  1481. break;
  1482. }
  1483. // check is there any requeue periodic entry is added in
  1484. // current loop, if there is, we need to add to current loop
  1485. if (!tickOptions.processNewMacroTasksSynchronously) {
  1486. this._currentTickRequeuePeriodicEntries.forEach(newEntry => {
  1487. let i = 0;
  1488. for (; i < schedulerQueue.length; i++) {
  1489. const currentEntry = schedulerQueue[i];
  1490. if (newEntry.endTime < currentEntry.endTime) {
  1491. break;
  1492. }
  1493. }
  1494. schedulerQueue.splice(i, 0, newEntry);
  1495. });
  1496. }
  1497. }
  1498. }
  1499. lastCurrentTime = this._currentTickTime;
  1500. this._currentTickTime = finalTime;
  1501. if (doTick) {
  1502. doTick(this._currentTickTime - lastCurrentTime);
  1503. }
  1504. }
  1505. flushOnlyPendingTimers(doTick) {
  1506. if (this._schedulerQueue.length === 0) {
  1507. return 0;
  1508. }
  1509. // Find the last task currently queued in the scheduler queue and tick
  1510. // till that time.
  1511. const startTime = this._currentTickTime;
  1512. const lastTask = this._schedulerQueue[this._schedulerQueue.length - 1];
  1513. this.tick(lastTask.endTime - startTime, doTick, { processNewMacroTasksSynchronously: false });
  1514. return this._currentTickTime - startTime;
  1515. }
  1516. flush(limit = 20, flushPeriodic = false, doTick) {
  1517. if (flushPeriodic) {
  1518. return this.flushPeriodic(doTick);
  1519. }
  1520. else {
  1521. return this.flushNonPeriodic(limit, doTick);
  1522. }
  1523. }
  1524. flushPeriodic(doTick) {
  1525. if (this._schedulerQueue.length === 0) {
  1526. return 0;
  1527. }
  1528. // Find the last task currently queued in the scheduler queue and tick
  1529. // till that time.
  1530. const startTime = this._currentTickTime;
  1531. const lastTask = this._schedulerQueue[this._schedulerQueue.length - 1];
  1532. this.tick(lastTask.endTime - startTime, doTick);
  1533. return this._currentTickTime - startTime;
  1534. }
  1535. flushNonPeriodic(limit, doTick) {
  1536. const startTime = this._currentTickTime;
  1537. let lastCurrentTime = 0;
  1538. let count = 0;
  1539. while (this._schedulerQueue.length > 0) {
  1540. count++;
  1541. if (count > limit) {
  1542. throw new Error('flush failed after reaching the limit of ' + limit +
  1543. ' tasks. Does your code use a polling timeout?');
  1544. }
  1545. // flush only non-periodic timers.
  1546. // If the only remaining tasks are periodic(or requestAnimationFrame), finish flushing.
  1547. if (this._schedulerQueue.filter(task => !task.isPeriodic && !task.isRequestAnimationFrame)
  1548. .length === 0) {
  1549. break;
  1550. }
  1551. const current = this._schedulerQueue.shift();
  1552. lastCurrentTime = this._currentTickTime;
  1553. this._currentTickTime = current.endTime;
  1554. if (doTick) {
  1555. // Update any secondary schedulers like Jasmine mock Date.
  1556. doTick(this._currentTickTime - lastCurrentTime);
  1557. }
  1558. const retval = current.func.apply(global, current.args);
  1559. if (!retval) {
  1560. // Uncaught exception in the current scheduled function. Stop processing the queue.
  1561. break;
  1562. }
  1563. }
  1564. return this._currentTickTime - startTime;
  1565. }
  1566. }
  1567. // Next scheduler id.
  1568. Scheduler.nextId = 1;
  1569. class FakeAsyncTestZoneSpec {
  1570. static assertInZone() {
  1571. if (Zone.current.get('FakeAsyncTestZoneSpec') == null) {
  1572. throw new Error('The code should be running in the fakeAsync zone to call this function');
  1573. }
  1574. }
  1575. constructor(namePrefix, trackPendingRequestAnimationFrame = false, macroTaskOptions) {
  1576. this.trackPendingRequestAnimationFrame = trackPendingRequestAnimationFrame;
  1577. this.macroTaskOptions = macroTaskOptions;
  1578. this._scheduler = new Scheduler();
  1579. this._microtasks = [];
  1580. this._lastError = null;
  1581. this._uncaughtPromiseErrors = Promise[Zone.__symbol__('uncaughtPromiseErrors')];
  1582. this.pendingPeriodicTimers = [];
  1583. this.pendingTimers = [];
  1584. this.patchDateLocked = false;
  1585. this.properties = { 'FakeAsyncTestZoneSpec': this };
  1586. this.name = 'fakeAsyncTestZone for ' + namePrefix;
  1587. // in case user can't access the construction of FakeAsyncTestSpec
  1588. // user can also define macroTaskOptions by define a global variable.
  1589. if (!this.macroTaskOptions) {
  1590. this.macroTaskOptions = global[Zone.__symbol__('FakeAsyncTestMacroTask')];
  1591. }
  1592. }
  1593. _fnAndFlush(fn, completers) {
  1594. return (...args) => {
  1595. fn.apply(global, args);
  1596. if (this._lastError === null) { // Success
  1597. if (completers.onSuccess != null) {
  1598. completers.onSuccess.apply(global);
  1599. }
  1600. // Flush microtasks only on success.
  1601. this.flushMicrotasks();
  1602. }
  1603. else { // Failure
  1604. if (completers.onError != null) {
  1605. completers.onError.apply(global);
  1606. }
  1607. }
  1608. // Return true if there were no errors, false otherwise.
  1609. return this._lastError === null;
  1610. };
  1611. }
  1612. static _removeTimer(timers, id) {
  1613. let index = timers.indexOf(id);
  1614. if (index > -1) {
  1615. timers.splice(index, 1);
  1616. }
  1617. }
  1618. _dequeueTimer(id) {
  1619. return () => {
  1620. FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id);
  1621. };
  1622. }
  1623. _requeuePeriodicTimer(fn, interval, args, id) {
  1624. return () => {
  1625. // Requeue the timer callback if it's not been canceled.
  1626. if (this.pendingPeriodicTimers.indexOf(id) !== -1) {
  1627. this._scheduler.scheduleFunction(fn, interval, { args, isPeriodic: true, id, isRequeuePeriodic: true });
  1628. }
  1629. };
  1630. }
  1631. _dequeuePeriodicTimer(id) {
  1632. return () => {
  1633. FakeAsyncTestZoneSpec._removeTimer(this.pendingPeriodicTimers, id);
  1634. };
  1635. }
  1636. _setTimeout(fn, delay, args, isTimer = true) {
  1637. let removeTimerFn = this._dequeueTimer(Scheduler.nextId);
  1638. // Queue the callback and dequeue the timer on success and error.
  1639. let cb = this._fnAndFlush(fn, { onSuccess: removeTimerFn, onError: removeTimerFn });
  1640. let id = this._scheduler.scheduleFunction(cb, delay, { args, isRequestAnimationFrame: !isTimer });
  1641. if (isTimer) {
  1642. this.pendingTimers.push(id);
  1643. }
  1644. return id;
  1645. }
  1646. _clearTimeout(id) {
  1647. FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id);
  1648. this._scheduler.removeScheduledFunctionWithId(id);
  1649. }
  1650. _setInterval(fn, interval, args) {
  1651. let id = Scheduler.nextId;
  1652. let completers = { onSuccess: null, onError: this._dequeuePeriodicTimer(id) };
  1653. let cb = this._fnAndFlush(fn, completers);
  1654. // Use the callback created above to requeue on success.
  1655. completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id);
  1656. // Queue the callback and dequeue the periodic timer only on error.
  1657. this._scheduler.scheduleFunction(cb, interval, { args, isPeriodic: true });
  1658. this.pendingPeriodicTimers.push(id);
  1659. return id;
  1660. }
  1661. _clearInterval(id) {
  1662. FakeAsyncTestZoneSpec._removeTimer(this.pendingPeriodicTimers, id);
  1663. this._scheduler.removeScheduledFunctionWithId(id);
  1664. }
  1665. _resetLastErrorAndThrow() {
  1666. let error = this._lastError || this._uncaughtPromiseErrors[0];
  1667. this._uncaughtPromiseErrors.length = 0;
  1668. this._lastError = null;
  1669. throw error;
  1670. }
  1671. getCurrentTickTime() {
  1672. return this._scheduler.getCurrentTickTime();
  1673. }
  1674. getFakeSystemTime() {
  1675. return this._scheduler.getFakeSystemTime();
  1676. }
  1677. setFakeBaseSystemTime(realTime) {
  1678. this._scheduler.setFakeBaseSystemTime(realTime);
  1679. }
  1680. getRealSystemTime() {
  1681. return this._scheduler.getRealSystemTime();
  1682. }
  1683. static patchDate() {
  1684. if (!!global[Zone.__symbol__('disableDatePatching')]) {
  1685. // we don't want to patch global Date
  1686. // because in some case, global Date
  1687. // is already being patched, we need to provide
  1688. // an option to let user still use their
  1689. // own version of Date.
  1690. return;
  1691. }
  1692. if (global['Date'] === FakeDate) {
  1693. // already patched
  1694. return;
  1695. }
  1696. global['Date'] = FakeDate;
  1697. FakeDate.prototype = OriginalDate.prototype;
  1698. // try check and reset timers
  1699. // because jasmine.clock().install() may
  1700. // have replaced the global timer
  1701. FakeAsyncTestZoneSpec.checkTimerPatch();
  1702. }
  1703. static resetDate() {
  1704. if (global['Date'] === FakeDate) {
  1705. global['Date'] = OriginalDate;
  1706. }
  1707. }
  1708. static checkTimerPatch() {
  1709. if (global.setTimeout !== timers.setTimeout) {
  1710. global.setTimeout = timers.setTimeout;
  1711. global.clearTimeout = timers.clearTimeout;
  1712. }
  1713. if (global.setInterval !== timers.setInterval) {
  1714. global.setInterval = timers.setInterval;
  1715. global.clearInterval = timers.clearInterval;
  1716. }
  1717. }
  1718. lockDatePatch() {
  1719. this.patchDateLocked = true;
  1720. FakeAsyncTestZoneSpec.patchDate();
  1721. }
  1722. unlockDatePatch() {
  1723. this.patchDateLocked = false;
  1724. FakeAsyncTestZoneSpec.resetDate();
  1725. }
  1726. tickToNext(steps = 1, doTick, tickOptions = { processNewMacroTasksSynchronously: true }) {
  1727. if (steps <= 0) {
  1728. return;
  1729. }
  1730. FakeAsyncTestZoneSpec.assertInZone();
  1731. this.flushMicrotasks();
  1732. this._scheduler.tickToNext(steps, doTick, tickOptions);
  1733. if (this._lastError !== null) {
  1734. this._resetLastErrorAndThrow();
  1735. }
  1736. }
  1737. tick(millis = 0, doTick, tickOptions = { processNewMacroTasksSynchronously: true }) {
  1738. FakeAsyncTestZoneSpec.assertInZone();
  1739. this.flushMicrotasks();
  1740. this._scheduler.tick(millis, doTick, tickOptions);
  1741. if (this._lastError !== null) {
  1742. this._resetLastErrorAndThrow();
  1743. }
  1744. }
  1745. flushMicrotasks() {
  1746. FakeAsyncTestZoneSpec.assertInZone();
  1747. const flushErrors = () => {
  1748. if (this._lastError !== null || this._uncaughtPromiseErrors.length) {
  1749. // If there is an error stop processing the microtask queue and rethrow the error.
  1750. this._resetLastErrorAndThrow();
  1751. }
  1752. };
  1753. while (this._microtasks.length > 0) {
  1754. let microtask = this._microtasks.shift();
  1755. microtask.func.apply(microtask.target, microtask.args);
  1756. }
  1757. flushErrors();
  1758. }
  1759. flush(limit, flushPeriodic, doTick) {
  1760. FakeAsyncTestZoneSpec.assertInZone();
  1761. this.flushMicrotasks();
  1762. const elapsed = this._scheduler.flush(limit, flushPeriodic, doTick);
  1763. if (this._lastError !== null) {
  1764. this._resetLastErrorAndThrow();
  1765. }
  1766. return elapsed;
  1767. }
  1768. flushOnlyPendingTimers(doTick) {
  1769. FakeAsyncTestZoneSpec.assertInZone();
  1770. this.flushMicrotasks();
  1771. const elapsed = this._scheduler.flushOnlyPendingTimers(doTick);
  1772. if (this._lastError !== null) {
  1773. this._resetLastErrorAndThrow();
  1774. }
  1775. return elapsed;
  1776. }
  1777. removeAllTimers() {
  1778. FakeAsyncTestZoneSpec.assertInZone();
  1779. this._scheduler.removeAll();
  1780. this.pendingPeriodicTimers = [];
  1781. this.pendingTimers = [];
  1782. }
  1783. getTimerCount() {
  1784. return this._scheduler.getTimerCount() + this._microtasks.length;
  1785. }
  1786. onScheduleTask(delegate, current, target, task) {
  1787. switch (task.type) {
  1788. case 'microTask':
  1789. let args = task.data && task.data.args;
  1790. // should pass additional arguments to callback if have any
  1791. // currently we know process.nextTick will have such additional
  1792. // arguments
  1793. let additionalArgs;
  1794. if (args) {
  1795. let callbackIndex = task.data.cbIdx;
  1796. if (typeof args.length === 'number' && args.length > callbackIndex + 1) {
  1797. additionalArgs = Array.prototype.slice.call(args, callbackIndex + 1);
  1798. }
  1799. }
  1800. this._microtasks.push({
  1801. func: task.invoke,
  1802. args: additionalArgs,
  1803. target: task.data && task.data.target
  1804. });
  1805. break;
  1806. case 'macroTask':
  1807. switch (task.source) {
  1808. case 'setTimeout':
  1809. task.data['handleId'] = this._setTimeout(task.invoke, task.data['delay'], Array.prototype.slice.call(task.data['args'], 2));
  1810. break;
  1811. case 'setImmediate':
  1812. task.data['handleId'] = this._setTimeout(task.invoke, 0, Array.prototype.slice.call(task.data['args'], 1));
  1813. break;
  1814. case 'setInterval':
  1815. task.data['handleId'] = this._setInterval(task.invoke, task.data['delay'], Array.prototype.slice.call(task.data['args'], 2));
  1816. break;
  1817. case 'XMLHttpRequest.send':
  1818. throw new Error('Cannot make XHRs from within a fake async test. Request URL: ' +
  1819. task.data['url']);
  1820. case 'requestAnimationFrame':
  1821. case 'webkitRequestAnimationFrame':
  1822. case 'mozRequestAnimationFrame':
  1823. // Simulate a requestAnimationFrame by using a setTimeout with 16 ms.
  1824. // (60 frames per second)
  1825. task.data['handleId'] = this._setTimeout(task.invoke, 16, task.data['args'], this.trackPendingRequestAnimationFrame);
  1826. break;
  1827. default:
  1828. // user can define which macroTask they want to support by passing
  1829. // macroTaskOptions
  1830. const macroTaskOption = this.findMacroTaskOption(task);
  1831. if (macroTaskOption) {
  1832. const args = task.data && task.data['args'];
  1833. const delay = args && args.length > 1 ? args[1] : 0;
  1834. let callbackArgs = macroTaskOption.callbackArgs ? macroTaskOption.callbackArgs : args;
  1835. if (!!macroTaskOption.isPeriodic) {
  1836. // periodic macroTask, use setInterval to simulate
  1837. task.data['handleId'] = this._setInterval(task.invoke, delay, callbackArgs);
  1838. task.data.isPeriodic = true;
  1839. }
  1840. else {
  1841. // not periodic, use setTimeout to simulate
  1842. task.data['handleId'] = this._setTimeout(task.invoke, delay, callbackArgs);
  1843. }
  1844. break;
  1845. }
  1846. throw new Error('Unknown macroTask scheduled in fake async test: ' + task.source);
  1847. }
  1848. break;
  1849. case 'eventTask':
  1850. task = delegate.scheduleTask(target, task);
  1851. break;
  1852. }
  1853. return task;
  1854. }
  1855. onCancelTask(delegate, current, target, task) {
  1856. switch (task.source) {
  1857. case 'setTimeout':
  1858. case 'requestAnimationFrame':
  1859. case 'webkitRequestAnimationFrame':
  1860. case 'mozRequestAnimationFrame':
  1861. return this._clearTimeout(task.data['handleId']);
  1862. case 'setInterval':
  1863. return this._clearInterval(task.data['handleId']);
  1864. default:
  1865. // user can define which macroTask they want to support by passing
  1866. // macroTaskOptions
  1867. const macroTaskOption = this.findMacroTaskOption(task);
  1868. if (macroTaskOption) {
  1869. const handleId = task.data['handleId'];
  1870. return macroTaskOption.isPeriodic ? this._clearInterval(handleId) :
  1871. this._clearTimeout(handleId);
  1872. }
  1873. return delegate.cancelTask(target, task);
  1874. }
  1875. }
  1876. onInvoke(delegate, current, target, callback, applyThis, applyArgs, source) {
  1877. try {
  1878. FakeAsyncTestZoneSpec.patchDate();
  1879. return delegate.invoke(target, callback, applyThis, applyArgs, source);
  1880. }
  1881. finally {
  1882. if (!this.patchDateLocked) {
  1883. FakeAsyncTestZoneSpec.resetDate();
  1884. }
  1885. }
  1886. }
  1887. findMacroTaskOption(task) {
  1888. if (!this.macroTaskOptions) {
  1889. return null;
  1890. }
  1891. for (let i = 0; i < this.macroTaskOptions.length; i++) {
  1892. const macroTaskOption = this.macroTaskOptions[i];
  1893. if (macroTaskOption.source === task.source) {
  1894. return macroTaskOption;
  1895. }
  1896. }
  1897. return null;
  1898. }
  1899. onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
  1900. this._lastError = error;
  1901. return false; // Don't propagate error to parent zone.
  1902. }
  1903. }
  1904. // Export the class so that new instances can be created with proper
  1905. // constructor params.
  1906. Zone['FakeAsyncTestZoneSpec'] = FakeAsyncTestZoneSpec;
  1907. })(typeof window === 'object' && window || typeof self === 'object' && self || global);
  1908. Zone.__load_patch('fakeasync', (global, Zone, api) => {
  1909. const FakeAsyncTestZoneSpec = Zone && Zone['FakeAsyncTestZoneSpec'];
  1910. function getProxyZoneSpec() {
  1911. return Zone && Zone['ProxyZoneSpec'];
  1912. }
  1913. let _fakeAsyncTestZoneSpec = null;
  1914. /**
  1915. * Clears out the shared fake async zone for a test.
  1916. * To be called in a global `beforeEach`.
  1917. *
  1918. * @experimental
  1919. */
  1920. function resetFakeAsyncZone() {
  1921. if (_fakeAsyncTestZoneSpec) {
  1922. _fakeAsyncTestZoneSpec.unlockDatePatch();
  1923. }
  1924. _fakeAsyncTestZoneSpec = null;
  1925. // in node.js testing we may not have ProxyZoneSpec in which case there is nothing to reset.
  1926. getProxyZoneSpec() && getProxyZoneSpec().assertPresent().resetDelegate();
  1927. }
  1928. /**
  1929. * Wraps a function to be executed in the fakeAsync zone:
  1930. * - microtasks are manually executed by calling `flushMicrotasks()`,
  1931. * - timers are synchronous, `tick()` simulates the asynchronous passage of time.
  1932. *
  1933. * If there are any pending timers at the end of the function, an exception will be thrown.
  1934. *
  1935. * Can be used to wrap inject() calls.
  1936. *
  1937. * ## Example
  1938. *
  1939. * {@example core/testing/ts/fake_async.ts region='basic'}
  1940. *
  1941. * @param fn
  1942. * @returns The function wrapped to be executed in the fakeAsync zone
  1943. *
  1944. * @experimental
  1945. */
  1946. function fakeAsync(fn) {
  1947. // Not using an arrow function to preserve context passed from call site
  1948. const fakeAsyncFn = function (...args) {
  1949. const ProxyZoneSpec = getProxyZoneSpec();
  1950. if (!ProxyZoneSpec) {
  1951. throw new Error('ProxyZoneSpec is needed for the async() test helper but could not be found. ' +
  1952. 'Please make sure that your environment includes zone.js/plugins/proxy');
  1953. }
  1954. const proxyZoneSpec = ProxyZoneSpec.assertPresent();
  1955. if (Zone.current.get('FakeAsyncTestZoneSpec')) {
  1956. throw new Error('fakeAsync() calls can not be nested');
  1957. }
  1958. try {
  1959. // in case jasmine.clock init a fakeAsyncTestZoneSpec
  1960. if (!_fakeAsyncTestZoneSpec) {
  1961. if (proxyZoneSpec.getDelegate() instanceof FakeAsyncTestZoneSpec) {
  1962. throw new Error('fakeAsync() calls can not be nested');
  1963. }
  1964. _fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec();
  1965. }
  1966. let res;
  1967. const lastProxyZoneSpec = proxyZoneSpec.getDelegate();
  1968. proxyZoneSpec.setDelegate(_fakeAsyncTestZoneSpec);
  1969. _fakeAsyncTestZoneSpec.lockDatePatch();
  1970. try {
  1971. res = fn.apply(this, args);
  1972. flushMicrotasks();
  1973. }
  1974. finally {
  1975. proxyZoneSpec.setDelegate(lastProxyZoneSpec);
  1976. }
  1977. if (_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) {
  1978. throw new Error(`${_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` +
  1979. `periodic timer(s) still in the queue.`);
  1980. }
  1981. if (_fakeAsyncTestZoneSpec.pendingTimers.length > 0) {
  1982. throw new Error(`${_fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`);
  1983. }
  1984. return res;
  1985. }
  1986. finally {
  1987. resetFakeAsyncZone();
  1988. }
  1989. };
  1990. fakeAsyncFn.isFakeAsync = true;
  1991. return fakeAsyncFn;
  1992. }
  1993. function _getFakeAsyncZoneSpec() {
  1994. if (_fakeAsyncTestZoneSpec == null) {
  1995. _fakeAsyncTestZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  1996. if (_fakeAsyncTestZoneSpec == null) {
  1997. throw new Error('The code should be running in the fakeAsync zone to call this function');
  1998. }
  1999. }
  2000. return _fakeAsyncTestZoneSpec;
  2001. }
  2002. /**
  2003. * Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
  2004. *
  2005. * The microtasks queue is drained at the very start of this function and after any timer callback
  2006. * has been executed.
  2007. *
  2008. * ## Example
  2009. *
  2010. * {@example core/testing/ts/fake_async.ts region='basic'}
  2011. *
  2012. * @experimental
  2013. */
  2014. function tick(millis = 0, ignoreNestedTimeout = false) {
  2015. _getFakeAsyncZoneSpec().tick(millis, null, ignoreNestedTimeout);
  2016. }
  2017. /**
  2018. * Simulates the asynchronous passage of time for the timers in the fakeAsync zone by
  2019. * draining the macrotask queue until it is empty. The returned value is the milliseconds
  2020. * of time that would have been elapsed.
  2021. *
  2022. * @param maxTurns
  2023. * @returns The simulated time elapsed, in millis.
  2024. *
  2025. * @experimental
  2026. */
  2027. function flush(maxTurns) {
  2028. return _getFakeAsyncZoneSpec().flush(maxTurns);
  2029. }
  2030. /**
  2031. * Discard all remaining periodic tasks.
  2032. *
  2033. * @experimental
  2034. */
  2035. function discardPeriodicTasks() {
  2036. const zoneSpec = _getFakeAsyncZoneSpec();
  2037. zoneSpec.pendingPeriodicTimers;
  2038. zoneSpec.pendingPeriodicTimers.length = 0;
  2039. }
  2040. /**
  2041. * Flush any pending microtasks.
  2042. *
  2043. * @experimental
  2044. */
  2045. function flushMicrotasks() {
  2046. _getFakeAsyncZoneSpec().flushMicrotasks();
  2047. }
  2048. Zone[api.symbol('fakeAsyncTest')] =
  2049. { resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync };
  2050. }, true);
  2051. /**
  2052. * Promise for async/fakeAsync zoneSpec test
  2053. * can support async operation which not supported by zone.js
  2054. * such as
  2055. * it ('test jsonp in AsyncZone', async() => {
  2056. * new Promise(res => {
  2057. * jsonp(url, (data) => {
  2058. * // success callback
  2059. * res(data);
  2060. * });
  2061. * }).then((jsonpResult) => {
  2062. * // get jsonp result.
  2063. *
  2064. * // user will expect AsyncZoneSpec wait for
  2065. * // then, but because jsonp is not zone aware
  2066. * // AsyncZone will finish before then is called.
  2067. * });
  2068. * });
  2069. */
  2070. Zone.__load_patch('promisefortest', (global, Zone, api) => {
  2071. const symbolState = api.symbol('state');
  2072. const UNRESOLVED = null;
  2073. const symbolParentUnresolved = api.symbol('parentUnresolved');
  2074. // patch Promise.prototype.then to keep an internal
  2075. // number for tracking unresolved chained promise
  2076. // we will decrease this number when the parent promise
  2077. // being resolved/rejected and chained promise was
  2078. // scheduled as a microTask.
  2079. // so we can know such kind of chained promise still
  2080. // not resolved in AsyncTestZone
  2081. Promise[api.symbol('patchPromiseForTest')] = function patchPromiseForTest() {
  2082. let oriThen = Promise[Zone.__symbol__('ZonePromiseThen')];
  2083. if (oriThen) {
  2084. return;
  2085. }
  2086. oriThen = Promise[Zone.__symbol__('ZonePromiseThen')] = Promise.prototype.then;
  2087. Promise.prototype.then = function () {
  2088. const chained = oriThen.apply(this, arguments);
  2089. if (this[symbolState] === UNRESOLVED) {
  2090. // parent promise is unresolved.
  2091. const asyncTestZoneSpec = Zone.current.get('AsyncTestZoneSpec');
  2092. if (asyncTestZoneSpec) {
  2093. asyncTestZoneSpec.unresolvedChainedPromiseCount++;
  2094. chained[symbolParentUnresolved] = true;
  2095. }
  2096. }
  2097. return chained;
  2098. };
  2099. };
  2100. Promise[api.symbol('unPatchPromiseForTest')] = function unpatchPromiseForTest() {
  2101. // restore origin then
  2102. const oriThen = Promise[Zone.__symbol__('ZonePromiseThen')];
  2103. if (oriThen) {
  2104. Promise.prototype.then = oriThen;
  2105. Promise[Zone.__symbol__('ZonePromiseThen')] = undefined;
  2106. }
  2107. };
  2108. });