testing.mjs 131 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158
  1. /**
  2. * @license Angular v19.2.4
  3. * (c) 2010-2025 Google LLC. https://angular.io/
  4. * License: MIT
  5. */
  6. import * as i0 from '@angular/core';
  7. import { ɵDeferBlockState as _DeferBlockState, ɵtriggerResourceLoading as _triggerResourceLoading, ɵrenderDeferBlockState as _renderDeferBlockState, ɵCONTAINER_HEADER_OFFSET as _CONTAINER_HEADER_OFFSET, ɵgetDeferBlocks as _getDeferBlocks, InjectionToken, ɵDeferBlockBehavior as _DeferBlockBehavior, inject as inject$1, NgZone, ErrorHandler, Injectable, ɵNoopNgZone as _NoopNgZone, ApplicationRef, ɵPendingTasksInternal as _PendingTasksInternal, ɵZONELESS_ENABLED as _ZONELESS_ENABLED, ɵChangeDetectionScheduler as _ChangeDetectionScheduler, ɵEffectScheduler as _EffectScheduler, ɵMicrotaskEffectScheduler as _MicrotaskEffectScheduler, getDebugNode, RendererFactory2, ɵstringify as _stringify, Pipe, Directive, Component, NgModule, ɵReflectionCapabilities as _ReflectionCapabilities, ɵUSE_RUNTIME_DEPS_TRACKER_FOR_JIT as _USE_RUNTIME_DEPS_TRACKER_FOR_JIT, ɵdepsTracker as _depsTracker, ɵgetInjectableDef as _getInjectableDef, resolveForwardRef, ɵisComponentDefPendingResolution as _isComponentDefPendingResolution, ɵgetAsyncClassMetadataFn as _getAsyncClassMetadataFn, ɵresolveComponentResources as _resolveComponentResources, ɵRender3NgModuleRef as _Render3NgModuleRef, ApplicationInitStatus, LOCALE_ID, ɵDEFAULT_LOCALE_ID as _DEFAULT_LOCALE_ID, ɵsetLocaleId as _setLocaleId, ɵRender3ComponentFactory as _Render3ComponentFactory, ɵNG_COMP_DEF as _NG_COMP_DEF, ɵcompileComponent as _compileComponent, ɵNG_DIR_DEF as _NG_DIR_DEF, ɵcompileDirective as _compileDirective, ɵNG_PIPE_DEF as _NG_PIPE_DEF, ɵcompilePipe as _compilePipe, ɵNG_MOD_DEF as _NG_MOD_DEF, ɵpatchComponentDefWithScope as _patchComponentDefWithScope, ɵNG_INJ_DEF as _NG_INJ_DEF, ɵcompileNgModuleDefs as _compileNgModuleDefs, ɵclearResolutionOfComponentResourcesQueue as _clearResolutionOfComponentResourcesQueue, ɵrestoreComponentResolutionQueue as _restoreComponentResolutionQueue, ɵinternalProvideZoneChangeDetection as _internalProvideZoneChangeDetection, ɵChangeDetectionSchedulerImpl as _ChangeDetectionSchedulerImpl, Compiler, ɵDEFER_BLOCK_CONFIG as _DEFER_BLOCK_CONFIG, ɵINTERNAL_APPLICATION_ERROR_HANDLER as _INTERNAL_APPLICATION_ERROR_HANDLER, COMPILER_OPTIONS, Injector, ɵtransitiveScopesFor as _transitiveScopesFor, ɵgenerateStandaloneInDeclarationsError as _generateStandaloneInDeclarationsError, ɵNgModuleFactory as _NgModuleFactory, ModuleWithComponentFactories, ɵisEnvironmentProviders as _isEnvironmentProviders, ɵconvertToBitFlags as _convertToBitFlags, InjectFlags, ɵsetAllowDuplicateNgModuleIdsForTest as _setAllowDuplicateNgModuleIdsForTest, ɵresetCompiledComponents as _resetCompiledComponents, ɵsetUnknownElementStrictMode as _setUnknownElementStrictMode, ɵsetUnknownPropertyStrictMode as _setUnknownPropertyStrictMode, ɵgetUnknownElementStrictMode as _getUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode as _getUnknownPropertyStrictMode, runInInjectionContext, EnvironmentInjector, ɵflushModuleScopingQueueAsMuchAsPossible as _flushModuleScopingQueueAsMuchAsPossible } from '@angular/core';
  8. export { ɵDeferBlockBehavior as DeferBlockBehavior, ɵDeferBlockState as DeferBlockState } from '@angular/core';
  9. import { Subscription } from 'rxjs';
  10. import { ResourceLoader } from '@angular/compiler';
  11. /**
  12. * Wraps a test function in an asynchronous test zone. The test will automatically
  13. * complete when all asynchronous calls within this zone are done. Can be used
  14. * to wrap an {@link inject} call.
  15. *
  16. * Example:
  17. *
  18. * ```ts
  19. * it('...', waitForAsync(inject([AClass], (object) => {
  20. * object.doSomething.then(() => {
  21. * expect(...);
  22. * })
  23. * })));
  24. * ```
  25. *
  26. * @publicApi
  27. */
  28. function waitForAsync(fn) {
  29. const _Zone = typeof Zone !== 'undefined' ? Zone : null;
  30. if (!_Zone) {
  31. return function () {
  32. return Promise.reject('Zone is needed for the waitForAsync() test helper but could not be found. ' +
  33. 'Please make sure that your environment includes zone.js');
  34. };
  35. }
  36. const asyncTest = _Zone && _Zone[_Zone.__symbol__('asyncTest')];
  37. if (typeof asyncTest === 'function') {
  38. return asyncTest(fn);
  39. }
  40. return function () {
  41. return Promise.reject('zone-testing.js is needed for the async() test helper but could not be found. ' +
  42. 'Please make sure that your environment includes zone.js/testing');
  43. };
  44. }
  45. /**
  46. * Represents an individual defer block for testing purposes.
  47. *
  48. * @publicApi
  49. */
  50. class DeferBlockFixture {
  51. block;
  52. componentFixture;
  53. /** @nodoc */
  54. constructor(block, componentFixture) {
  55. this.block = block;
  56. this.componentFixture = componentFixture;
  57. }
  58. /**
  59. * Renders the specified state of the defer fixture.
  60. * @param state the defer state to render
  61. */
  62. async render(state) {
  63. if (!hasStateTemplate(state, this.block)) {
  64. const stateAsString = getDeferBlockStateNameFromEnum(state);
  65. throw new Error(`Tried to render this defer block in the \`${stateAsString}\` state, ` +
  66. `but there was no @${stateAsString.toLowerCase()} block defined in a template.`);
  67. }
  68. if (state === _DeferBlockState.Complete) {
  69. await _triggerResourceLoading(this.block.tDetails, this.block.lView, this.block.tNode);
  70. }
  71. // If the `render` method is used explicitly - skip timer-based scheduling for
  72. // `@placeholder` and `@loading` blocks and render them immediately.
  73. const skipTimerScheduling = true;
  74. _renderDeferBlockState(state, this.block.tNode, this.block.lContainer, skipTimerScheduling);
  75. this.componentFixture.detectChanges();
  76. }
  77. /**
  78. * Retrieves all nested child defer block fixtures
  79. * in a given defer block.
  80. */
  81. getDeferBlocks() {
  82. const deferBlocks = [];
  83. // An LContainer that represents a defer block has at most 1 view, which is
  84. // located right after an LContainer header. Get a hold of that view and inspect
  85. // it for nested defer blocks.
  86. const deferBlockFixtures = [];
  87. if (this.block.lContainer.length >= _CONTAINER_HEADER_OFFSET) {
  88. const lView = this.block.lContainer[_CONTAINER_HEADER_OFFSET];
  89. _getDeferBlocks(lView, deferBlocks);
  90. for (const block of deferBlocks) {
  91. deferBlockFixtures.push(new DeferBlockFixture(block, this.componentFixture));
  92. }
  93. }
  94. return Promise.resolve(deferBlockFixtures);
  95. }
  96. }
  97. function hasStateTemplate(state, block) {
  98. switch (state) {
  99. case _DeferBlockState.Placeholder:
  100. return block.tDetails.placeholderTmplIndex !== null;
  101. case _DeferBlockState.Loading:
  102. return block.tDetails.loadingTmplIndex !== null;
  103. case _DeferBlockState.Error:
  104. return block.tDetails.errorTmplIndex !== null;
  105. case _DeferBlockState.Complete:
  106. return true;
  107. default:
  108. return false;
  109. }
  110. }
  111. function getDeferBlockStateNameFromEnum(state) {
  112. switch (state) {
  113. case _DeferBlockState.Placeholder:
  114. return 'Placeholder';
  115. case _DeferBlockState.Loading:
  116. return 'Loading';
  117. case _DeferBlockState.Error:
  118. return 'Error';
  119. default:
  120. return 'Main';
  121. }
  122. }
  123. /** Whether test modules should be torn down by default. */
  124. const TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT = true;
  125. /** Whether unknown elements in templates should throw by default. */
  126. const THROW_ON_UNKNOWN_ELEMENTS_DEFAULT = false;
  127. /** Whether unknown properties in templates should throw by default. */
  128. const THROW_ON_UNKNOWN_PROPERTIES_DEFAULT = false;
  129. /** Whether defer blocks should use manual triggering or play through normally. */
  130. const DEFER_BLOCK_DEFAULT_BEHAVIOR = _DeferBlockBehavior.Playthrough;
  131. /**
  132. * An abstract class for inserting the root test component element in a platform independent way.
  133. *
  134. * @publicApi
  135. */
  136. class TestComponentRenderer {
  137. insertRootElement(rootElementId) { }
  138. removeAllRootElements() { }
  139. }
  140. /**
  141. * @publicApi
  142. */
  143. const ComponentFixtureAutoDetect = new InjectionToken('ComponentFixtureAutoDetect');
  144. /**
  145. * @publicApi
  146. */
  147. const ComponentFixtureNoNgZone = new InjectionToken('ComponentFixtureNoNgZone');
  148. const RETHROW_APPLICATION_ERRORS_DEFAULT = true;
  149. class TestBedApplicationErrorHandler {
  150. zone = inject$1(NgZone);
  151. userErrorHandler = inject$1(ErrorHandler);
  152. whenStableRejectFunctions = new Set();
  153. handleError(e) {
  154. try {
  155. this.zone.runOutsideAngular(() => this.userErrorHandler.handleError(e));
  156. }
  157. catch (userError) {
  158. e = userError;
  159. }
  160. // Instead of throwing the error when there are outstanding `fixture.whenStable` promises,
  161. // reject those promises with the error. This allows developers to write
  162. // expectAsync(fix.whenStable()).toBeRejected();
  163. if (this.whenStableRejectFunctions.size > 0) {
  164. for (const fn of this.whenStableRejectFunctions.values()) {
  165. fn(e);
  166. }
  167. this.whenStableRejectFunctions.clear();
  168. }
  169. else {
  170. throw e;
  171. }
  172. }
  173. static ɵfac = function TestBedApplicationErrorHandler_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TestBedApplicationErrorHandler)(); };
  174. static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: TestBedApplicationErrorHandler, factory: TestBedApplicationErrorHandler.ɵfac });
  175. }
  176. (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestBedApplicationErrorHandler, [{
  177. type: Injectable
  178. }], null, null); })();
  179. /**
  180. * Fixture for debugging and testing a component.
  181. *
  182. * @publicApi
  183. */
  184. class ComponentFixture {
  185. componentRef;
  186. /**
  187. * The DebugElement associated with the root element of this component.
  188. */
  189. debugElement;
  190. /**
  191. * The instance of the root component class.
  192. */
  193. componentInstance;
  194. /**
  195. * The native element at the root of the component.
  196. */
  197. nativeElement;
  198. /**
  199. * The ElementRef for the element at the root of the component.
  200. */
  201. elementRef;
  202. /**
  203. * The ChangeDetectorRef for the component
  204. */
  205. changeDetectorRef;
  206. _renderer;
  207. _isDestroyed = false;
  208. /** @internal */
  209. _noZoneOptionIsSet = inject$1(ComponentFixtureNoNgZone, { optional: true });
  210. /** @internal */
  211. _ngZone = this._noZoneOptionIsSet ? new _NoopNgZone() : inject$1(NgZone);
  212. // Inject ApplicationRef to ensure NgZone stableness causes after render hooks to run
  213. // This will likely happen as a result of fixture.detectChanges because it calls ngZone.run
  214. // This is a crazy way of doing things but hey, it's the world we live in.
  215. // The zoneless scheduler should instead do this more imperatively by attaching
  216. // the `ComponentRef` to `ApplicationRef` and calling `appRef.tick` as the `detectChanges`
  217. // behavior.
  218. /** @internal */
  219. _appRef = inject$1(ApplicationRef);
  220. _testAppRef = this._appRef;
  221. pendingTasks = inject$1(_PendingTasksInternal);
  222. appErrorHandler = inject$1(TestBedApplicationErrorHandler);
  223. zonelessEnabled = inject$1(_ZONELESS_ENABLED);
  224. scheduler = inject$1(_ChangeDetectionScheduler);
  225. rootEffectScheduler = inject$1(_EffectScheduler);
  226. microtaskEffectScheduler = inject$1(_MicrotaskEffectScheduler);
  227. autoDetectDefault = this.zonelessEnabled ? true : false;
  228. autoDetect = inject$1(ComponentFixtureAutoDetect, { optional: true }) ?? this.autoDetectDefault;
  229. subscriptions = new Subscription();
  230. // TODO(atscott): Remove this from public API
  231. ngZone = this._noZoneOptionIsSet ? null : this._ngZone;
  232. /** @nodoc */
  233. constructor(componentRef) {
  234. this.componentRef = componentRef;
  235. this.changeDetectorRef = componentRef.changeDetectorRef;
  236. this.elementRef = componentRef.location;
  237. this.debugElement = getDebugNode(this.elementRef.nativeElement);
  238. this.componentInstance = componentRef.instance;
  239. this.nativeElement = this.elementRef.nativeElement;
  240. this.componentRef = componentRef;
  241. if (this.autoDetect) {
  242. this._testAppRef.externalTestViews.add(this.componentRef.hostView);
  243. this.scheduler?.notify(8 /* ɵNotificationSource.ViewAttached */);
  244. this.scheduler?.notify(0 /* ɵNotificationSource.MarkAncestorsForTraversal */);
  245. }
  246. this.componentRef.hostView.onDestroy(() => {
  247. this._testAppRef.externalTestViews.delete(this.componentRef.hostView);
  248. });
  249. // Create subscriptions outside the NgZone so that the callbacks run outside
  250. // of NgZone.
  251. this._ngZone.runOutsideAngular(() => {
  252. this.subscriptions.add(this._ngZone.onError.subscribe({
  253. next: (error) => {
  254. throw error;
  255. },
  256. }));
  257. });
  258. }
  259. /**
  260. * Trigger a change detection cycle for the component.
  261. */
  262. detectChanges(checkNoChanges = true) {
  263. this.microtaskEffectScheduler.flush();
  264. const originalCheckNoChanges = this.componentRef.changeDetectorRef.checkNoChanges;
  265. try {
  266. if (!checkNoChanges) {
  267. this.componentRef.changeDetectorRef.checkNoChanges = () => { };
  268. }
  269. if (this.zonelessEnabled) {
  270. try {
  271. this._testAppRef.externalTestViews.add(this.componentRef.hostView);
  272. this._appRef.tick();
  273. }
  274. finally {
  275. if (!this.autoDetect) {
  276. this._testAppRef.externalTestViews.delete(this.componentRef.hostView);
  277. }
  278. }
  279. }
  280. else {
  281. // Run the change detection inside the NgZone so that any async tasks as part of the change
  282. // detection are captured by the zone and can be waited for in isStable.
  283. this._ngZone.run(() => {
  284. // Flush root effects before `detectChanges()`, to emulate the sequencing of `tick()`.
  285. this.rootEffectScheduler.flush();
  286. this.changeDetectorRef.detectChanges();
  287. this.checkNoChanges();
  288. });
  289. }
  290. }
  291. finally {
  292. this.componentRef.changeDetectorRef.checkNoChanges = originalCheckNoChanges;
  293. }
  294. this.microtaskEffectScheduler.flush();
  295. }
  296. /**
  297. * Do a change detection run to make sure there were no changes.
  298. */
  299. checkNoChanges() {
  300. this.changeDetectorRef.checkNoChanges();
  301. }
  302. /**
  303. * Set whether the fixture should autodetect changes.
  304. *
  305. * Also runs detectChanges once so that any existing change is detected.
  306. *
  307. * @param autoDetect Whether to autodetect changes. By default, `true`.
  308. */
  309. autoDetectChanges(autoDetect = true) {
  310. if (this._noZoneOptionIsSet && !this.zonelessEnabled) {
  311. throw new Error('Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set.');
  312. }
  313. if (autoDetect !== this.autoDetect) {
  314. if (autoDetect) {
  315. this._testAppRef.externalTestViews.add(this.componentRef.hostView);
  316. }
  317. else {
  318. this._testAppRef.externalTestViews.delete(this.componentRef.hostView);
  319. }
  320. }
  321. this.autoDetect = autoDetect;
  322. this.detectChanges();
  323. }
  324. /**
  325. * Return whether the fixture is currently stable or has async tasks that have not been completed
  326. * yet.
  327. */
  328. isStable() {
  329. return !this.pendingTasks.hasPendingTasks.value;
  330. }
  331. /**
  332. * Get a promise that resolves when the fixture is stable.
  333. *
  334. * This can be used to resume testing after events have triggered asynchronous activity or
  335. * asynchronous change detection.
  336. */
  337. whenStable() {
  338. if (this.isStable()) {
  339. return Promise.resolve(false);
  340. }
  341. return new Promise((resolve, reject) => {
  342. this.appErrorHandler.whenStableRejectFunctions.add(reject);
  343. this._appRef.whenStable().then(() => {
  344. this.appErrorHandler.whenStableRejectFunctions.delete(reject);
  345. resolve(true);
  346. });
  347. });
  348. }
  349. /**
  350. * Retrieves all defer block fixtures in the component fixture.
  351. */
  352. getDeferBlocks() {
  353. const deferBlocks = [];
  354. const lView = this.componentRef.hostView['_lView'];
  355. _getDeferBlocks(lView, deferBlocks);
  356. const deferBlockFixtures = [];
  357. for (const block of deferBlocks) {
  358. deferBlockFixtures.push(new DeferBlockFixture(block, this));
  359. }
  360. return Promise.resolve(deferBlockFixtures);
  361. }
  362. _getRenderer() {
  363. if (this._renderer === undefined) {
  364. this._renderer = this.componentRef.injector.get(RendererFactory2, null);
  365. }
  366. return this._renderer;
  367. }
  368. /**
  369. * Get a promise that resolves when the ui state is stable following animations.
  370. */
  371. whenRenderingDone() {
  372. const renderer = this._getRenderer();
  373. if (renderer && renderer.whenRenderingDone) {
  374. return renderer.whenRenderingDone();
  375. }
  376. return this.whenStable();
  377. }
  378. /**
  379. * Trigger component destruction.
  380. */
  381. destroy() {
  382. this.subscriptions.unsubscribe();
  383. this._testAppRef.externalTestViews.delete(this.componentRef.hostView);
  384. if (!this._isDestroyed) {
  385. this.componentRef.destroy();
  386. this._isDestroyed = true;
  387. }
  388. }
  389. }
  390. const _Zone = typeof Zone !== 'undefined' ? Zone : null;
  391. const fakeAsyncTestModule = _Zone && _Zone[_Zone.__symbol__('fakeAsyncTest')];
  392. const fakeAsyncTestModuleNotLoadedErrorMessage = `zone-testing.js is needed for the fakeAsync() test helper but could not be found.
  393. Please make sure that your environment includes zone.js/testing`;
  394. /**
  395. * Clears out the shared fake async zone for a test.
  396. * To be called in a global `beforeEach`.
  397. *
  398. * @publicApi
  399. */
  400. function resetFakeAsyncZone() {
  401. if (fakeAsyncTestModule) {
  402. return fakeAsyncTestModule.resetFakeAsyncZone();
  403. }
  404. throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
  405. }
  406. function resetFakeAsyncZoneIfExists() {
  407. if (fakeAsyncTestModule) {
  408. fakeAsyncTestModule.resetFakeAsyncZone();
  409. }
  410. }
  411. /**
  412. * Wraps a function to be executed in the `fakeAsync` zone:
  413. * - Microtasks are manually executed by calling `flushMicrotasks()`.
  414. * - Timers are synchronous; `tick()` simulates the asynchronous passage of time.
  415. *
  416. * Can be used to wrap `inject()` calls.
  417. *
  418. * @param fn The function that you want to wrap in the `fakeAsync` zone.
  419. * @param options
  420. * - flush: When true, will drain the macrotask queue after the test function completes.
  421. * When false, will throw an exception at the end of the function if there are pending timers.
  422. *
  423. * @usageNotes
  424. * ### Example
  425. *
  426. * {@example core/testing/ts/fake_async.ts region='basic'}
  427. *
  428. *
  429. * @returns The function wrapped to be executed in the `fakeAsync` zone.
  430. * Any arguments passed when calling this returned function will be passed through to the `fn`
  431. * function in the parameters when it is called.
  432. *
  433. * @publicApi
  434. */
  435. function fakeAsync(fn, options) {
  436. if (fakeAsyncTestModule) {
  437. return fakeAsyncTestModule.fakeAsync(fn, options);
  438. }
  439. throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
  440. }
  441. /**
  442. * Simulates the asynchronous passage of time for the timers in the `fakeAsync` zone.
  443. *
  444. * The microtasks queue is drained at the very start of this function and after any timer callback
  445. * has been executed.
  446. *
  447. * @param millis The number of milliseconds to advance the virtual timer.
  448. * @param tickOptions The options to pass to the `tick()` function.
  449. *
  450. * @usageNotes
  451. *
  452. * The `tick()` option is a flag called `processNewMacroTasksSynchronously`,
  453. * which determines whether or not to invoke new macroTasks.
  454. *
  455. * If you provide a `tickOptions` object, but do not specify a
  456. * `processNewMacroTasksSynchronously` property (`tick(100, {})`),
  457. * then `processNewMacroTasksSynchronously` defaults to true.
  458. *
  459. * If you omit the `tickOptions` parameter (`tick(100))`), then
  460. * `tickOptions` defaults to `{processNewMacroTasksSynchronously: true}`.
  461. *
  462. * ### Example
  463. *
  464. * {@example core/testing/ts/fake_async.ts region='basic'}
  465. *
  466. * The following example includes a nested timeout (new macroTask), and
  467. * the `tickOptions` parameter is allowed to default. In this case,
  468. * `processNewMacroTasksSynchronously` defaults to true, and the nested
  469. * function is executed on each tick.
  470. *
  471. * ```ts
  472. * it ('test with nested setTimeout', fakeAsync(() => {
  473. * let nestedTimeoutInvoked = false;
  474. * function funcWithNestedTimeout() {
  475. * setTimeout(() => {
  476. * nestedTimeoutInvoked = true;
  477. * });
  478. * };
  479. * setTimeout(funcWithNestedTimeout);
  480. * tick();
  481. * expect(nestedTimeoutInvoked).toBe(true);
  482. * }));
  483. * ```
  484. *
  485. * In the following case, `processNewMacroTasksSynchronously` is explicitly
  486. * set to false, so the nested timeout function is not invoked.
  487. *
  488. * ```ts
  489. * it ('test with nested setTimeout', fakeAsync(() => {
  490. * let nestedTimeoutInvoked = false;
  491. * function funcWithNestedTimeout() {
  492. * setTimeout(() => {
  493. * nestedTimeoutInvoked = true;
  494. * });
  495. * };
  496. * setTimeout(funcWithNestedTimeout);
  497. * tick(0, {processNewMacroTasksSynchronously: false});
  498. * expect(nestedTimeoutInvoked).toBe(false);
  499. * }));
  500. * ```
  501. *
  502. *
  503. * @publicApi
  504. */
  505. function tick(millis = 0, tickOptions = {
  506. processNewMacroTasksSynchronously: true,
  507. }) {
  508. if (fakeAsyncTestModule) {
  509. return fakeAsyncTestModule.tick(millis, tickOptions);
  510. }
  511. throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
  512. }
  513. /**
  514. * Flushes any pending microtasks and simulates the asynchronous passage of time for the timers in
  515. * the `fakeAsync` zone by
  516. * draining the macrotask queue until it is empty.
  517. *
  518. * @param maxTurns The maximum number of times the scheduler attempts to clear its queue before
  519. * throwing an error.
  520. * @returns The simulated time elapsed, in milliseconds.
  521. *
  522. * @publicApi
  523. */
  524. function flush(maxTurns) {
  525. if (fakeAsyncTestModule) {
  526. return fakeAsyncTestModule.flush(maxTurns);
  527. }
  528. throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
  529. }
  530. /**
  531. * Discard all remaining periodic tasks.
  532. *
  533. * @publicApi
  534. */
  535. function discardPeriodicTasks() {
  536. if (fakeAsyncTestModule) {
  537. return fakeAsyncTestModule.discardPeriodicTasks();
  538. }
  539. throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
  540. }
  541. /**
  542. * Flush any pending microtasks.
  543. *
  544. * @publicApi
  545. */
  546. function flushMicrotasks() {
  547. if (fakeAsyncTestModule) {
  548. return fakeAsyncTestModule.flushMicrotasks();
  549. }
  550. throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
  551. }
  552. let _nextReferenceId = 0;
  553. class MetadataOverrider {
  554. _references = new Map();
  555. /**
  556. * Creates a new instance for the given metadata class
  557. * based on an old instance and overrides.
  558. */
  559. overrideMetadata(metadataClass, oldMetadata, override) {
  560. const props = {};
  561. if (oldMetadata) {
  562. _valueProps(oldMetadata).forEach((prop) => (props[prop] = oldMetadata[prop]));
  563. }
  564. if (override.set) {
  565. if (override.remove || override.add) {
  566. throw new Error(`Cannot set and add/remove ${_stringify(metadataClass)} at the same time!`);
  567. }
  568. setMetadata(props, override.set);
  569. }
  570. if (override.remove) {
  571. removeMetadata(props, override.remove, this._references);
  572. }
  573. if (override.add) {
  574. addMetadata(props, override.add);
  575. }
  576. return new metadataClass(props);
  577. }
  578. }
  579. function removeMetadata(metadata, remove, references) {
  580. const removeObjects = new Set();
  581. for (const prop in remove) {
  582. const removeValue = remove[prop];
  583. if (Array.isArray(removeValue)) {
  584. removeValue.forEach((value) => {
  585. removeObjects.add(_propHashKey(prop, value, references));
  586. });
  587. }
  588. else {
  589. removeObjects.add(_propHashKey(prop, removeValue, references));
  590. }
  591. }
  592. for (const prop in metadata) {
  593. const propValue = metadata[prop];
  594. if (Array.isArray(propValue)) {
  595. metadata[prop] = propValue.filter((value) => !removeObjects.has(_propHashKey(prop, value, references)));
  596. }
  597. else {
  598. if (removeObjects.has(_propHashKey(prop, propValue, references))) {
  599. metadata[prop] = undefined;
  600. }
  601. }
  602. }
  603. }
  604. function addMetadata(metadata, add) {
  605. for (const prop in add) {
  606. const addValue = add[prop];
  607. const propValue = metadata[prop];
  608. if (propValue != null && Array.isArray(propValue)) {
  609. metadata[prop] = propValue.concat(addValue);
  610. }
  611. else {
  612. metadata[prop] = addValue;
  613. }
  614. }
  615. }
  616. function setMetadata(metadata, set) {
  617. for (const prop in set) {
  618. metadata[prop] = set[prop];
  619. }
  620. }
  621. function _propHashKey(propName, propValue, references) {
  622. let nextObjectId = 0;
  623. const objectIds = new Map();
  624. const replacer = (key, value) => {
  625. if (value !== null && typeof value === 'object') {
  626. if (objectIds.has(value)) {
  627. return objectIds.get(value);
  628. }
  629. // Record an id for this object such that any later references use the object's id instead
  630. // of the object itself, in order to break cyclic pointers in objects.
  631. objectIds.set(value, `ɵobj#${nextObjectId++}`);
  632. // The first time an object is seen the object itself is serialized.
  633. return value;
  634. }
  635. else if (typeof value === 'function') {
  636. value = _serializeReference(value, references);
  637. }
  638. return value;
  639. };
  640. return `${propName}:${JSON.stringify(propValue, replacer)}`;
  641. }
  642. function _serializeReference(ref, references) {
  643. let id = references.get(ref);
  644. if (!id) {
  645. id = `${_stringify(ref)}${_nextReferenceId++}`;
  646. references.set(ref, id);
  647. }
  648. return id;
  649. }
  650. function _valueProps(obj) {
  651. const props = [];
  652. // regular public props
  653. Object.keys(obj).forEach((prop) => {
  654. if (!prop.startsWith('_')) {
  655. props.push(prop);
  656. }
  657. });
  658. // getters
  659. let proto = obj;
  660. while ((proto = Object.getPrototypeOf(proto))) {
  661. Object.keys(proto).forEach((protoProp) => {
  662. const desc = Object.getOwnPropertyDescriptor(proto, protoProp);
  663. if (!protoProp.startsWith('_') && desc && 'get' in desc) {
  664. props.push(protoProp);
  665. }
  666. });
  667. }
  668. return props;
  669. }
  670. const reflection = new _ReflectionCapabilities();
  671. /**
  672. * Allows to override ivy metadata for tests (via the `TestBed`).
  673. */
  674. class OverrideResolver {
  675. overrides = new Map();
  676. resolved = new Map();
  677. addOverride(type, override) {
  678. const overrides = this.overrides.get(type) || [];
  679. overrides.push(override);
  680. this.overrides.set(type, overrides);
  681. this.resolved.delete(type);
  682. }
  683. setOverrides(overrides) {
  684. this.overrides.clear();
  685. overrides.forEach(([type, override]) => {
  686. this.addOverride(type, override);
  687. });
  688. }
  689. getAnnotation(type) {
  690. const annotations = reflection.annotations(type);
  691. // Try to find the nearest known Type annotation and make sure that this annotation is an
  692. // instance of the type we are looking for, so we can use it for resolution. Note: there might
  693. // be multiple known annotations found due to the fact that Components can extend Directives (so
  694. // both Directive and Component annotations would be present), so we always check if the known
  695. // annotation has the right type.
  696. for (let i = annotations.length - 1; i >= 0; i--) {
  697. const annotation = annotations[i];
  698. const isKnownType = annotation instanceof Directive ||
  699. annotation instanceof Component ||
  700. annotation instanceof Pipe ||
  701. annotation instanceof NgModule;
  702. if (isKnownType) {
  703. return annotation instanceof this.type ? annotation : null;
  704. }
  705. }
  706. return null;
  707. }
  708. resolve(type) {
  709. let resolved = this.resolved.get(type) || null;
  710. if (!resolved) {
  711. resolved = this.getAnnotation(type);
  712. if (resolved) {
  713. const overrides = this.overrides.get(type);
  714. if (overrides) {
  715. const overrider = new MetadataOverrider();
  716. overrides.forEach((override) => {
  717. resolved = overrider.overrideMetadata(this.type, resolved, override);
  718. });
  719. }
  720. }
  721. this.resolved.set(type, resolved);
  722. }
  723. return resolved;
  724. }
  725. }
  726. class DirectiveResolver extends OverrideResolver {
  727. get type() {
  728. return Directive;
  729. }
  730. }
  731. class ComponentResolver extends OverrideResolver {
  732. get type() {
  733. return Component;
  734. }
  735. }
  736. class PipeResolver extends OverrideResolver {
  737. get type() {
  738. return Pipe;
  739. }
  740. }
  741. class NgModuleResolver extends OverrideResolver {
  742. get type() {
  743. return NgModule;
  744. }
  745. }
  746. var TestingModuleOverride;
  747. (function (TestingModuleOverride) {
  748. TestingModuleOverride[TestingModuleOverride["DECLARATION"] = 0] = "DECLARATION";
  749. TestingModuleOverride[TestingModuleOverride["OVERRIDE_TEMPLATE"] = 1] = "OVERRIDE_TEMPLATE";
  750. })(TestingModuleOverride || (TestingModuleOverride = {}));
  751. function isTestingModuleOverride(value) {
  752. return (value === TestingModuleOverride.DECLARATION || value === TestingModuleOverride.OVERRIDE_TEMPLATE);
  753. }
  754. function assertNoStandaloneComponents(types, resolver, location) {
  755. types.forEach((type) => {
  756. if (!_getAsyncClassMetadataFn(type)) {
  757. const component = resolver.resolve(type);
  758. if (component && (component.standalone == null || component.standalone)) {
  759. throw new Error(_generateStandaloneInDeclarationsError(type, location));
  760. }
  761. }
  762. });
  763. }
  764. class TestBedCompiler {
  765. platform;
  766. additionalModuleTypes;
  767. originalComponentResolutionQueue = null;
  768. // Testing module configuration
  769. declarations = [];
  770. imports = [];
  771. providers = [];
  772. schemas = [];
  773. // Queues of components/directives/pipes that should be recompiled.
  774. pendingComponents = new Set();
  775. pendingDirectives = new Set();
  776. pendingPipes = new Set();
  777. // Set of components with async metadata, i.e. components with `@defer` blocks
  778. // in their templates.
  779. componentsWithAsyncMetadata = new Set();
  780. // Keep track of all components and directives, so we can patch Providers onto defs later.
  781. seenComponents = new Set();
  782. seenDirectives = new Set();
  783. // Keep track of overridden modules, so that we can collect all affected ones in the module tree.
  784. overriddenModules = new Set();
  785. // Store resolved styles for Components that have template overrides present and `styleUrls`
  786. // defined at the same time.
  787. existingComponentStyles = new Map();
  788. resolvers = initResolvers();
  789. // Map of component type to an NgModule that declares it.
  790. //
  791. // There are a couple special cases:
  792. // - for standalone components, the module scope value is `null`
  793. // - when a component is declared in `TestBed.configureTestingModule()` call or
  794. // a component's template is overridden via `TestBed.overrideTemplateUsingTestingModule()`.
  795. // we use a special value from the `TestingModuleOverride` enum.
  796. componentToModuleScope = new Map();
  797. // Map that keeps initial version of component/directive/pipe defs in case
  798. // we compile a Type again, thus overriding respective static fields. This is
  799. // required to make sure we restore defs to their initial states between test runs.
  800. // Note: one class may have multiple defs (for example: ɵmod and ɵinj in case of an
  801. // NgModule), store all of them in a map.
  802. initialNgDefs = new Map();
  803. // Array that keeps cleanup operations for initial versions of component/directive/pipe/module
  804. // defs in case TestBed makes changes to the originals.
  805. defCleanupOps = [];
  806. _injector = null;
  807. compilerProviders = null;
  808. providerOverrides = [];
  809. rootProviderOverrides = [];
  810. // Overrides for injectables with `{providedIn: SomeModule}` need to be tracked and added to that
  811. // module's provider list.
  812. providerOverridesByModule = new Map();
  813. providerOverridesByToken = new Map();
  814. scopesWithOverriddenProviders = new Set();
  815. testModuleType;
  816. testModuleRef = null;
  817. deferBlockBehavior = DEFER_BLOCK_DEFAULT_BEHAVIOR;
  818. rethrowApplicationTickErrors = RETHROW_APPLICATION_ERRORS_DEFAULT;
  819. constructor(platform, additionalModuleTypes) {
  820. this.platform = platform;
  821. this.additionalModuleTypes = additionalModuleTypes;
  822. class DynamicTestModule {
  823. }
  824. this.testModuleType = DynamicTestModule;
  825. }
  826. setCompilerProviders(providers) {
  827. this.compilerProviders = providers;
  828. this._injector = null;
  829. }
  830. configureTestingModule(moduleDef) {
  831. // Enqueue any compilation tasks for the directly declared component.
  832. if (moduleDef.declarations !== undefined) {
  833. // Verify that there are no standalone components
  834. assertNoStandaloneComponents(moduleDef.declarations, this.resolvers.component, '"TestBed.configureTestingModule" call');
  835. this.queueTypeArray(moduleDef.declarations, TestingModuleOverride.DECLARATION);
  836. this.declarations.push(...moduleDef.declarations);
  837. }
  838. // Enqueue any compilation tasks for imported modules.
  839. if (moduleDef.imports !== undefined) {
  840. this.queueTypesFromModulesArray(moduleDef.imports);
  841. this.imports.push(...moduleDef.imports);
  842. }
  843. if (moduleDef.providers !== undefined) {
  844. this.providers.push(...moduleDef.providers);
  845. }
  846. if (moduleDef.schemas !== undefined) {
  847. this.schemas.push(...moduleDef.schemas);
  848. }
  849. this.deferBlockBehavior = moduleDef.deferBlockBehavior ?? DEFER_BLOCK_DEFAULT_BEHAVIOR;
  850. this.rethrowApplicationTickErrors =
  851. moduleDef.rethrowApplicationErrors ?? RETHROW_APPLICATION_ERRORS_DEFAULT;
  852. }
  853. overrideModule(ngModule, override) {
  854. if (_USE_RUNTIME_DEPS_TRACKER_FOR_JIT) {
  855. _depsTracker.clearScopeCacheFor(ngModule);
  856. }
  857. this.overriddenModules.add(ngModule);
  858. // Compile the module right away.
  859. this.resolvers.module.addOverride(ngModule, override);
  860. const metadata = this.resolvers.module.resolve(ngModule);
  861. if (metadata === null) {
  862. throw invalidTypeError(ngModule.name, 'NgModule');
  863. }
  864. this.recompileNgModule(ngModule, metadata);
  865. // At this point, the module has a valid module def (ɵmod), but the override may have introduced
  866. // new declarations or imported modules. Ingest any possible new types and add them to the
  867. // current queue.
  868. this.queueTypesFromModulesArray([ngModule]);
  869. }
  870. overrideComponent(component, override) {
  871. this.verifyNoStandaloneFlagOverrides(component, override);
  872. this.resolvers.component.addOverride(component, override);
  873. this.pendingComponents.add(component);
  874. // If this is a component with async metadata (i.e. a component with a `@defer` block
  875. // in a template) - store it for future processing.
  876. this.maybeRegisterComponentWithAsyncMetadata(component);
  877. }
  878. overrideDirective(directive, override) {
  879. this.verifyNoStandaloneFlagOverrides(directive, override);
  880. this.resolvers.directive.addOverride(directive, override);
  881. this.pendingDirectives.add(directive);
  882. }
  883. overridePipe(pipe, override) {
  884. this.verifyNoStandaloneFlagOverrides(pipe, override);
  885. this.resolvers.pipe.addOverride(pipe, override);
  886. this.pendingPipes.add(pipe);
  887. }
  888. verifyNoStandaloneFlagOverrides(type, override) {
  889. if (override.add?.hasOwnProperty('standalone') ||
  890. override.set?.hasOwnProperty('standalone') ||
  891. override.remove?.hasOwnProperty('standalone')) {
  892. throw new Error(`An override for the ${type.name} class has the \`standalone\` flag. ` +
  893. `Changing the \`standalone\` flag via TestBed overrides is not supported.`);
  894. }
  895. }
  896. overrideProvider(token, provider) {
  897. let providerDef;
  898. if (provider.useFactory !== undefined) {
  899. providerDef = {
  900. provide: token,
  901. useFactory: provider.useFactory,
  902. deps: provider.deps || [],
  903. multi: provider.multi,
  904. };
  905. }
  906. else if (provider.useValue !== undefined) {
  907. providerDef = { provide: token, useValue: provider.useValue, multi: provider.multi };
  908. }
  909. else {
  910. providerDef = { provide: token };
  911. }
  912. const injectableDef = typeof token !== 'string' ? _getInjectableDef(token) : null;
  913. const providedIn = injectableDef === null ? null : resolveForwardRef(injectableDef.providedIn);
  914. const overridesBucket = providedIn === 'root' ? this.rootProviderOverrides : this.providerOverrides;
  915. overridesBucket.push(providerDef);
  916. // Keep overrides grouped by token as well for fast lookups using token
  917. this.providerOverridesByToken.set(token, providerDef);
  918. if (injectableDef !== null && providedIn !== null && typeof providedIn !== 'string') {
  919. const existingOverrides = this.providerOverridesByModule.get(providedIn);
  920. if (existingOverrides !== undefined) {
  921. existingOverrides.push(providerDef);
  922. }
  923. else {
  924. this.providerOverridesByModule.set(providedIn, [providerDef]);
  925. }
  926. }
  927. }
  928. overrideTemplateUsingTestingModule(type, template) {
  929. const def = type[_NG_COMP_DEF];
  930. const hasStyleUrls = () => {
  931. const metadata = this.resolvers.component.resolve(type);
  932. return !!metadata.styleUrl || !!metadata.styleUrls?.length;
  933. };
  934. const overrideStyleUrls = !!def && !_isComponentDefPendingResolution(type) && hasStyleUrls();
  935. // In Ivy, compiling a component does not require knowing the module providing the
  936. // component's scope, so overrideTemplateUsingTestingModule can be implemented purely via
  937. // overrideComponent. Important: overriding template requires full Component re-compilation,
  938. // which may fail in case styleUrls are also present (thus Component is considered as required
  939. // resolution). In order to avoid this, we preemptively set styleUrls to an empty array,
  940. // preserve current styles available on Component def and restore styles back once compilation
  941. // is complete.
  942. const override = overrideStyleUrls
  943. ? { template, styles: [], styleUrls: [], styleUrl: undefined }
  944. : { template };
  945. this.overrideComponent(type, { set: override });
  946. if (overrideStyleUrls && def.styles && def.styles.length > 0) {
  947. this.existingComponentStyles.set(type, def.styles);
  948. }
  949. // Set the component's scope to be the testing module.
  950. this.componentToModuleScope.set(type, TestingModuleOverride.OVERRIDE_TEMPLATE);
  951. }
  952. async resolvePendingComponentsWithAsyncMetadata() {
  953. if (this.componentsWithAsyncMetadata.size === 0)
  954. return;
  955. const promises = [];
  956. for (const component of this.componentsWithAsyncMetadata) {
  957. const asyncMetadataFn = _getAsyncClassMetadataFn(component);
  958. if (asyncMetadataFn) {
  959. promises.push(asyncMetadataFn());
  960. }
  961. }
  962. this.componentsWithAsyncMetadata.clear();
  963. const resolvedDeps = await Promise.all(promises);
  964. const flatResolvedDeps = resolvedDeps.flat(2);
  965. this.queueTypesFromModulesArray(flatResolvedDeps);
  966. // Loaded standalone components might contain imports of NgModules
  967. // with providers, make sure we override providers there too.
  968. for (const component of flatResolvedDeps) {
  969. this.applyProviderOverridesInScope(component);
  970. }
  971. }
  972. async compileComponents() {
  973. this.clearComponentResolutionQueue();
  974. // Wait for all async metadata for components that were
  975. // overridden, we need resolved metadata to perform an override
  976. // and re-compile a component.
  977. await this.resolvePendingComponentsWithAsyncMetadata();
  978. // Verify that there were no standalone components present in the `declarations` field
  979. // during the `TestBed.configureTestingModule` call. We perform this check here in addition
  980. // to the logic in the `configureTestingModule` function, since at this point we have
  981. // all async metadata resolved.
  982. assertNoStandaloneComponents(this.declarations, this.resolvers.component, '"TestBed.configureTestingModule" call');
  983. // Run compilers for all queued types.
  984. let needsAsyncResources = this.compileTypesSync();
  985. // compileComponents() should not be async unless it needs to be.
  986. if (needsAsyncResources) {
  987. let resourceLoader;
  988. let resolver = (url) => {
  989. if (!resourceLoader) {
  990. resourceLoader = this.injector.get(ResourceLoader);
  991. }
  992. return Promise.resolve(resourceLoader.get(url));
  993. };
  994. await _resolveComponentResources(resolver);
  995. }
  996. }
  997. finalize() {
  998. // One last compile
  999. this.compileTypesSync();
  1000. // Create the testing module itself.
  1001. this.compileTestModule();
  1002. this.applyTransitiveScopes();
  1003. this.applyProviderOverrides();
  1004. // Patch previously stored `styles` Component values (taken from ɵcmp), in case these
  1005. // Components have `styleUrls` fields defined and template override was requested.
  1006. this.patchComponentsWithExistingStyles();
  1007. // Clear the componentToModuleScope map, so that future compilations don't reset the scope of
  1008. // every component.
  1009. this.componentToModuleScope.clear();
  1010. const parentInjector = this.platform.injector;
  1011. this.testModuleRef = new _Render3NgModuleRef(this.testModuleType, parentInjector, []);
  1012. // ApplicationInitStatus.runInitializers() is marked @internal to core.
  1013. // Cast it to any before accessing it.
  1014. this.testModuleRef.injector.get(ApplicationInitStatus).runInitializers();
  1015. // Set locale ID after running app initializers, since locale information might be updated while
  1016. // running initializers. This is also consistent with the execution order while bootstrapping an
  1017. // app (see `packages/core/src/application_ref.ts` file).
  1018. const localeId = this.testModuleRef.injector.get(LOCALE_ID, _DEFAULT_LOCALE_ID);
  1019. _setLocaleId(localeId);
  1020. return this.testModuleRef;
  1021. }
  1022. /**
  1023. * @internal
  1024. */
  1025. _compileNgModuleSync(moduleType) {
  1026. this.queueTypesFromModulesArray([moduleType]);
  1027. this.compileTypesSync();
  1028. this.applyProviderOverrides();
  1029. this.applyProviderOverridesInScope(moduleType);
  1030. this.applyTransitiveScopes();
  1031. }
  1032. /**
  1033. * @internal
  1034. */
  1035. async _compileNgModuleAsync(moduleType) {
  1036. this.queueTypesFromModulesArray([moduleType]);
  1037. await this.compileComponents();
  1038. this.applyProviderOverrides();
  1039. this.applyProviderOverridesInScope(moduleType);
  1040. this.applyTransitiveScopes();
  1041. }
  1042. /**
  1043. * @internal
  1044. */
  1045. _getModuleResolver() {
  1046. return this.resolvers.module;
  1047. }
  1048. /**
  1049. * @internal
  1050. */
  1051. _getComponentFactories(moduleType) {
  1052. return maybeUnwrapFn(moduleType.ɵmod.declarations).reduce((factories, declaration) => {
  1053. const componentDef = declaration.ɵcmp;
  1054. componentDef && factories.push(new _Render3ComponentFactory(componentDef, this.testModuleRef));
  1055. return factories;
  1056. }, []);
  1057. }
  1058. compileTypesSync() {
  1059. // Compile all queued components, directives, pipes.
  1060. let needsAsyncResources = false;
  1061. this.pendingComponents.forEach((declaration) => {
  1062. if (_getAsyncClassMetadataFn(declaration)) {
  1063. throw new Error(`Component '${declaration.name}' has unresolved metadata. ` +
  1064. `Please call \`await TestBed.compileComponents()\` before running this test.`);
  1065. }
  1066. needsAsyncResources = needsAsyncResources || _isComponentDefPendingResolution(declaration);
  1067. const metadata = this.resolvers.component.resolve(declaration);
  1068. if (metadata === null) {
  1069. throw invalidTypeError(declaration.name, 'Component');
  1070. }
  1071. this.maybeStoreNgDef(_NG_COMP_DEF, declaration);
  1072. if (_USE_RUNTIME_DEPS_TRACKER_FOR_JIT) {
  1073. _depsTracker.clearScopeCacheFor(declaration);
  1074. }
  1075. _compileComponent(declaration, metadata);
  1076. });
  1077. this.pendingComponents.clear();
  1078. this.pendingDirectives.forEach((declaration) => {
  1079. const metadata = this.resolvers.directive.resolve(declaration);
  1080. if (metadata === null) {
  1081. throw invalidTypeError(declaration.name, 'Directive');
  1082. }
  1083. this.maybeStoreNgDef(_NG_DIR_DEF, declaration);
  1084. _compileDirective(declaration, metadata);
  1085. });
  1086. this.pendingDirectives.clear();
  1087. this.pendingPipes.forEach((declaration) => {
  1088. const metadata = this.resolvers.pipe.resolve(declaration);
  1089. if (metadata === null) {
  1090. throw invalidTypeError(declaration.name, 'Pipe');
  1091. }
  1092. this.maybeStoreNgDef(_NG_PIPE_DEF, declaration);
  1093. _compilePipe(declaration, metadata);
  1094. });
  1095. this.pendingPipes.clear();
  1096. return needsAsyncResources;
  1097. }
  1098. applyTransitiveScopes() {
  1099. if (this.overriddenModules.size > 0) {
  1100. // Module overrides (via `TestBed.overrideModule`) might affect scopes that were previously
  1101. // calculated and stored in `transitiveCompileScopes`. If module overrides are present,
  1102. // collect all affected modules and reset scopes to force their re-calculation.
  1103. const testingModuleDef = this.testModuleType[_NG_MOD_DEF];
  1104. const affectedModules = this.collectModulesAffectedByOverrides(testingModuleDef.imports);
  1105. if (affectedModules.size > 0) {
  1106. affectedModules.forEach((moduleType) => {
  1107. if (!_USE_RUNTIME_DEPS_TRACKER_FOR_JIT) {
  1108. this.storeFieldOfDefOnType(moduleType, _NG_MOD_DEF, 'transitiveCompileScopes');
  1109. moduleType[_NG_MOD_DEF].transitiveCompileScopes = null;
  1110. }
  1111. else {
  1112. _depsTracker.clearScopeCacheFor(moduleType);
  1113. }
  1114. });
  1115. }
  1116. }
  1117. const moduleToScope = new Map();
  1118. const getScopeOfModule = (moduleType) => {
  1119. if (!moduleToScope.has(moduleType)) {
  1120. const isTestingModule = isTestingModuleOverride(moduleType);
  1121. const realType = isTestingModule ? this.testModuleType : moduleType;
  1122. moduleToScope.set(moduleType, _transitiveScopesFor(realType));
  1123. }
  1124. return moduleToScope.get(moduleType);
  1125. };
  1126. this.componentToModuleScope.forEach((moduleType, componentType) => {
  1127. if (moduleType !== null) {
  1128. const moduleScope = getScopeOfModule(moduleType);
  1129. this.storeFieldOfDefOnType(componentType, _NG_COMP_DEF, 'directiveDefs');
  1130. this.storeFieldOfDefOnType(componentType, _NG_COMP_DEF, 'pipeDefs');
  1131. _patchComponentDefWithScope(getComponentDef(componentType), moduleScope);
  1132. }
  1133. // `tView` that is stored on component def contains information about directives and pipes
  1134. // that are in the scope of this component. Patching component scope will cause `tView` to be
  1135. // changed. Store original `tView` before patching scope, so the `tView` (including scope
  1136. // information) is restored back to its previous/original state before running next test.
  1137. // Resetting `tView` is also needed for cases when we apply provider overrides and those
  1138. // providers are defined on component's level, in which case they may end up included into
  1139. // `tView.blueprint`.
  1140. this.storeFieldOfDefOnType(componentType, _NG_COMP_DEF, 'tView');
  1141. });
  1142. this.componentToModuleScope.clear();
  1143. }
  1144. applyProviderOverrides() {
  1145. const maybeApplyOverrides = (field) => (type) => {
  1146. const resolver = field === _NG_COMP_DEF ? this.resolvers.component : this.resolvers.directive;
  1147. const metadata = resolver.resolve(type);
  1148. if (this.hasProviderOverrides(metadata.providers)) {
  1149. this.patchDefWithProviderOverrides(type, field);
  1150. }
  1151. };
  1152. this.seenComponents.forEach(maybeApplyOverrides(_NG_COMP_DEF));
  1153. this.seenDirectives.forEach(maybeApplyOverrides(_NG_DIR_DEF));
  1154. this.seenComponents.clear();
  1155. this.seenDirectives.clear();
  1156. }
  1157. /**
  1158. * Applies provider overrides to a given type (either an NgModule or a standalone component)
  1159. * and all imported NgModules and standalone components recursively.
  1160. */
  1161. applyProviderOverridesInScope(type) {
  1162. const hasScope = isStandaloneComponent(type) || isNgModule(type);
  1163. // The function can be re-entered recursively while inspecting dependencies
  1164. // of an NgModule or a standalone component. Exit early if we come across a
  1165. // type that can not have a scope (directive or pipe) or the type is already
  1166. // processed earlier.
  1167. if (!hasScope || this.scopesWithOverriddenProviders.has(type)) {
  1168. return;
  1169. }
  1170. this.scopesWithOverriddenProviders.add(type);
  1171. // NOTE: the line below triggers JIT compilation of the module injector,
  1172. // which also invokes verification of the NgModule semantics, which produces
  1173. // detailed error messages. The fact that the code relies on this line being
  1174. // present here is suspicious and should be refactored in a way that the line
  1175. // below can be moved (for ex. after an early exit check below).
  1176. const injectorDef = type[_NG_INJ_DEF];
  1177. // No provider overrides, exit early.
  1178. if (this.providerOverridesByToken.size === 0)
  1179. return;
  1180. if (isStandaloneComponent(type)) {
  1181. // Visit all component dependencies and override providers there.
  1182. const def = getComponentDef(type);
  1183. const dependencies = maybeUnwrapFn(def.dependencies ?? []);
  1184. for (const dependency of dependencies) {
  1185. this.applyProviderOverridesInScope(dependency);
  1186. }
  1187. }
  1188. else {
  1189. const providers = [
  1190. ...injectorDef.providers,
  1191. ...(this.providerOverridesByModule.get(type) || []),
  1192. ];
  1193. if (this.hasProviderOverrides(providers)) {
  1194. this.maybeStoreNgDef(_NG_INJ_DEF, type);
  1195. this.storeFieldOfDefOnType(type, _NG_INJ_DEF, 'providers');
  1196. injectorDef.providers = this.getOverriddenProviders(providers);
  1197. }
  1198. // Apply provider overrides to imported modules recursively
  1199. const moduleDef = type[_NG_MOD_DEF];
  1200. const imports = maybeUnwrapFn(moduleDef.imports);
  1201. for (const importedModule of imports) {
  1202. this.applyProviderOverridesInScope(importedModule);
  1203. }
  1204. // Also override the providers on any ModuleWithProviders imports since those don't appear in
  1205. // the moduleDef.
  1206. for (const importedModule of flatten(injectorDef.imports)) {
  1207. if (isModuleWithProviders(importedModule)) {
  1208. this.defCleanupOps.push({
  1209. object: importedModule,
  1210. fieldName: 'providers',
  1211. originalValue: importedModule.providers,
  1212. });
  1213. importedModule.providers = this.getOverriddenProviders(importedModule.providers);
  1214. }
  1215. }
  1216. }
  1217. }
  1218. patchComponentsWithExistingStyles() {
  1219. this.existingComponentStyles.forEach((styles, type) => (type[_NG_COMP_DEF].styles = styles));
  1220. this.existingComponentStyles.clear();
  1221. }
  1222. queueTypeArray(arr, moduleType) {
  1223. for (const value of arr) {
  1224. if (Array.isArray(value)) {
  1225. this.queueTypeArray(value, moduleType);
  1226. }
  1227. else {
  1228. this.queueType(value, moduleType);
  1229. }
  1230. }
  1231. }
  1232. recompileNgModule(ngModule, metadata) {
  1233. // Cache the initial ngModuleDef as it will be overwritten.
  1234. this.maybeStoreNgDef(_NG_MOD_DEF, ngModule);
  1235. this.maybeStoreNgDef(_NG_INJ_DEF, ngModule);
  1236. _compileNgModuleDefs(ngModule, metadata);
  1237. }
  1238. maybeRegisterComponentWithAsyncMetadata(type) {
  1239. const asyncMetadataFn = _getAsyncClassMetadataFn(type);
  1240. if (asyncMetadataFn) {
  1241. this.componentsWithAsyncMetadata.add(type);
  1242. }
  1243. }
  1244. queueType(type, moduleType) {
  1245. // If this is a component with async metadata (i.e. a component with a `@defer` block
  1246. // in a template) - store it for future processing.
  1247. this.maybeRegisterComponentWithAsyncMetadata(type);
  1248. const component = this.resolvers.component.resolve(type);
  1249. if (component) {
  1250. // Check whether a give Type has respective NG def (ɵcmp) and compile if def is
  1251. // missing. That might happen in case a class without any Angular decorators extends another
  1252. // class where Component/Directive/Pipe decorator is defined.
  1253. if (_isComponentDefPendingResolution(type) || !type.hasOwnProperty(_NG_COMP_DEF)) {
  1254. this.pendingComponents.add(type);
  1255. }
  1256. this.seenComponents.add(type);
  1257. // Keep track of the module which declares this component, so later the component's scope
  1258. // can be set correctly. If the component has already been recorded here, then one of several
  1259. // cases is true:
  1260. // * the module containing the component was imported multiple times (common).
  1261. // * the component is declared in multiple modules (which is an error).
  1262. // * the component was in 'declarations' of the testing module, and also in an imported module
  1263. // in which case the module scope will be TestingModuleOverride.DECLARATION.
  1264. // * overrideTemplateUsingTestingModule was called for the component in which case the module
  1265. // scope will be TestingModuleOverride.OVERRIDE_TEMPLATE.
  1266. //
  1267. // If the component was previously in the testing module's 'declarations' (meaning the
  1268. // current value is TestingModuleOverride.DECLARATION), then `moduleType` is the component's
  1269. // real module, which was imported. This pattern is understood to mean that the component
  1270. // should use its original scope, but that the testing module should also contain the
  1271. // component in its scope.
  1272. if (!this.componentToModuleScope.has(type) ||
  1273. this.componentToModuleScope.get(type) === TestingModuleOverride.DECLARATION) {
  1274. this.componentToModuleScope.set(type, moduleType);
  1275. }
  1276. return;
  1277. }
  1278. const directive = this.resolvers.directive.resolve(type);
  1279. if (directive) {
  1280. if (!type.hasOwnProperty(_NG_DIR_DEF)) {
  1281. this.pendingDirectives.add(type);
  1282. }
  1283. this.seenDirectives.add(type);
  1284. return;
  1285. }
  1286. const pipe = this.resolvers.pipe.resolve(type);
  1287. if (pipe && !type.hasOwnProperty(_NG_PIPE_DEF)) {
  1288. this.pendingPipes.add(type);
  1289. return;
  1290. }
  1291. }
  1292. queueTypesFromModulesArray(arr) {
  1293. // Because we may encounter the same NgModule or a standalone Component while processing
  1294. // the dependencies of an NgModule or a standalone Component, we cache them in this set so we
  1295. // can skip ones that have already been seen encountered. In some test setups, this caching
  1296. // resulted in 10X runtime improvement.
  1297. const processedDefs = new Set();
  1298. const queueTypesFromModulesArrayRecur = (arr) => {
  1299. for (const value of arr) {
  1300. if (Array.isArray(value)) {
  1301. queueTypesFromModulesArrayRecur(value);
  1302. }
  1303. else if (hasNgModuleDef(value)) {
  1304. const def = value.ɵmod;
  1305. if (processedDefs.has(def)) {
  1306. continue;
  1307. }
  1308. processedDefs.add(def);
  1309. // Look through declarations, imports, and exports, and queue
  1310. // everything found there.
  1311. this.queueTypeArray(maybeUnwrapFn(def.declarations), value);
  1312. queueTypesFromModulesArrayRecur(maybeUnwrapFn(def.imports));
  1313. queueTypesFromModulesArrayRecur(maybeUnwrapFn(def.exports));
  1314. }
  1315. else if (isModuleWithProviders(value)) {
  1316. queueTypesFromModulesArrayRecur([value.ngModule]);
  1317. }
  1318. else if (isStandaloneComponent(value)) {
  1319. this.queueType(value, null);
  1320. const def = getComponentDef(value);
  1321. if (processedDefs.has(def)) {
  1322. continue;
  1323. }
  1324. processedDefs.add(def);
  1325. const dependencies = maybeUnwrapFn(def.dependencies ?? []);
  1326. dependencies.forEach((dependency) => {
  1327. // Note: in AOT, the `dependencies` might also contain regular
  1328. // (NgModule-based) Component, Directive and Pipes, so we handle
  1329. // them separately and proceed with recursive process for standalone
  1330. // Components and NgModules only.
  1331. if (isStandaloneComponent(dependency) || hasNgModuleDef(dependency)) {
  1332. queueTypesFromModulesArrayRecur([dependency]);
  1333. }
  1334. else {
  1335. this.queueType(dependency, null);
  1336. }
  1337. });
  1338. }
  1339. }
  1340. };
  1341. queueTypesFromModulesArrayRecur(arr);
  1342. }
  1343. // When module overrides (via `TestBed.overrideModule`) are present, it might affect all modules
  1344. // that import (even transitively) an overridden one. For all affected modules we need to
  1345. // recalculate their scopes for a given test run and restore original scopes at the end. The goal
  1346. // of this function is to collect all affected modules in a set for further processing. Example:
  1347. // if we have the following module hierarchy: A -> B -> C (where `->` means `imports`) and module
  1348. // `C` is overridden, we consider `A` and `B` as affected, since their scopes might become
  1349. // invalidated with the override.
  1350. collectModulesAffectedByOverrides(arr) {
  1351. const seenModules = new Set();
  1352. const affectedModules = new Set();
  1353. const calcAffectedModulesRecur = (arr, path) => {
  1354. for (const value of arr) {
  1355. if (Array.isArray(value)) {
  1356. // If the value is an array, just flatten it (by invoking this function recursively),
  1357. // keeping "path" the same.
  1358. calcAffectedModulesRecur(value, path);
  1359. }
  1360. else if (hasNgModuleDef(value)) {
  1361. if (seenModules.has(value)) {
  1362. // If we've seen this module before and it's included into "affected modules" list, mark
  1363. // the whole path that leads to that module as affected, but do not descend into its
  1364. // imports, since we already examined them before.
  1365. if (affectedModules.has(value)) {
  1366. path.forEach((item) => affectedModules.add(item));
  1367. }
  1368. continue;
  1369. }
  1370. seenModules.add(value);
  1371. if (this.overriddenModules.has(value)) {
  1372. path.forEach((item) => affectedModules.add(item));
  1373. }
  1374. // Examine module imports recursively to look for overridden modules.
  1375. const moduleDef = value[_NG_MOD_DEF];
  1376. calcAffectedModulesRecur(maybeUnwrapFn(moduleDef.imports), path.concat(value));
  1377. }
  1378. }
  1379. };
  1380. calcAffectedModulesRecur(arr, []);
  1381. return affectedModules;
  1382. }
  1383. /**
  1384. * Preserve an original def (such as ɵmod, ɵinj, etc) before applying an override.
  1385. * Note: one class may have multiple defs (for example: ɵmod and ɵinj in case of
  1386. * an NgModule). If there is a def in a set already, don't override it, since
  1387. * an original one should be restored at the end of a test.
  1388. */
  1389. maybeStoreNgDef(prop, type) {
  1390. if (!this.initialNgDefs.has(type)) {
  1391. this.initialNgDefs.set(type, new Map());
  1392. }
  1393. const currentDefs = this.initialNgDefs.get(type);
  1394. if (!currentDefs.has(prop)) {
  1395. const currentDef = Object.getOwnPropertyDescriptor(type, prop);
  1396. currentDefs.set(prop, currentDef);
  1397. }
  1398. }
  1399. storeFieldOfDefOnType(type, defField, fieldName) {
  1400. const def = type[defField];
  1401. const originalValue = def[fieldName];
  1402. this.defCleanupOps.push({ object: def, fieldName, originalValue });
  1403. }
  1404. /**
  1405. * Clears current components resolution queue, but stores the state of the queue, so we can
  1406. * restore it later. Clearing the queue is required before we try to compile components (via
  1407. * `TestBed.compileComponents`), so that component defs are in sync with the resolution queue.
  1408. */
  1409. clearComponentResolutionQueue() {
  1410. if (this.originalComponentResolutionQueue === null) {
  1411. this.originalComponentResolutionQueue = new Map();
  1412. }
  1413. _clearResolutionOfComponentResourcesQueue().forEach((value, key) => this.originalComponentResolutionQueue.set(key, value));
  1414. }
  1415. /*
  1416. * Restores component resolution queue to the previously saved state. This operation is performed
  1417. * as a part of restoring the state after completion of the current set of tests (that might
  1418. * potentially mutate the state).
  1419. */
  1420. restoreComponentResolutionQueue() {
  1421. if (this.originalComponentResolutionQueue !== null) {
  1422. _restoreComponentResolutionQueue(this.originalComponentResolutionQueue);
  1423. this.originalComponentResolutionQueue = null;
  1424. }
  1425. }
  1426. restoreOriginalState() {
  1427. // Process cleanup ops in reverse order so the field's original value is restored correctly (in
  1428. // case there were multiple overrides for the same field).
  1429. forEachRight(this.defCleanupOps, (op) => {
  1430. op.object[op.fieldName] = op.originalValue;
  1431. });
  1432. // Restore initial component/directive/pipe defs
  1433. this.initialNgDefs.forEach((defs, type) => {
  1434. if (_USE_RUNTIME_DEPS_TRACKER_FOR_JIT) {
  1435. _depsTracker.clearScopeCacheFor(type);
  1436. }
  1437. defs.forEach((descriptor, prop) => {
  1438. if (!descriptor) {
  1439. // Delete operations are generally undesirable since they have performance
  1440. // implications on objects they were applied to. In this particular case, situations
  1441. // where this code is invoked should be quite rare to cause any noticeable impact,
  1442. // since it's applied only to some test cases (for example when class with no
  1443. // annotations extends some @Component) when we need to clear 'ɵcmp' field on a given
  1444. // class to restore its original state (before applying overrides and running tests).
  1445. delete type[prop];
  1446. }
  1447. else {
  1448. Object.defineProperty(type, prop, descriptor);
  1449. }
  1450. });
  1451. });
  1452. this.initialNgDefs.clear();
  1453. this.scopesWithOverriddenProviders.clear();
  1454. this.restoreComponentResolutionQueue();
  1455. // Restore the locale ID to the default value, this shouldn't be necessary but we never know
  1456. _setLocaleId(_DEFAULT_LOCALE_ID);
  1457. }
  1458. compileTestModule() {
  1459. class RootScopeModule {
  1460. }
  1461. _compileNgModuleDefs(RootScopeModule, {
  1462. providers: [
  1463. ...this.rootProviderOverrides,
  1464. _internalProvideZoneChangeDetection({}),
  1465. TestBedApplicationErrorHandler,
  1466. { provide: _ChangeDetectionScheduler, useExisting: _ChangeDetectionSchedulerImpl },
  1467. ],
  1468. });
  1469. const providers = [
  1470. { provide: Compiler, useFactory: () => new R3TestCompiler(this) },
  1471. { provide: _DEFER_BLOCK_CONFIG, useValue: { behavior: this.deferBlockBehavior } },
  1472. {
  1473. provide: _INTERNAL_APPLICATION_ERROR_HANDLER,
  1474. useFactory: () => {
  1475. if (this.rethrowApplicationTickErrors) {
  1476. const handler = inject$1(TestBedApplicationErrorHandler);
  1477. return (e) => {
  1478. handler.handleError(e);
  1479. };
  1480. }
  1481. else {
  1482. const userErrorHandler = inject$1(ErrorHandler);
  1483. const ngZone = inject$1(NgZone);
  1484. return (e) => ngZone.runOutsideAngular(() => userErrorHandler.handleError(e));
  1485. }
  1486. },
  1487. },
  1488. ...this.providers,
  1489. ...this.providerOverrides,
  1490. ];
  1491. const imports = [RootScopeModule, this.additionalModuleTypes, this.imports || []];
  1492. _compileNgModuleDefs(this.testModuleType, {
  1493. declarations: this.declarations,
  1494. imports,
  1495. schemas: this.schemas,
  1496. providers,
  1497. },
  1498. /* allowDuplicateDeclarationsInRoot */ true);
  1499. this.applyProviderOverridesInScope(this.testModuleType);
  1500. }
  1501. get injector() {
  1502. if (this._injector !== null) {
  1503. return this._injector;
  1504. }
  1505. const providers = [];
  1506. const compilerOptions = this.platform.injector.get(COMPILER_OPTIONS, []);
  1507. compilerOptions.forEach((opts) => {
  1508. if (opts.providers) {
  1509. providers.push(opts.providers);
  1510. }
  1511. });
  1512. if (this.compilerProviders !== null) {
  1513. providers.push(...this.compilerProviders);
  1514. }
  1515. this._injector = Injector.create({ providers, parent: this.platform.injector });
  1516. return this._injector;
  1517. }
  1518. // get overrides for a specific provider (if any)
  1519. getSingleProviderOverrides(provider) {
  1520. const token = getProviderToken(provider);
  1521. return this.providerOverridesByToken.get(token) || null;
  1522. }
  1523. getProviderOverrides(providers) {
  1524. if (!providers || !providers.length || this.providerOverridesByToken.size === 0)
  1525. return [];
  1526. // There are two flattening operations here. The inner flattenProviders() operates on the
  1527. // metadata's providers and applies a mapping function which retrieves overrides for each
  1528. // incoming provider. The outer flatten() then flattens the produced overrides array. If this is
  1529. // not done, the array can contain other empty arrays (e.g. `[[], []]`) which leak into the
  1530. // providers array and contaminate any error messages that might be generated.
  1531. return flatten(flattenProviders(providers, (provider) => this.getSingleProviderOverrides(provider) || []));
  1532. }
  1533. getOverriddenProviders(providers) {
  1534. if (!providers || !providers.length || this.providerOverridesByToken.size === 0)
  1535. return [];
  1536. const flattenedProviders = flattenProviders(providers);
  1537. const overrides = this.getProviderOverrides(flattenedProviders);
  1538. const overriddenProviders = [...flattenedProviders, ...overrides];
  1539. const final = [];
  1540. const seenOverriddenProviders = new Set();
  1541. // We iterate through the list of providers in reverse order to make sure provider overrides
  1542. // take precedence over the values defined in provider list. We also filter out all providers
  1543. // that have overrides, keeping overridden values only. This is needed, since presence of a
  1544. // provider with `ngOnDestroy` hook will cause this hook to be registered and invoked later.
  1545. forEachRight(overriddenProviders, (provider) => {
  1546. const token = getProviderToken(provider);
  1547. if (this.providerOverridesByToken.has(token)) {
  1548. if (!seenOverriddenProviders.has(token)) {
  1549. seenOverriddenProviders.add(token);
  1550. // Treat all overridden providers as `{multi: false}` (even if it's a multi-provider) to
  1551. // make sure that provided override takes highest precedence and is not combined with
  1552. // other instances of the same multi provider.
  1553. final.unshift({ ...provider, multi: false });
  1554. }
  1555. }
  1556. else {
  1557. final.unshift(provider);
  1558. }
  1559. });
  1560. return final;
  1561. }
  1562. hasProviderOverrides(providers) {
  1563. return this.getProviderOverrides(providers).length > 0;
  1564. }
  1565. patchDefWithProviderOverrides(declaration, field) {
  1566. const def = declaration[field];
  1567. if (def && def.providersResolver) {
  1568. this.maybeStoreNgDef(field, declaration);
  1569. const resolver = def.providersResolver;
  1570. const processProvidersFn = (providers) => this.getOverriddenProviders(providers);
  1571. this.storeFieldOfDefOnType(declaration, field, 'providersResolver');
  1572. def.providersResolver = (ngDef) => resolver(ngDef, processProvidersFn);
  1573. }
  1574. }
  1575. }
  1576. function initResolvers() {
  1577. return {
  1578. module: new NgModuleResolver(),
  1579. component: new ComponentResolver(),
  1580. directive: new DirectiveResolver(),
  1581. pipe: new PipeResolver(),
  1582. };
  1583. }
  1584. function isStandaloneComponent(value) {
  1585. const def = getComponentDef(value);
  1586. return !!def?.standalone;
  1587. }
  1588. function getComponentDef(value) {
  1589. return value.ɵcmp ?? null;
  1590. }
  1591. function hasNgModuleDef(value) {
  1592. return value.hasOwnProperty('ɵmod');
  1593. }
  1594. function isNgModule(value) {
  1595. return hasNgModuleDef(value);
  1596. }
  1597. function maybeUnwrapFn(maybeFn) {
  1598. return maybeFn instanceof Function ? maybeFn() : maybeFn;
  1599. }
  1600. function flatten(values) {
  1601. const out = [];
  1602. values.forEach((value) => {
  1603. if (Array.isArray(value)) {
  1604. out.push(...flatten(value));
  1605. }
  1606. else {
  1607. out.push(value);
  1608. }
  1609. });
  1610. return out;
  1611. }
  1612. function identityFn(value) {
  1613. return value;
  1614. }
  1615. function flattenProviders(providers, mapFn = identityFn) {
  1616. const out = [];
  1617. for (let provider of providers) {
  1618. if (_isEnvironmentProviders(provider)) {
  1619. provider = provider.ɵproviders;
  1620. }
  1621. if (Array.isArray(provider)) {
  1622. out.push(...flattenProviders(provider, mapFn));
  1623. }
  1624. else {
  1625. out.push(mapFn(provider));
  1626. }
  1627. }
  1628. return out;
  1629. }
  1630. function getProviderField(provider, field) {
  1631. return provider && typeof provider === 'object' && provider[field];
  1632. }
  1633. function getProviderToken(provider) {
  1634. return getProviderField(provider, 'provide') || provider;
  1635. }
  1636. function isModuleWithProviders(value) {
  1637. return value.hasOwnProperty('ngModule');
  1638. }
  1639. function forEachRight(values, fn) {
  1640. for (let idx = values.length - 1; idx >= 0; idx--) {
  1641. fn(values[idx], idx);
  1642. }
  1643. }
  1644. function invalidTypeError(name, expectedType) {
  1645. return new Error(`${name} class doesn't have @${expectedType} decorator or is missing metadata.`);
  1646. }
  1647. class R3TestCompiler {
  1648. testBed;
  1649. constructor(testBed) {
  1650. this.testBed = testBed;
  1651. }
  1652. compileModuleSync(moduleType) {
  1653. this.testBed._compileNgModuleSync(moduleType);
  1654. return new _NgModuleFactory(moduleType);
  1655. }
  1656. async compileModuleAsync(moduleType) {
  1657. await this.testBed._compileNgModuleAsync(moduleType);
  1658. return new _NgModuleFactory(moduleType);
  1659. }
  1660. compileModuleAndAllComponentsSync(moduleType) {
  1661. const ngModuleFactory = this.compileModuleSync(moduleType);
  1662. const componentFactories = this.testBed._getComponentFactories(moduleType);
  1663. return new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
  1664. }
  1665. async compileModuleAndAllComponentsAsync(moduleType) {
  1666. const ngModuleFactory = await this.compileModuleAsync(moduleType);
  1667. const componentFactories = this.testBed._getComponentFactories(moduleType);
  1668. return new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
  1669. }
  1670. clearCache() { }
  1671. clearCacheFor(type) { }
  1672. getModuleId(moduleType) {
  1673. const meta = this.testBed._getModuleResolver().resolve(moduleType);
  1674. return (meta && meta.id) || undefined;
  1675. }
  1676. }
  1677. // The formatter and CI disagree on how this import statement should be formatted. Both try to keep
  1678. // it on one line, too, which has gotten very hard to read & manage. So disable the formatter for
  1679. // this statement only.
  1680. let _nextRootElementId = 0;
  1681. /**
  1682. * Returns a singleton of the `TestBed` class.
  1683. *
  1684. * @publicApi
  1685. */
  1686. function getTestBed() {
  1687. return TestBedImpl.INSTANCE;
  1688. }
  1689. /**
  1690. * @description
  1691. * Configures and initializes environment for unit testing and provides methods for
  1692. * creating components and services in unit tests.
  1693. *
  1694. * TestBed is the primary api for writing unit tests for Angular applications and libraries.
  1695. */
  1696. class TestBedImpl {
  1697. static _INSTANCE = null;
  1698. static get INSTANCE() {
  1699. return (TestBedImpl._INSTANCE = TestBedImpl._INSTANCE || new TestBedImpl());
  1700. }
  1701. /**
  1702. * Teardown options that have been configured at the environment level.
  1703. * Used as a fallback if no instance-level options have been provided.
  1704. */
  1705. static _environmentTeardownOptions;
  1706. /**
  1707. * "Error on unknown elements" option that has been configured at the environment level.
  1708. * Used as a fallback if no instance-level option has been provided.
  1709. */
  1710. static _environmentErrorOnUnknownElementsOption;
  1711. /**
  1712. * "Error on unknown properties" option that has been configured at the environment level.
  1713. * Used as a fallback if no instance-level option has been provided.
  1714. */
  1715. static _environmentErrorOnUnknownPropertiesOption;
  1716. /**
  1717. * Teardown options that have been configured at the `TestBed` instance level.
  1718. * These options take precedence over the environment-level ones.
  1719. */
  1720. _instanceTeardownOptions;
  1721. /**
  1722. * Defer block behavior option that specifies whether defer blocks will be triggered manually
  1723. * or set to play through.
  1724. */
  1725. _instanceDeferBlockBehavior = DEFER_BLOCK_DEFAULT_BEHAVIOR;
  1726. /**
  1727. * "Error on unknown elements" option that has been configured at the `TestBed` instance level.
  1728. * This option takes precedence over the environment-level one.
  1729. */
  1730. _instanceErrorOnUnknownElementsOption;
  1731. /**
  1732. * "Error on unknown properties" option that has been configured at the `TestBed` instance level.
  1733. * This option takes precedence over the environment-level one.
  1734. */
  1735. _instanceErrorOnUnknownPropertiesOption;
  1736. /**
  1737. * Stores the previous "Error on unknown elements" option value,
  1738. * allowing to restore it in the reset testing module logic.
  1739. */
  1740. _previousErrorOnUnknownElementsOption;
  1741. /**
  1742. * Stores the previous "Error on unknown properties" option value,
  1743. * allowing to restore it in the reset testing module logic.
  1744. */
  1745. _previousErrorOnUnknownPropertiesOption;
  1746. /**
  1747. * Initialize the environment for testing with a compiler factory, a PlatformRef, and an
  1748. * angular module. These are common to every test in the suite.
  1749. *
  1750. * This may only be called once, to set up the common providers for the current test
  1751. * suite on the current platform. If you absolutely need to change the providers,
  1752. * first use `resetTestEnvironment`.
  1753. *
  1754. * Test modules and platforms for individual platforms are available from
  1755. * '@angular/<platform_name>/testing'.
  1756. *
  1757. * @publicApi
  1758. */
  1759. static initTestEnvironment(ngModule, platform, options) {
  1760. const testBed = TestBedImpl.INSTANCE;
  1761. testBed.initTestEnvironment(ngModule, platform, options);
  1762. return testBed;
  1763. }
  1764. /**
  1765. * Reset the providers for the test injector.
  1766. *
  1767. * @publicApi
  1768. */
  1769. static resetTestEnvironment() {
  1770. TestBedImpl.INSTANCE.resetTestEnvironment();
  1771. }
  1772. static configureCompiler(config) {
  1773. return TestBedImpl.INSTANCE.configureCompiler(config);
  1774. }
  1775. /**
  1776. * Allows overriding default providers, directives, pipes, modules of the test injector,
  1777. * which are defined in test_injector.js
  1778. */
  1779. static configureTestingModule(moduleDef) {
  1780. return TestBedImpl.INSTANCE.configureTestingModule(moduleDef);
  1781. }
  1782. /**
  1783. * Compile components with a `templateUrl` for the test's NgModule.
  1784. * It is necessary to call this function
  1785. * as fetching urls is asynchronous.
  1786. */
  1787. static compileComponents() {
  1788. return TestBedImpl.INSTANCE.compileComponents();
  1789. }
  1790. static overrideModule(ngModule, override) {
  1791. return TestBedImpl.INSTANCE.overrideModule(ngModule, override);
  1792. }
  1793. static overrideComponent(component, override) {
  1794. return TestBedImpl.INSTANCE.overrideComponent(component, override);
  1795. }
  1796. static overrideDirective(directive, override) {
  1797. return TestBedImpl.INSTANCE.overrideDirective(directive, override);
  1798. }
  1799. static overridePipe(pipe, override) {
  1800. return TestBedImpl.INSTANCE.overridePipe(pipe, override);
  1801. }
  1802. static overrideTemplate(component, template) {
  1803. return TestBedImpl.INSTANCE.overrideTemplate(component, template);
  1804. }
  1805. /**
  1806. * Overrides the template of the given component, compiling the template
  1807. * in the context of the TestingModule.
  1808. *
  1809. * Note: This works for JIT and AOTed components as well.
  1810. */
  1811. static overrideTemplateUsingTestingModule(component, template) {
  1812. return TestBedImpl.INSTANCE.overrideTemplateUsingTestingModule(component, template);
  1813. }
  1814. static overrideProvider(token, provider) {
  1815. return TestBedImpl.INSTANCE.overrideProvider(token, provider);
  1816. }
  1817. static inject(token, notFoundValue, flags) {
  1818. return TestBedImpl.INSTANCE.inject(token, notFoundValue, _convertToBitFlags(flags));
  1819. }
  1820. /** @deprecated from v9.0.0 use TestBed.inject */
  1821. static get(token, notFoundValue = Injector.THROW_IF_NOT_FOUND, flags = InjectFlags.Default) {
  1822. return TestBedImpl.INSTANCE.inject(token, notFoundValue, flags);
  1823. }
  1824. /**
  1825. * Runs the given function in the `EnvironmentInjector` context of `TestBed`.
  1826. *
  1827. * @see {@link EnvironmentInjector#runInContext}
  1828. */
  1829. static runInInjectionContext(fn) {
  1830. return TestBedImpl.INSTANCE.runInInjectionContext(fn);
  1831. }
  1832. static createComponent(component) {
  1833. return TestBedImpl.INSTANCE.createComponent(component);
  1834. }
  1835. static resetTestingModule() {
  1836. return TestBedImpl.INSTANCE.resetTestingModule();
  1837. }
  1838. static execute(tokens, fn, context) {
  1839. return TestBedImpl.INSTANCE.execute(tokens, fn, context);
  1840. }
  1841. static get platform() {
  1842. return TestBedImpl.INSTANCE.platform;
  1843. }
  1844. static get ngModule() {
  1845. return TestBedImpl.INSTANCE.ngModule;
  1846. }
  1847. static flushEffects() {
  1848. return TestBedImpl.INSTANCE.flushEffects();
  1849. }
  1850. // Properties
  1851. platform = null;
  1852. ngModule = null;
  1853. _compiler = null;
  1854. _testModuleRef = null;
  1855. _activeFixtures = [];
  1856. /**
  1857. * Internal-only flag to indicate whether a module
  1858. * scoping queue has been checked and flushed already.
  1859. * @nodoc
  1860. */
  1861. globalCompilationChecked = false;
  1862. /**
  1863. * Initialize the environment for testing with a compiler factory, a PlatformRef, and an
  1864. * angular module. These are common to every test in the suite.
  1865. *
  1866. * This may only be called once, to set up the common providers for the current test
  1867. * suite on the current platform. If you absolutely need to change the providers,
  1868. * first use `resetTestEnvironment`.
  1869. *
  1870. * Test modules and platforms for individual platforms are available from
  1871. * '@angular/<platform_name>/testing'.
  1872. *
  1873. * @publicApi
  1874. */
  1875. initTestEnvironment(ngModule, platform, options) {
  1876. if (this.platform || this.ngModule) {
  1877. throw new Error('Cannot set base providers because it has already been called');
  1878. }
  1879. TestBedImpl._environmentTeardownOptions = options?.teardown;
  1880. TestBedImpl._environmentErrorOnUnknownElementsOption = options?.errorOnUnknownElements;
  1881. TestBedImpl._environmentErrorOnUnknownPropertiesOption = options?.errorOnUnknownProperties;
  1882. this.platform = platform;
  1883. this.ngModule = ngModule;
  1884. this._compiler = new TestBedCompiler(this.platform, this.ngModule);
  1885. // TestBed does not have an API which can reliably detect the start of a test, and thus could be
  1886. // used to track the state of the NgModule registry and reset it correctly. Instead, when we
  1887. // know we're in a testing scenario, we disable the check for duplicate NgModule registration
  1888. // completely.
  1889. _setAllowDuplicateNgModuleIdsForTest(true);
  1890. }
  1891. /**
  1892. * Reset the providers for the test injector.
  1893. *
  1894. * @publicApi
  1895. */
  1896. resetTestEnvironment() {
  1897. this.resetTestingModule();
  1898. this._compiler = null;
  1899. this.platform = null;
  1900. this.ngModule = null;
  1901. TestBedImpl._environmentTeardownOptions = undefined;
  1902. _setAllowDuplicateNgModuleIdsForTest(false);
  1903. }
  1904. resetTestingModule() {
  1905. this.checkGlobalCompilationFinished();
  1906. _resetCompiledComponents();
  1907. if (this._compiler !== null) {
  1908. this.compiler.restoreOriginalState();
  1909. }
  1910. this._compiler = new TestBedCompiler(this.platform, this.ngModule);
  1911. // Restore the previous value of the "error on unknown elements" option
  1912. _setUnknownElementStrictMode(this._previousErrorOnUnknownElementsOption ?? THROW_ON_UNKNOWN_ELEMENTS_DEFAULT);
  1913. // Restore the previous value of the "error on unknown properties" option
  1914. _setUnknownPropertyStrictMode(this._previousErrorOnUnknownPropertiesOption ?? THROW_ON_UNKNOWN_PROPERTIES_DEFAULT);
  1915. // We have to chain a couple of try/finally blocks, because each step can
  1916. // throw errors and we don't want it to interrupt the next step and we also
  1917. // want an error to be thrown at the end.
  1918. try {
  1919. this.destroyActiveFixtures();
  1920. }
  1921. finally {
  1922. try {
  1923. if (this.shouldTearDownTestingModule()) {
  1924. this.tearDownTestingModule();
  1925. }
  1926. }
  1927. finally {
  1928. this._testModuleRef = null;
  1929. this._instanceTeardownOptions = undefined;
  1930. this._instanceErrorOnUnknownElementsOption = undefined;
  1931. this._instanceErrorOnUnknownPropertiesOption = undefined;
  1932. this._instanceDeferBlockBehavior = DEFER_BLOCK_DEFAULT_BEHAVIOR;
  1933. }
  1934. }
  1935. return this;
  1936. }
  1937. configureCompiler(config) {
  1938. if (config.useJit != null) {
  1939. throw new Error('JIT compiler is not configurable via TestBed APIs.');
  1940. }
  1941. if (config.providers !== undefined) {
  1942. this.compiler.setCompilerProviders(config.providers);
  1943. }
  1944. return this;
  1945. }
  1946. configureTestingModule(moduleDef) {
  1947. this.assertNotInstantiated('TestBed.configureTestingModule', 'configure the test module');
  1948. // Trigger module scoping queue flush before executing other TestBed operations in a test.
  1949. // This is needed for the first test invocation to ensure that globally declared modules have
  1950. // their components scoped properly. See the `checkGlobalCompilationFinished` function
  1951. // description for additional info.
  1952. this.checkGlobalCompilationFinished();
  1953. // Always re-assign the options, even if they're undefined.
  1954. // This ensures that we don't carry them between tests.
  1955. this._instanceTeardownOptions = moduleDef.teardown;
  1956. this._instanceErrorOnUnknownElementsOption = moduleDef.errorOnUnknownElements;
  1957. this._instanceErrorOnUnknownPropertiesOption = moduleDef.errorOnUnknownProperties;
  1958. this._instanceDeferBlockBehavior = moduleDef.deferBlockBehavior ?? DEFER_BLOCK_DEFAULT_BEHAVIOR;
  1959. // Store the current value of the strict mode option,
  1960. // so we can restore it later
  1961. this._previousErrorOnUnknownElementsOption = _getUnknownElementStrictMode();
  1962. _setUnknownElementStrictMode(this.shouldThrowErrorOnUnknownElements());
  1963. this._previousErrorOnUnknownPropertiesOption = _getUnknownPropertyStrictMode();
  1964. _setUnknownPropertyStrictMode(this.shouldThrowErrorOnUnknownProperties());
  1965. this.compiler.configureTestingModule(moduleDef);
  1966. return this;
  1967. }
  1968. compileComponents() {
  1969. return this.compiler.compileComponents();
  1970. }
  1971. inject(token, notFoundValue, flags) {
  1972. if (token === TestBed) {
  1973. return this;
  1974. }
  1975. const UNDEFINED = {};
  1976. const result = this.testModuleRef.injector.get(token, UNDEFINED, _convertToBitFlags(flags));
  1977. return result === UNDEFINED
  1978. ? this.compiler.injector.get(token, notFoundValue, flags)
  1979. : result;
  1980. }
  1981. /** @deprecated from v9.0.0 use TestBed.inject */
  1982. get(token, notFoundValue = Injector.THROW_IF_NOT_FOUND, flags = InjectFlags.Default) {
  1983. return this.inject(token, notFoundValue, flags);
  1984. }
  1985. runInInjectionContext(fn) {
  1986. return runInInjectionContext(this.inject(EnvironmentInjector), fn);
  1987. }
  1988. execute(tokens, fn, context) {
  1989. const params = tokens.map((t) => this.inject(t));
  1990. return fn.apply(context, params);
  1991. }
  1992. overrideModule(ngModule, override) {
  1993. this.assertNotInstantiated('overrideModule', 'override module metadata');
  1994. this.compiler.overrideModule(ngModule, override);
  1995. return this;
  1996. }
  1997. overrideComponent(component, override) {
  1998. this.assertNotInstantiated('overrideComponent', 'override component metadata');
  1999. this.compiler.overrideComponent(component, override);
  2000. return this;
  2001. }
  2002. overrideTemplateUsingTestingModule(component, template) {
  2003. this.assertNotInstantiated('TestBed.overrideTemplateUsingTestingModule', 'Cannot override template when the test module has already been instantiated');
  2004. this.compiler.overrideTemplateUsingTestingModule(component, template);
  2005. return this;
  2006. }
  2007. overrideDirective(directive, override) {
  2008. this.assertNotInstantiated('overrideDirective', 'override directive metadata');
  2009. this.compiler.overrideDirective(directive, override);
  2010. return this;
  2011. }
  2012. overridePipe(pipe, override) {
  2013. this.assertNotInstantiated('overridePipe', 'override pipe metadata');
  2014. this.compiler.overridePipe(pipe, override);
  2015. return this;
  2016. }
  2017. /**
  2018. * Overwrites all providers for the given token with the given provider definition.
  2019. */
  2020. overrideProvider(token, provider) {
  2021. this.assertNotInstantiated('overrideProvider', 'override provider');
  2022. this.compiler.overrideProvider(token, provider);
  2023. return this;
  2024. }
  2025. overrideTemplate(component, template) {
  2026. return this.overrideComponent(component, { set: { template, templateUrl: null } });
  2027. }
  2028. createComponent(type) {
  2029. const testComponentRenderer = this.inject(TestComponentRenderer);
  2030. const rootElId = `root${_nextRootElementId++}`;
  2031. testComponentRenderer.insertRootElement(rootElId);
  2032. if (_getAsyncClassMetadataFn(type)) {
  2033. throw new Error(`Component '${type.name}' has unresolved metadata. ` +
  2034. `Please call \`await TestBed.compileComponents()\` before running this test.`);
  2035. }
  2036. const componentDef = type.ɵcmp;
  2037. if (!componentDef) {
  2038. throw new Error(`It looks like '${_stringify(type)}' has not been compiled.`);
  2039. }
  2040. const componentFactory = new _Render3ComponentFactory(componentDef);
  2041. const initComponent = () => {
  2042. const componentRef = componentFactory.create(Injector.NULL, [], `#${rootElId}`, this.testModuleRef);
  2043. return this.runInInjectionContext(() => new ComponentFixture(componentRef));
  2044. };
  2045. const noNgZone = this.inject(ComponentFixtureNoNgZone, false);
  2046. const ngZone = noNgZone ? null : this.inject(NgZone, null);
  2047. const fixture = ngZone ? ngZone.run(initComponent) : initComponent();
  2048. this._activeFixtures.push(fixture);
  2049. return fixture;
  2050. }
  2051. /**
  2052. * @internal strip this from published d.ts files due to
  2053. * https://github.com/microsoft/TypeScript/issues/36216
  2054. */
  2055. get compiler() {
  2056. if (this._compiler === null) {
  2057. throw new Error(`Need to call TestBed.initTestEnvironment() first`);
  2058. }
  2059. return this._compiler;
  2060. }
  2061. /**
  2062. * @internal strip this from published d.ts files due to
  2063. * https://github.com/microsoft/TypeScript/issues/36216
  2064. */
  2065. get testModuleRef() {
  2066. if (this._testModuleRef === null) {
  2067. this._testModuleRef = this.compiler.finalize();
  2068. }
  2069. return this._testModuleRef;
  2070. }
  2071. assertNotInstantiated(methodName, methodDescription) {
  2072. if (this._testModuleRef !== null) {
  2073. throw new Error(`Cannot ${methodDescription} when the test module has already been instantiated. ` +
  2074. `Make sure you are not using \`inject\` before \`${methodName}\`.`);
  2075. }
  2076. }
  2077. /**
  2078. * Check whether the module scoping queue should be flushed, and flush it if needed.
  2079. *
  2080. * When the TestBed is reset, it clears the JIT module compilation queue, cancelling any
  2081. * in-progress module compilation. This creates a potential hazard - the very first time the
  2082. * TestBed is initialized (or if it's reset without being initialized), there may be pending
  2083. * compilations of modules declared in global scope. These compilations should be finished.
  2084. *
  2085. * To ensure that globally declared modules have their components scoped properly, this function
  2086. * is called whenever TestBed is initialized or reset. The _first_ time that this happens, prior
  2087. * to any other operations, the scoping queue is flushed.
  2088. */
  2089. checkGlobalCompilationFinished() {
  2090. // Checking _testNgModuleRef is null should not be necessary, but is left in as an additional
  2091. // guard that compilations queued in tests (after instantiation) are never flushed accidentally.
  2092. if (!this.globalCompilationChecked && this._testModuleRef === null) {
  2093. _flushModuleScopingQueueAsMuchAsPossible();
  2094. }
  2095. this.globalCompilationChecked = true;
  2096. }
  2097. destroyActiveFixtures() {
  2098. let errorCount = 0;
  2099. this._activeFixtures.forEach((fixture) => {
  2100. try {
  2101. fixture.destroy();
  2102. }
  2103. catch (e) {
  2104. errorCount++;
  2105. console.error('Error during cleanup of component', {
  2106. component: fixture.componentInstance,
  2107. stacktrace: e,
  2108. });
  2109. }
  2110. });
  2111. this._activeFixtures = [];
  2112. if (errorCount > 0 && this.shouldRethrowTeardownErrors()) {
  2113. throw Error(`${errorCount} ${errorCount === 1 ? 'component' : 'components'} ` +
  2114. `threw errors during cleanup`);
  2115. }
  2116. }
  2117. shouldRethrowTeardownErrors() {
  2118. const instanceOptions = this._instanceTeardownOptions;
  2119. const environmentOptions = TestBedImpl._environmentTeardownOptions;
  2120. // If the new teardown behavior hasn't been configured, preserve the old behavior.
  2121. if (!instanceOptions && !environmentOptions) {
  2122. return TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT;
  2123. }
  2124. // Otherwise use the configured behavior or default to rethrowing.
  2125. return (instanceOptions?.rethrowErrors ??
  2126. environmentOptions?.rethrowErrors ??
  2127. this.shouldTearDownTestingModule());
  2128. }
  2129. shouldThrowErrorOnUnknownElements() {
  2130. // Check if a configuration has been provided to throw when an unknown element is found
  2131. return (this._instanceErrorOnUnknownElementsOption ??
  2132. TestBedImpl._environmentErrorOnUnknownElementsOption ??
  2133. THROW_ON_UNKNOWN_ELEMENTS_DEFAULT);
  2134. }
  2135. shouldThrowErrorOnUnknownProperties() {
  2136. // Check if a configuration has been provided to throw when an unknown property is found
  2137. return (this._instanceErrorOnUnknownPropertiesOption ??
  2138. TestBedImpl._environmentErrorOnUnknownPropertiesOption ??
  2139. THROW_ON_UNKNOWN_PROPERTIES_DEFAULT);
  2140. }
  2141. shouldTearDownTestingModule() {
  2142. return (this._instanceTeardownOptions?.destroyAfterEach ??
  2143. TestBedImpl._environmentTeardownOptions?.destroyAfterEach ??
  2144. TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT);
  2145. }
  2146. getDeferBlockBehavior() {
  2147. return this._instanceDeferBlockBehavior;
  2148. }
  2149. tearDownTestingModule() {
  2150. // If the module ref has already been destroyed, we won't be able to get a test renderer.
  2151. if (this._testModuleRef === null) {
  2152. return;
  2153. }
  2154. // Resolve the renderer ahead of time, because we want to remove the root elements as the very
  2155. // last step, but the injector will be destroyed as a part of the module ref destruction.
  2156. const testRenderer = this.inject(TestComponentRenderer);
  2157. try {
  2158. this._testModuleRef.destroy();
  2159. }
  2160. catch (e) {
  2161. if (this.shouldRethrowTeardownErrors()) {
  2162. throw e;
  2163. }
  2164. else {
  2165. console.error('Error during cleanup of a testing module', {
  2166. component: this._testModuleRef.instance,
  2167. stacktrace: e,
  2168. });
  2169. }
  2170. }
  2171. finally {
  2172. testRenderer.removeAllRootElements?.();
  2173. }
  2174. }
  2175. /**
  2176. * Execute any pending effects.
  2177. *
  2178. * @developerPreview
  2179. */
  2180. flushEffects() {
  2181. this.inject(_MicrotaskEffectScheduler).flush();
  2182. this.inject(_EffectScheduler).flush();
  2183. }
  2184. }
  2185. /**
  2186. * @description
  2187. * Configures and initializes environment for unit testing and provides methods for
  2188. * creating components and services in unit tests.
  2189. *
  2190. * `TestBed` is the primary api for writing unit tests for Angular applications and libraries.
  2191. *
  2192. * @publicApi
  2193. */
  2194. const TestBed = TestBedImpl;
  2195. /**
  2196. * Allows injecting dependencies in `beforeEach()` and `it()`. Note: this function
  2197. * (imported from the `@angular/core/testing` package) can **only** be used to inject dependencies
  2198. * in tests. To inject dependencies in your application code, use the [`inject`](api/core/inject)
  2199. * function from the `@angular/core` package instead.
  2200. *
  2201. * Example:
  2202. *
  2203. * ```ts
  2204. * beforeEach(inject([Dependency, AClass], (dep, object) => {
  2205. * // some code that uses `dep` and `object`
  2206. * // ...
  2207. * }));
  2208. *
  2209. * it('...', inject([AClass], (object) => {
  2210. * object.doSomething();
  2211. * expect(...);
  2212. * })
  2213. * ```
  2214. *
  2215. * @publicApi
  2216. */
  2217. function inject(tokens, fn) {
  2218. const testBed = TestBedImpl.INSTANCE;
  2219. // Not using an arrow function to preserve context passed from call site
  2220. return function () {
  2221. return testBed.execute(tokens, fn, this);
  2222. };
  2223. }
  2224. /**
  2225. * @publicApi
  2226. */
  2227. class InjectSetupWrapper {
  2228. _moduleDef;
  2229. constructor(_moduleDef) {
  2230. this._moduleDef = _moduleDef;
  2231. }
  2232. _addModule() {
  2233. const moduleDef = this._moduleDef();
  2234. if (moduleDef) {
  2235. TestBedImpl.configureTestingModule(moduleDef);
  2236. }
  2237. }
  2238. inject(tokens, fn) {
  2239. const self = this;
  2240. // Not using an arrow function to preserve context passed from call site
  2241. return function () {
  2242. self._addModule();
  2243. return inject(tokens, fn).call(this);
  2244. };
  2245. }
  2246. }
  2247. function withModule(moduleDef, fn) {
  2248. if (fn) {
  2249. // Not using an arrow function to preserve context passed from call site
  2250. return function () {
  2251. const testBed = TestBedImpl.INSTANCE;
  2252. if (moduleDef) {
  2253. testBed.configureTestingModule(moduleDef);
  2254. }
  2255. return fn.apply(this);
  2256. };
  2257. }
  2258. return new InjectSetupWrapper(() => moduleDef);
  2259. }
  2260. /**
  2261. * Public Test Library for unit testing Angular applications. Assumes that you are running
  2262. * with Jasmine, Mocha, or a similar framework which exports a beforeEach function and
  2263. * allows tests to be asynchronous by either returning a promise or using a 'done' parameter.
  2264. */
  2265. // Reset the test providers and the fake async zone before each test.
  2266. // We keep a guard because somehow this file can make it into a bundle and be executed
  2267. // beforeEach is only defined when executing the tests
  2268. globalThis.beforeEach?.(getCleanupHook(false));
  2269. // We provide both a `beforeEach` and `afterEach`, because the updated behavior for
  2270. // tearing down the module is supposed to run after the test so that we can associate
  2271. // teardown errors with the correct test.
  2272. // We keep a guard because somehow this file can make it into a bundle and be executed
  2273. // afterEach is only defined when executing the tests
  2274. globalThis.afterEach?.(getCleanupHook(true));
  2275. function getCleanupHook(expectedTeardownValue) {
  2276. return () => {
  2277. const testBed = TestBedImpl.INSTANCE;
  2278. if (testBed.shouldTearDownTestingModule() === expectedTeardownValue) {
  2279. testBed.resetTestingModule();
  2280. resetFakeAsyncZoneIfExists();
  2281. }
  2282. };
  2283. }
  2284. /**
  2285. * This API should be removed. But doing so seems to break `google3` and so it requires a bit of
  2286. * investigation.
  2287. *
  2288. * A work around is to mark it as `@codeGenApi` for now and investigate later.
  2289. *
  2290. * @codeGenApi
  2291. */
  2292. // TODO(iminar): Remove this code in a safe way.
  2293. const __core_private_testing_placeholder__ = '';
  2294. /**
  2295. * Fake implementation of user agent history and navigation behavior. This is a
  2296. * high-fidelity implementation of browser behavior that attempts to emulate
  2297. * things like traversal delay.
  2298. */
  2299. class FakeNavigation {
  2300. window;
  2301. /**
  2302. * The fake implementation of an entries array. Only same-document entries
  2303. * allowed.
  2304. */
  2305. entriesArr = [];
  2306. /**
  2307. * The current active entry index into `entriesArr`.
  2308. */
  2309. currentEntryIndex = 0;
  2310. /**
  2311. * The current navigate event.
  2312. * @internal
  2313. */
  2314. navigateEvent = null;
  2315. /**
  2316. * A Map of pending traversals, so that traversals to the same entry can be
  2317. * re-used.
  2318. */
  2319. traversalQueue = new Map();
  2320. /**
  2321. * A Promise that resolves when the previous traversals have finished. Used to
  2322. * simulate the cross-process communication necessary for traversals.
  2323. */
  2324. nextTraversal = Promise.resolve();
  2325. /**
  2326. * A prospective current active entry index, which includes unresolved
  2327. * traversals. Used by `go` to determine where navigations are intended to go.
  2328. */
  2329. prospectiveEntryIndex = 0;
  2330. /**
  2331. * A test-only option to make traversals synchronous, rather than emulate
  2332. * cross-process communication.
  2333. */
  2334. synchronousTraversals = false;
  2335. /** Whether to allow a call to setInitialEntryForTesting. */
  2336. canSetInitialEntry = true;
  2337. /**
  2338. * `EventTarget` to dispatch events.
  2339. * @internal
  2340. */
  2341. eventTarget;
  2342. /** The next unique id for created entries. Replace recreates this id. */
  2343. nextId = 0;
  2344. /** The next unique key for created entries. Replace inherits this id. */
  2345. nextKey = 0;
  2346. /** Whether this fake is disposed. */
  2347. disposed = false;
  2348. /** Equivalent to `navigation.currentEntry`. */
  2349. get currentEntry() {
  2350. return this.entriesArr[this.currentEntryIndex];
  2351. }
  2352. get canGoBack() {
  2353. return this.currentEntryIndex > 0;
  2354. }
  2355. get canGoForward() {
  2356. return this.currentEntryIndex < this.entriesArr.length - 1;
  2357. }
  2358. constructor(window, startURL) {
  2359. this.window = window;
  2360. this.eventTarget = this.window.document.createElement('div');
  2361. // First entry.
  2362. this.setInitialEntryForTesting(startURL);
  2363. }
  2364. /**
  2365. * Sets the initial entry.
  2366. */
  2367. setInitialEntryForTesting(url, options = { historyState: null }) {
  2368. if (!this.canSetInitialEntry) {
  2369. throw new Error('setInitialEntryForTesting can only be called before any ' + 'navigation has occurred');
  2370. }
  2371. const currentInitialEntry = this.entriesArr[0];
  2372. this.entriesArr[0] = new FakeNavigationHistoryEntry(this.window.document.createElement('div'), new URL(url).toString(), {
  2373. index: 0,
  2374. key: currentInitialEntry?.key ?? String(this.nextKey++),
  2375. id: currentInitialEntry?.id ?? String(this.nextId++),
  2376. sameDocument: true,
  2377. historyState: options?.historyState,
  2378. state: options.state,
  2379. });
  2380. }
  2381. /** Returns whether the initial entry is still eligible to be set. */
  2382. canSetInitialEntryForTesting() {
  2383. return this.canSetInitialEntry;
  2384. }
  2385. /**
  2386. * Sets whether to emulate traversals as synchronous rather than
  2387. * asynchronous.
  2388. */
  2389. setSynchronousTraversalsForTesting(synchronousTraversals) {
  2390. this.synchronousTraversals = synchronousTraversals;
  2391. }
  2392. /** Equivalent to `navigation.entries()`. */
  2393. entries() {
  2394. return this.entriesArr.slice();
  2395. }
  2396. /** Equivalent to `navigation.navigate()`. */
  2397. navigate(url, options) {
  2398. const fromUrl = new URL(this.currentEntry.url);
  2399. const toUrl = new URL(url, this.currentEntry.url);
  2400. let navigationType;
  2401. if (!options?.history || options.history === 'auto') {
  2402. // Auto defaults to push, but if the URLs are the same, is a replace.
  2403. if (fromUrl.toString() === toUrl.toString()) {
  2404. navigationType = 'replace';
  2405. }
  2406. else {
  2407. navigationType = 'push';
  2408. }
  2409. }
  2410. else {
  2411. navigationType = options.history;
  2412. }
  2413. const hashChange = isHashChange(fromUrl, toUrl);
  2414. const destination = new FakeNavigationDestination({
  2415. url: toUrl.toString(),
  2416. state: options?.state,
  2417. sameDocument: hashChange,
  2418. historyState: null,
  2419. });
  2420. const result = new InternalNavigationResult(this);
  2421. this.userAgentNavigate(destination, result, {
  2422. navigationType,
  2423. cancelable: true,
  2424. canIntercept: true,
  2425. // Always false for navigate().
  2426. userInitiated: false,
  2427. hashChange,
  2428. info: options?.info,
  2429. });
  2430. return {
  2431. committed: result.committed,
  2432. finished: result.finished,
  2433. };
  2434. }
  2435. /** Equivalent to `history.pushState()`. */
  2436. pushState(data, title, url) {
  2437. this.pushOrReplaceState('push', data, title, url);
  2438. }
  2439. /** Equivalent to `history.replaceState()`. */
  2440. replaceState(data, title, url) {
  2441. this.pushOrReplaceState('replace', data, title, url);
  2442. }
  2443. pushOrReplaceState(navigationType, data, _title, url) {
  2444. const fromUrl = new URL(this.currentEntry.url);
  2445. const toUrl = url ? new URL(url, this.currentEntry.url) : fromUrl;
  2446. const hashChange = isHashChange(fromUrl, toUrl);
  2447. const destination = new FakeNavigationDestination({
  2448. url: toUrl.toString(),
  2449. sameDocument: true,
  2450. historyState: data,
  2451. });
  2452. const result = new InternalNavigationResult(this);
  2453. this.userAgentNavigate(destination, result, {
  2454. navigationType,
  2455. cancelable: true,
  2456. canIntercept: true,
  2457. // Always false for pushState() or replaceState().
  2458. userInitiated: false,
  2459. hashChange,
  2460. });
  2461. }
  2462. /** Equivalent to `navigation.traverseTo()`. */
  2463. traverseTo(key, options) {
  2464. const fromUrl = new URL(this.currentEntry.url);
  2465. const entry = this.findEntry(key);
  2466. if (!entry) {
  2467. const domException = new DOMException('Invalid key', 'InvalidStateError');
  2468. const committed = Promise.reject(domException);
  2469. const finished = Promise.reject(domException);
  2470. committed.catch(() => { });
  2471. finished.catch(() => { });
  2472. return {
  2473. committed,
  2474. finished,
  2475. };
  2476. }
  2477. if (entry === this.currentEntry) {
  2478. return {
  2479. committed: Promise.resolve(this.currentEntry),
  2480. finished: Promise.resolve(this.currentEntry),
  2481. };
  2482. }
  2483. if (this.traversalQueue.has(entry.key)) {
  2484. const existingResult = this.traversalQueue.get(entry.key);
  2485. return {
  2486. committed: existingResult.committed,
  2487. finished: existingResult.finished,
  2488. };
  2489. }
  2490. const hashChange = isHashChange(fromUrl, new URL(entry.url, this.currentEntry.url));
  2491. const destination = new FakeNavigationDestination({
  2492. url: entry.url,
  2493. state: entry.getState(),
  2494. historyState: entry.getHistoryState(),
  2495. key: entry.key,
  2496. id: entry.id,
  2497. index: entry.index,
  2498. sameDocument: entry.sameDocument,
  2499. });
  2500. this.prospectiveEntryIndex = entry.index;
  2501. const result = new InternalNavigationResult(this);
  2502. this.traversalQueue.set(entry.key, result);
  2503. this.runTraversal(() => {
  2504. this.traversalQueue.delete(entry.key);
  2505. const event = this.userAgentNavigate(destination, result, {
  2506. navigationType: 'traverse',
  2507. cancelable: true,
  2508. canIntercept: true,
  2509. // Always false for traverseTo().
  2510. userInitiated: false,
  2511. hashChange,
  2512. info: options?.info,
  2513. });
  2514. // Note this does not pay attention at all to the commit status of the event (and thus, does not support deferred commit for traversals)
  2515. this.userAgentTraverse(event);
  2516. });
  2517. return {
  2518. committed: result.committed,
  2519. finished: result.finished,
  2520. };
  2521. }
  2522. /** Equivalent to `navigation.back()`. */
  2523. back(options) {
  2524. if (this.currentEntryIndex === 0) {
  2525. const domException = new DOMException('Cannot go back', 'InvalidStateError');
  2526. const committed = Promise.reject(domException);
  2527. const finished = Promise.reject(domException);
  2528. committed.catch(() => { });
  2529. finished.catch(() => { });
  2530. return {
  2531. committed,
  2532. finished,
  2533. };
  2534. }
  2535. const entry = this.entriesArr[this.currentEntryIndex - 1];
  2536. return this.traverseTo(entry.key, options);
  2537. }
  2538. /** Equivalent to `navigation.forward()`. */
  2539. forward(options) {
  2540. if (this.currentEntryIndex === this.entriesArr.length - 1) {
  2541. const domException = new DOMException('Cannot go forward', 'InvalidStateError');
  2542. const committed = Promise.reject(domException);
  2543. const finished = Promise.reject(domException);
  2544. committed.catch(() => { });
  2545. finished.catch(() => { });
  2546. return {
  2547. committed,
  2548. finished,
  2549. };
  2550. }
  2551. const entry = this.entriesArr[this.currentEntryIndex + 1];
  2552. return this.traverseTo(entry.key, options);
  2553. }
  2554. /**
  2555. * Equivalent to `history.go()`.
  2556. * Note that this method does not actually work precisely to how Chrome
  2557. * does, instead choosing a simpler model with less unexpected behavior.
  2558. * Chrome has a few edge case optimizations, for instance with repeated
  2559. * `back(); forward()` chains it collapses certain traversals.
  2560. */
  2561. go(direction) {
  2562. const targetIndex = this.prospectiveEntryIndex + direction;
  2563. if (targetIndex >= this.entriesArr.length || targetIndex < 0) {
  2564. return;
  2565. }
  2566. this.prospectiveEntryIndex = targetIndex;
  2567. this.runTraversal(() => {
  2568. // Check again that destination is in the entries array.
  2569. if (targetIndex >= this.entriesArr.length || targetIndex < 0) {
  2570. return;
  2571. }
  2572. const fromUrl = new URL(this.currentEntry.url);
  2573. const entry = this.entriesArr[targetIndex];
  2574. const hashChange = isHashChange(fromUrl, new URL(entry.url, this.currentEntry.url));
  2575. const destination = new FakeNavigationDestination({
  2576. url: entry.url,
  2577. state: entry.getState(),
  2578. historyState: entry.getHistoryState(),
  2579. key: entry.key,
  2580. id: entry.id,
  2581. index: entry.index,
  2582. sameDocument: entry.sameDocument,
  2583. });
  2584. const result = new InternalNavigationResult(this);
  2585. const event = this.userAgentNavigate(destination, result, {
  2586. navigationType: 'traverse',
  2587. cancelable: true,
  2588. canIntercept: true,
  2589. // Always false for go().
  2590. userInitiated: false,
  2591. hashChange,
  2592. });
  2593. // Note this does not pay attention at all to the commit status of the event (and thus, does not support deferred commit for traversals)
  2594. this.userAgentTraverse(event);
  2595. });
  2596. }
  2597. /** Runs a traversal synchronously or asynchronously */
  2598. runTraversal(traversal) {
  2599. if (this.synchronousTraversals) {
  2600. traversal();
  2601. return;
  2602. }
  2603. // Each traversal occupies a single timeout resolution.
  2604. // This means that Promises added to commit and finish should resolve
  2605. // before the next traversal.
  2606. this.nextTraversal = this.nextTraversal.then(() => {
  2607. return new Promise((resolve) => {
  2608. setTimeout(() => {
  2609. resolve();
  2610. traversal();
  2611. });
  2612. });
  2613. });
  2614. }
  2615. /** Equivalent to `navigation.addEventListener()`. */
  2616. addEventListener(type, callback, options) {
  2617. this.eventTarget.addEventListener(type, callback, options);
  2618. }
  2619. /** Equivalent to `navigation.removeEventListener()`. */
  2620. removeEventListener(type, callback, options) {
  2621. this.eventTarget.removeEventListener(type, callback, options);
  2622. }
  2623. /** Equivalent to `navigation.dispatchEvent()` */
  2624. dispatchEvent(event) {
  2625. return this.eventTarget.dispatchEvent(event);
  2626. }
  2627. /** Cleans up resources. */
  2628. dispose() {
  2629. // Recreate eventTarget to release current listeners.
  2630. // `document.createElement` because NodeJS `EventTarget` is incompatible with Domino's `Event`.
  2631. this.eventTarget = this.window.document.createElement('div');
  2632. this.disposed = true;
  2633. }
  2634. /** Returns whether this fake is disposed. */
  2635. isDisposed() {
  2636. return this.disposed;
  2637. }
  2638. /** Implementation for all navigations and traversals. */
  2639. userAgentNavigate(destination, result, options) {
  2640. // The first navigation should disallow any future calls to set the initial
  2641. // entry.
  2642. this.canSetInitialEntry = false;
  2643. if (this.navigateEvent) {
  2644. this.navigateEvent.cancel(new DOMException('Navigation was aborted', 'AbortError'));
  2645. this.navigateEvent = null;
  2646. }
  2647. return dispatchNavigateEvent({
  2648. navigationType: options.navigationType,
  2649. cancelable: options.cancelable,
  2650. canIntercept: options.canIntercept,
  2651. userInitiated: options.userInitiated,
  2652. hashChange: options.hashChange,
  2653. signal: result.signal,
  2654. destination,
  2655. info: options.info,
  2656. sameDocument: destination.sameDocument,
  2657. result,
  2658. });
  2659. }
  2660. /**
  2661. * Implementation to commit a navigation.
  2662. * https://whatpr.org/html/10919/nav-history-apis.html#navigateevent-commit
  2663. * @internal
  2664. */
  2665. commitNavigateEvent(navigateEvent) {
  2666. navigateEvent.interceptionState = 'committed';
  2667. const from = this.currentEntry;
  2668. if (!from) {
  2669. throw new Error('cannot commit navigation when current entry is null');
  2670. }
  2671. if (!navigateEvent.sameDocument) {
  2672. const error = new Error('Cannot navigate to a non-same-document URL.');
  2673. navigateEvent.cancel(error);
  2674. throw error;
  2675. }
  2676. // "If navigationType is "push" or "replace", then run the URL and history update steps given document and event's destination's URL, with serialiedData set to event's classic history API state and historyHandling set to navigationType."
  2677. if (navigateEvent.navigationType === 'push' || navigateEvent.navigationType === 'replace') {
  2678. this.urlAndHistoryUpdateSteps(navigateEvent);
  2679. }
  2680. else if (navigateEvent.navigationType === 'reload') {
  2681. this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
  2682. }
  2683. else ;
  2684. }
  2685. /**
  2686. * Implementation for a push or replace navigation.
  2687. * https://whatpr.org/html/10919/browsing-the-web.html#url-and-history-update-steps
  2688. * https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
  2689. */
  2690. urlAndHistoryUpdateSteps(navigateEvent) {
  2691. this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
  2692. }
  2693. /**
  2694. * Implementation for a traverse navigation.
  2695. *
  2696. * https://whatpr.org/html/10919/browsing-the-web.html#apply-the-traverse-history-step
  2697. * ...
  2698. * > Let updateDocument be an algorithm step which performs update document for history step application given targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength, scriptHistoryIndex, navigationType, entriesForNavigationAPI, and previousEntry.
  2699. * > If targetEntry's document is equal to displayedDocument, then perform updateDocument.
  2700. * https://whatpr.org/html/10919/browsing-the-web.html#update-document-for-history-step-application
  2701. * which then goes to https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
  2702. */
  2703. userAgentTraverse(navigateEvent) {
  2704. this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
  2705. // Happens as part of "updating the document" steps https://whatpr.org/html/10919/browsing-the-web.html#updating-the-document
  2706. const popStateEvent = createPopStateEvent({
  2707. state: navigateEvent.destination.getHistoryState(),
  2708. });
  2709. this.window.dispatchEvent(popStateEvent);
  2710. // TODO(atscott): If oldURL's fragment is not equal to entry's URL's fragment, then queue a global task to fire an event named hashchange
  2711. }
  2712. /** https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation */
  2713. updateNavigationEntriesForSameDocumentNavigation({ destination, navigationType, result, }) {
  2714. const oldCurrentNHE = this.currentEntry;
  2715. const disposedNHEs = [];
  2716. if (navigationType === 'traverse') {
  2717. this.currentEntryIndex = destination.index;
  2718. if (this.currentEntryIndex === -1) {
  2719. throw new Error('unexpected current entry index');
  2720. }
  2721. }
  2722. else if (navigationType === 'push') {
  2723. this.currentEntryIndex++;
  2724. this.prospectiveEntryIndex = this.currentEntryIndex; // prospectiveEntryIndex isn't in the spec but is an implementation detail
  2725. disposedNHEs.push(...this.entriesArr.splice(this.currentEntryIndex));
  2726. }
  2727. else if (navigationType === 'replace') {
  2728. disposedNHEs.push(oldCurrentNHE);
  2729. }
  2730. if (navigationType === 'push' || navigationType === 'replace') {
  2731. const index = this.currentEntryIndex;
  2732. const key = navigationType === 'push' ? String(this.nextKey++) : this.currentEntry.key;
  2733. const newNHE = new FakeNavigationHistoryEntry(this.window.document.createElement('div'), destination.url, {
  2734. id: String(this.nextId++),
  2735. key,
  2736. index,
  2737. sameDocument: true,
  2738. state: destination.getState(),
  2739. historyState: destination.getHistoryState(),
  2740. });
  2741. this.entriesArr[this.currentEntryIndex] = newNHE;
  2742. }
  2743. result.committedResolve(this.currentEntry);
  2744. const currentEntryChangeEvent = createFakeNavigationCurrentEntryChangeEvent({
  2745. from: oldCurrentNHE,
  2746. navigationType: navigationType,
  2747. });
  2748. this.eventTarget.dispatchEvent(currentEntryChangeEvent);
  2749. for (const disposedNHE of disposedNHEs) {
  2750. disposedNHE.dispose();
  2751. }
  2752. }
  2753. /** Utility method for finding entries with the given `key`. */
  2754. findEntry(key) {
  2755. for (const entry of this.entriesArr) {
  2756. if (entry.key === key)
  2757. return entry;
  2758. }
  2759. return undefined;
  2760. }
  2761. set onnavigate(
  2762. // tslint:disable-next-line:no-any
  2763. _handler) {
  2764. throw new Error('unimplemented');
  2765. }
  2766. // tslint:disable-next-line:no-any
  2767. get onnavigate() {
  2768. throw new Error('unimplemented');
  2769. }
  2770. set oncurrententrychange(_handler) {
  2771. throw new Error('unimplemented');
  2772. }
  2773. get oncurrententrychange() {
  2774. throw new Error('unimplemented');
  2775. }
  2776. set onnavigatesuccess(
  2777. // tslint:disable-next-line:no-any
  2778. _handler) {
  2779. throw new Error('unimplemented');
  2780. }
  2781. // tslint:disable-next-line:no-any
  2782. get onnavigatesuccess() {
  2783. throw new Error('unimplemented');
  2784. }
  2785. set onnavigateerror(
  2786. // tslint:disable-next-line:no-any
  2787. _handler) {
  2788. throw new Error('unimplemented');
  2789. }
  2790. // tslint:disable-next-line:no-any
  2791. get onnavigateerror() {
  2792. throw new Error('unimplemented');
  2793. }
  2794. _transition = null;
  2795. /** @internal */
  2796. set transition(t) {
  2797. this._transition = t;
  2798. }
  2799. get transition() {
  2800. return this._transition;
  2801. }
  2802. updateCurrentEntry(_options) {
  2803. throw new Error('unimplemented');
  2804. }
  2805. reload(_options) {
  2806. throw new Error('unimplemented');
  2807. }
  2808. }
  2809. /**
  2810. * Fake equivalent of `NavigationHistoryEntry`.
  2811. */
  2812. class FakeNavigationHistoryEntry {
  2813. eventTarget;
  2814. url;
  2815. sameDocument;
  2816. id;
  2817. key;
  2818. index;
  2819. state;
  2820. historyState;
  2821. // tslint:disable-next-line:no-any
  2822. ondispose = null;
  2823. constructor(eventTarget, url, { id, key, index, sameDocument, state, historyState, }) {
  2824. this.eventTarget = eventTarget;
  2825. this.url = url;
  2826. this.id = id;
  2827. this.key = key;
  2828. this.index = index;
  2829. this.sameDocument = sameDocument;
  2830. this.state = state;
  2831. this.historyState = historyState;
  2832. }
  2833. getState() {
  2834. // Budget copy.
  2835. return this.state ? JSON.parse(JSON.stringify(this.state)) : this.state;
  2836. }
  2837. getHistoryState() {
  2838. // Budget copy.
  2839. return this.historyState
  2840. ? JSON.parse(JSON.stringify(this.historyState))
  2841. : this.historyState;
  2842. }
  2843. addEventListener(type, callback, options) {
  2844. this.eventTarget.addEventListener(type, callback, options);
  2845. }
  2846. removeEventListener(type, callback, options) {
  2847. this.eventTarget.removeEventListener(type, callback, options);
  2848. }
  2849. dispatchEvent(event) {
  2850. return this.eventTarget.dispatchEvent(event);
  2851. }
  2852. /** internal */
  2853. dispose() {
  2854. const disposeEvent = new Event('disposed');
  2855. this.dispatchEvent(disposeEvent);
  2856. // release current listeners
  2857. this.eventTarget = null;
  2858. }
  2859. }
  2860. /**
  2861. * Create a fake equivalent of `NavigateEvent`. This is not a class because ES5
  2862. * transpiled JavaScript cannot extend native Event.
  2863. *
  2864. * https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigate-event-firing
  2865. */
  2866. function dispatchNavigateEvent({ cancelable, canIntercept, userInitiated, hashChange, navigationType, signal, destination, info, sameDocument, result, }) {
  2867. const { navigation } = result;
  2868. const event = new Event('navigate', { bubbles: false, cancelable });
  2869. event.focusResetBehavior = null;
  2870. event.scrollBehavior = null;
  2871. event.interceptionState = 'none';
  2872. event.canIntercept = canIntercept;
  2873. event.userInitiated = userInitiated;
  2874. event.hashChange = hashChange;
  2875. event.navigationType = navigationType;
  2876. event.signal = signal;
  2877. event.destination = destination;
  2878. event.info = info;
  2879. event.downloadRequest = null;
  2880. event.formData = null;
  2881. event.result = result;
  2882. event.sameDocument = sameDocument;
  2883. event.commitOption = 'immediate';
  2884. let handlersFinished = [Promise.resolve()];
  2885. let dispatchedNavigateEvent = false;
  2886. event.intercept = function (options) {
  2887. if (!this.canIntercept) {
  2888. throw new DOMException(`Cannot intercept when canIntercept is 'false'`, 'SecurityError');
  2889. }
  2890. this.interceptionState = 'intercepted';
  2891. event.sameDocument = true;
  2892. const handler = options?.handler;
  2893. if (handler) {
  2894. handlersFinished.push(handler());
  2895. }
  2896. // override old options with new ones. UA _may_ report a console warning if new options differ from previous
  2897. event.commitOption = options?.commit ?? event.commitOption;
  2898. event.scrollBehavior = options?.scroll ?? event.scrollBehavior;
  2899. event.focusResetBehavior = options?.focusReset ?? event.focusResetBehavior;
  2900. };
  2901. event.scroll = function () {
  2902. if (event.interceptionState !== 'committed') {
  2903. throw new DOMException(`Failed to execute 'scroll' on 'NavigateEvent': scroll() must be ` +
  2904. `called after commit() and interception options must specify manual scroll.`, 'InvalidStateError');
  2905. }
  2906. processScrollBehavior(event);
  2907. };
  2908. event.commit = function (internal = false) {
  2909. if (!internal && this.interceptionState !== 'intercepted') {
  2910. throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': intercept() must be ` +
  2911. `called before commit() and commit() cannot be already called.`, 'InvalidStateError');
  2912. }
  2913. if (!internal && event.commitOption !== 'after-transition') {
  2914. throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
  2915. `called if commit behavior is not "after-transition",.`, 'InvalidStateError');
  2916. }
  2917. if (!dispatchedNavigateEvent) {
  2918. throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
  2919. `called during event dispatch.`, 'InvalidStateError');
  2920. }
  2921. this.interceptionState = 'committed';
  2922. result.navigation.commitNavigateEvent(event);
  2923. };
  2924. // Internal only.
  2925. event.cancel = function (reason) {
  2926. result.committedReject(reason);
  2927. result.finishedReject(reason);
  2928. };
  2929. function dispatch() {
  2930. navigation.navigateEvent = event;
  2931. navigation.eventTarget.dispatchEvent(event);
  2932. dispatchedNavigateEvent = true;
  2933. if (event.interceptionState !== 'none') {
  2934. navigation.transition = new InternalNavigationTransition(navigation.currentEntry, navigationType);
  2935. if (event.commitOption !== 'after-transition') {
  2936. event.commit(/** internal */ true);
  2937. }
  2938. }
  2939. else {
  2940. // In the spec, this isn't really part of the navigate API. Instead, the navigate event firing returns "true" to indicate
  2941. // navigation steps should "continue" (https://whatpr.org/html/10919/browsing-the-web.html#beginning-navigation)
  2942. event.commit(/** internal */ true);
  2943. }
  2944. Promise.all(handlersFinished).then(() => {
  2945. // Follows steps outlined under "Wait for all of promisesList, with the following success steps:"
  2946. // in the spec https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigate-event-firing.
  2947. if (result.signal.aborted) {
  2948. return;
  2949. }
  2950. if (event !== navigation.navigateEvent) {
  2951. throw new Error("Navigation's ongoing event not equal to resolved event");
  2952. }
  2953. navigation.navigateEvent = null;
  2954. if (event.interceptionState === 'intercepted') {
  2955. navigation.commitNavigateEvent(event);
  2956. }
  2957. finishNavigationEvent(event, true);
  2958. const navigatesuccessEvent = new Event('navigatesuccess', { bubbles: false, cancelable });
  2959. navigation.eventTarget.dispatchEvent(navigatesuccessEvent);
  2960. result.finishedResolve();
  2961. if (navigation.transition !== null) {
  2962. navigation.transition.finishedResolve();
  2963. }
  2964. navigation.transition = null;
  2965. }, (reason) => {
  2966. if (result.signal.aborted) {
  2967. return;
  2968. }
  2969. if (event !== navigation.navigateEvent) {
  2970. throw new Error("Navigation's ongoing event not equal to resolved event");
  2971. }
  2972. navigation.navigateEvent = null;
  2973. event.interceptionState = 'rejected'; // TODO(atscott): this is not in the spec https://github.com/whatwg/html/issues/11087
  2974. finishNavigationEvent(event, false);
  2975. const navigateerrorEvent = new Event('navigateerror', { bubbles: false, cancelable });
  2976. navigation.eventTarget.dispatchEvent(navigateerrorEvent);
  2977. result.finishedReject(reason);
  2978. if (navigation.transition !== null) {
  2979. navigation.transition.finishedResolve();
  2980. }
  2981. navigation.transition = null;
  2982. });
  2983. }
  2984. dispatch();
  2985. return event;
  2986. }
  2987. /** https://whatpr.org/html/10919/nav-history-apis.html#navigateevent-finish */
  2988. function finishNavigationEvent(event, didFulfill) {
  2989. if (event.interceptionState === 'intercepted' || event.interceptionState === 'finished') {
  2990. throw new Error('Attempting to finish navigation event that was incomplete or already finished');
  2991. }
  2992. if (event.interceptionState === 'none') {
  2993. return;
  2994. }
  2995. if (didFulfill) {
  2996. // TODO(atscott): https://github.com/whatwg/html/issues/11087 focus reset is not guarded by didFulfill in the spec
  2997. potentiallyResetFocus(event);
  2998. potentiallyResetScroll(event);
  2999. }
  3000. event.interceptionState = 'finished';
  3001. }
  3002. /** https://whatpr.org/html/10919/nav-history-apis.html#potentially-reset-the-focus */
  3003. function potentiallyResetFocus(event) {
  3004. if (event.interceptionState !== 'committed' && event.interceptionState !== 'scrolled') {
  3005. throw new Error('cannot reset focus if navigation event is not committed or scrolled');
  3006. }
  3007. // TODO(atscott): The rest of the steps
  3008. }
  3009. function potentiallyResetScroll(event) {
  3010. if (event.interceptionState !== 'committed' && event.interceptionState !== 'scrolled') {
  3011. throw new Error('cannot reset scroll if navigation event is not committed or scrolled');
  3012. }
  3013. if (event.interceptionState === 'scrolled' || event.scrollBehavior === 'manual') {
  3014. return;
  3015. }
  3016. processScrollBehavior(event);
  3017. }
  3018. /* https://whatpr.org/html/10919/nav-history-apis.html#process-scroll-behavior */
  3019. function processScrollBehavior(event) {
  3020. if (event.interceptionState !== 'committed') {
  3021. throw new Error('invalid event interception state when processing scroll behavior');
  3022. }
  3023. event.interceptionState = 'scrolled';
  3024. // TODO(atscott): the rest of the steps
  3025. }
  3026. /**
  3027. * Create a fake equivalent of `NavigationCurrentEntryChange`. This does not use
  3028. * a class because ES5 transpiled JavaScript cannot extend native Event.
  3029. */
  3030. function createFakeNavigationCurrentEntryChangeEvent({ from, navigationType, }) {
  3031. const event = new Event('currententrychange', {
  3032. bubbles: false,
  3033. cancelable: false,
  3034. });
  3035. event.from = from;
  3036. event.navigationType = navigationType;
  3037. return event;
  3038. }
  3039. /**
  3040. * Create a fake equivalent of `PopStateEvent`. This does not use a class
  3041. * because ES5 transpiled JavaScript cannot extend native Event.
  3042. */
  3043. function createPopStateEvent({ state }) {
  3044. const event = new Event('popstate', {
  3045. bubbles: false,
  3046. cancelable: false,
  3047. });
  3048. event.state = state;
  3049. return event;
  3050. }
  3051. /**
  3052. * Fake equivalent of `NavigationDestination`.
  3053. */
  3054. class FakeNavigationDestination {
  3055. url;
  3056. sameDocument;
  3057. key;
  3058. id;
  3059. index;
  3060. state;
  3061. historyState;
  3062. constructor({ url, sameDocument, historyState, state, key = null, id = null, index = -1, }) {
  3063. this.url = url;
  3064. this.sameDocument = sameDocument;
  3065. this.state = state;
  3066. this.historyState = historyState;
  3067. this.key = key;
  3068. this.id = id;
  3069. this.index = index;
  3070. }
  3071. getState() {
  3072. return this.state;
  3073. }
  3074. getHistoryState() {
  3075. return this.historyState;
  3076. }
  3077. }
  3078. /** Utility function to determine whether two UrlLike have the same hash. */
  3079. function isHashChange(from, to) {
  3080. return (to.hash !== from.hash &&
  3081. to.hostname === from.hostname &&
  3082. to.pathname === from.pathname &&
  3083. to.search === from.search);
  3084. }
  3085. class InternalNavigationTransition {
  3086. from;
  3087. navigationType;
  3088. finished;
  3089. finishedResolve;
  3090. finishedReject;
  3091. constructor(from, navigationType) {
  3092. this.from = from;
  3093. this.navigationType = navigationType;
  3094. this.finished = new Promise((resolve, reject) => {
  3095. this.finishedReject = reject;
  3096. this.finishedResolve = resolve;
  3097. });
  3098. }
  3099. }
  3100. /**
  3101. * Internal utility class for representing the result of a navigation.
  3102. * Generally equivalent to the "apiMethodTracker" in the spec.
  3103. */
  3104. class InternalNavigationResult {
  3105. navigation;
  3106. committedTo = null;
  3107. committedResolve;
  3108. committedReject;
  3109. finishedResolve;
  3110. finishedReject;
  3111. committed;
  3112. finished;
  3113. get signal() {
  3114. return this.abortController.signal;
  3115. }
  3116. abortController = new AbortController();
  3117. constructor(navigation) {
  3118. this.navigation = navigation;
  3119. this.committed = new Promise((resolve, reject) => {
  3120. this.committedResolve = (entry) => {
  3121. this.committedTo = entry;
  3122. resolve(entry);
  3123. };
  3124. this.committedReject = reject;
  3125. });
  3126. this.finished = new Promise(async (resolve, reject) => {
  3127. this.finishedResolve = () => {
  3128. if (this.committedTo === null) {
  3129. throw new Error('NavigateEvent should have been committed before resolving finished promise.');
  3130. }
  3131. resolve(this.committedTo);
  3132. };
  3133. this.finishedReject = (reason) => {
  3134. reject(reason);
  3135. this.abortController.abort(reason);
  3136. };
  3137. });
  3138. // All rejections are handled.
  3139. this.committed.catch(() => { });
  3140. this.finished.catch(() => { });
  3141. }
  3142. }
  3143. export { ComponentFixture, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, DeferBlockFixture, InjectSetupWrapper, TestBed, TestComponentRenderer, __core_private_testing_placeholder__, discardPeriodicTasks, fakeAsync, flush, flushMicrotasks, getTestBed, inject, resetFakeAsyncZone, tick, waitForAsync, withModule, FakeNavigation as ɵFakeNavigation, MetadataOverrider as ɵMetadataOverrider };
  3144. //# sourceMappingURL=testing.mjs.map