escribe('with animations', () => {
      @Component({
        standalone: true,
        selector: 'hello-app',
        template:
          '<div @myAnimation (@myAnimation.start)="onStart($event)">Hello from AnimationCmp!</div>',
        animations: [
          trigger('myAnimation', [transition('void => *', [style({opacity: 1}), animate(5)])]),
        ],
      })
      class AnimationCmp {
        renderer = _inject(ANIMATION_MODULE_TYPE, {optional: true}) ?? 'not found';
        startEvent?: {};

        onStart(event: {}) {
          this.startEvent = event;
        }
      }

      it('should enable animations when using provideAnimations()', async () => {
        const appRef = await bootstrapApplication(AnimationCmp, {
          providers: [provideAnimations()],
        });
        const cmp = appRef.components[0].instance;

        // Wait until animation is completed.
        await new Promise((resolve) => setTimeout(resolve, 10));

        expect(cmp.renderer).toBe('BrowserAnimations');
        expect(cmp.startEvent.triggerName).toEqual('myAnimation');
        expect(cmp.startEvent.phaseName).toEqual('start');

        expect(el.innerText).toBe('Hello from AnimationCmp!');
      });

      it('should use noop animations renderer when using provideNoopAnimations()', async () => {
        const appRef = await bootstrapApplication(AnimationCmp, {
          providers: [provideNoopAnimations()],
        });
        const cmp = appRef.components[0].instance;

        // Wait until animation is completed.
        await new Promise((resolve) => setTimeout(resolve, 10));

        expect(cmp.renderer).toBe('NoopAnimations');
        expect(cmp.startEvent.triggerName).toEqual('myAnimation');
        expect(cmp.startEvent.phaseName).toEqual('start');

        expect(el.innerText).toBe('Hello from AnimationCmp!');
      });
    });

    it('initializes modules inside the NgZone when using `provideZoneChangeDetection`', async () => {
      let moduleInitialized = false;
      @NgModule({})
      class SomeModule {
        constructor() {
          expect(NgZone.isInAngularZone()).toBe(true);
          moduleInitialized = true;
        }
      }
      @Component({
        template: '',
        selector: 'hello-app',
        imports: [SomeModule],
        standalone: true,
      })
      class AnimationCmp {}

      await bootstrapApplication(AnimationCmp, {
        providers: [provideZoneChangeDetection({eventCoalescing: true})],
      });
      expect(moduleInitialized).toBe(true);
    });
  });

  it('should throw if bootstrapped Directive is not a Component', (done) => {
    const logger = new MockConsole();
    const errorHandler = new ErrorHandler();
    (errorHandler as any)._console = logger as any;
    bootstrap(HelloRootDirectiveIsNotCmp, [{provide: ErrorHandler, useValue: errorHandler}]).catch(
      (error: Error) => {
        expect(error).toEqual(
          new Error(`HelloRootDirectiveIsNotCmp cannot be used as an entry component.`),
        );
        done();
      },
    );
  });

  it('should have the TransferState token available in NgModule bootstrap', async () => {
    let state: TransferState | undefined;
    @Component({
      selector: 'hello-app',
      template: '...',
      standalone: false,
    })
    class NonStandaloneComponent {
      constructor() {
        state = _inject(TransferState);
      }
    }

    await bootstrap(NonStandaloneComponent);
    expect(state).toBeInstanceOf(TransferState);
  });

  it('should retrieve sanitizer', inject([Injector], (injector: Injector) => {
    const sanitizer: Sanitizer | null = injector.get(Sanitizer, null);
    // We don't want to have sanitizer in DI. We use DI only to overwrite the
    // sanitizer, but not for default one. The default one is pulled in by the Ivy
    // instructions as needed.
    expect(sanitizer).toBe(null);
  }));

  it('should throw if no element is found', (done) => {
    const logger = new MockConsole();
    const errorHandler = new ErrorHandler();
    (errorHandler as any)._console = logger as any;
    bootstrap(NonExistentComp, [{provide: ErrorHandler, useValue: errorHandler}]).then(
      null,
      (reason) => {
        expect(reason.message).toContain('The selector "non-existent" did not match any elements');
        done();
        return null;
      },
    );
  });

  it('should throw if no provider', async () => {
    const logger = new MockConsole();
    const errorHandler = new ErrorHandler();
    (errorHandler as any)._console = logger as any;

    class IDontExist {}

    @Component({
      selector: 'cmp',
      template: 'Cmp',
      standalone: false,
    })
    class CustomCmp {
      constructor(iDontExist: IDontExist) {}
    }

    @Component({
      selector: 'hello-app',
      template: '<cmp></cmp>',
      standalone: false,
    })
    class RootCmp {}

    @NgModule({declarations: [CustomCmp], exports: [CustomCmp]})
    class CustomModule {}

    await expectAsync(
      bootstrap(RootCmp, [{provide: ErrorHandler, useValue: errorHandler}], [], [CustomModule]),
    ).toBeRejected();
  });

  if (getDOM().supportsDOMEvents) {
    it('should forward the error to promise when bootstrap fails', (done) => {
      const logger = new MockConsole();
      const errorHandler = new ErrorHandler();
      (errorHandler as any)._console = logger as any;

      const refPromise = bootstrap(NonExistentComp, [
        {provide: ErrorHandler, useValue: errorHandler},
      ]);
      refPromise.then(null, (reason: any) => {
        expect(reason.message).toContain('The selector "non-existent" did not match any elements');
        done();
      });
    });

    it('should invoke the default exception handler when bootstrap fails', (done) => {
      const logger = new MockConsole();
      const errorHandler = new ErrorHandler();
      (errorHandler as any)._console = logger as any;

      const refPromise = bootstrap(NonExistentComp, [
        {provide: ErrorHandler, useValue: errorHandler},
      ]);
      refPromise.then(null, (reason) => {
        expect(logger.res[0].join('#')).toContain(
          'ERROR#Error: NG05104: The selector "non-existent" did not match any elements',
        );
        done();
        return null;
      });
    });
  }

  it('should create an injector promise', async () => {
    const refPromise = bootstrap(HelloRootCmp, testProviders);
    expect(refPromise).toEqual(jasmine.any(Promise));
    await refPromise; // complete component initialization before switching to the next test
  });

  it('should set platform name to browser', (done) => {
    const refPromise = bootstrap(HelloRootCmp, testProviders);
    refPromise.then((ref) => {
      expect(isPlatformBrowser(ref.injector.get(PLATFORM_ID))).toBe(true);
      done();
    }, done.fail);
  });

  it('should display hello world', (done) => {
    const refPromise = bootstrap(HelloRootCmp, testProviders);
    refPromise.then((ref) => {
      expect(el).toHaveText('hello world!');
      expect(el.getAttribute('ng-version')).toEqual(VERSION.full);
      done();
    }, done.fail);
  });

  it('should throw a descriptive error if BrowserModule is installed again via a lazily loaded module', (done) => {
    @NgModule({imports: [BrowserModule]})
    class AsyncModule {}
    bootstrap(HelloRootCmp, testProviders)
      .then((ref: ComponentRef<HelloRootCmp>) => {
        const compiler: Compiler = ref.injector.get(Compiler);
        return compiler.compileModuleAsync(AsyncModule).then((factory) => {
          expect(() => factory.create(ref.injector)).toThrowError(
            'NG05100: Providers from the `BrowserModule` have already been loaded. ' +
              'If you need access to common directives such as NgIf and NgFor, ' +
              'import the `CommonModule` instead.',
          );
        });
      })
      .then(
        () => done(),
        (err) => done.fail(err),
      );
  });

  it('should support multiple calls to bootstrap', (done) => {
    const refPromise1 = bootstrap(HelloRootCmp, testProviders);
    const refPromise2 = bootstrap(HelloRootCmp2, testProviders);
    Promise.all([refPromise1, refPromise2]).then((refs) => {
      expect(el).toHaveText('hello world!');
      expect(el2).toHaveText('hello world, again!');
      done();
    }, done.fail);
  });

  it('should not crash if change detection is invoked when the root component is disposed', (done) => {
    bootstrap(HelloOnDestroyTickCmp, testProviders).then((ref) => {
      expect(() => ref.destroy()).not.toThrow();
      done();
    });
  });


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Component,
  destroyPlatform,
  ErrorHandler,
  Inject,
  Injectable,
  InjectionToken,
  NgModule,
  NgZone,
  PlatformRef,
} from '@angular/core';
import {R3Injector} from '@angular/core/src/di/r3_injector';
import {NoopNgZone} from '@angular/core/src/zone/ng_zone';
import {withBody} from '@angular/private/testing';

import {bootstrapApplication, BrowserModule} from '../../src/browser';

describe('bootstrapApplication for standalone components', () => {
  beforeEach(destroyPlatform);
  afterEach(destroyPlatform);

  class SilentErrorHandler extends ErrorHandler {
    override handleError() {
      // the error is already re-thrown by the application ref.
      // we don't want to print it, but instead catch it in tests.
    }
  }

  it(
    'should create injector where ambient providers shadow explicit providers',
    withBody('<test-app></test-app>', async () => {
      const testToken = new InjectionToken('test token');

      @NgModule({
        providers: [{provide: testToken, useValue: 'Ambient'}],
      })
      class AmbientModule {}

      @Component({
        selector: 'test-app',
        standalone: true,
        template: `({{testToken}})`,
        imports: [AmbientModule],
      })
      class StandaloneCmp {
        constructor(@Inject(testToken) readonly testToken: String) {}
      }

      const appRef = await bootstrapApplication(StandaloneCmp, {
        providers: [{provide: testToken, useValue: 'Bootstrap'}],
      });

      appRef.tick();

      // make sure that ambient providers "shadow" ones explicitly provided during bootstrap
      expect(document.body.textContent).toBe('(Ambient)');
    }),
  );

  it(
    'should be able to provide a custom zone implementation in DI',
    withBody('<test-app></test-app>', async () => {
      @Component({
        selector: 'test-app',
        standalone: true,
        template: ``,
      })
      class StandaloneCmp {}

      class CustomZone extends NoopNgZone {}
      const instance = new CustomZone();

      const appRef = await bootstrapApplication(StandaloneCmp, {
        providers: [{provide: NgZone, useValue: instance}],
      });

      appRef.tick();
      expect(appRef.injector.get(NgZone)).toEqual(instance);
    }),
  );

  /*
    This test verifies that ambient providers for the standalone component being bootstrapped
    (providers collected from the import graph of a standalone component) are instantiated in a
    dedicated standalone injector. As the result we are ending up with the following injectors
    hierarchy:
    - platform injector (platform specific providers go here);
    - application injector (providers specified in the bootstrap options go here);
    - standalone injector (ambient providers go here);
  */
  it(
    'should create a standalone injector for standalone components with ambient providers',
    withBody('<test-app></test-app>', async () => {
      const ambientToken = new InjectionToken('ambient token');

      @NgModule({
        providers: [{provide: ambientToken, useValue: 'Only in AmbientNgModule'}],
      })
      class AmbientModule {}

      @Injectable()
      class NeedsAmbientProvider {
        constructor(@Inject(ambientToken) readonly ambientToken: String) {}
      }

      @Component({
        selector: 'test-app',
        template: `({{service.ambientToken}})`,
        standalone: true,
        imports: [AmbientModule],
      })
      class StandaloneCmp {
        constructor(readonly service: NeedsAmbientProvider) {}
      }

      try {
        await bootstrapApplication(StandaloneCmp, {
          providers: [{provide: ErrorHandler, useClass: SilentErrorHandler}, NeedsAmbientProvider],
        });

        // we expect the bootstrap process to fail since the "NeedsAmbientProvider" service
        // (located in the application injector) can't "see" ambient providers (located in a
        // standalone injector that is a child of the application injector).
        fail('Expected to throw');
      } catch (e: unknown) {
        expect(e).toBeInstanceOf(Error);
        expect((e as Error).message).toContain('No provider for InjectionToken ambient token!');
      }
    }),
  );

  it(
    'should throw if `BrowserModule` is imported in the standalone bootstrap scenario',
    withBody('<test-app></test-app>', async () => {
      @Component({
        selector: 'test-app',
        template: '...',
        standalone: true,
        imports: [BrowserModule],
      })
      class StandaloneCmp {}

      try {
        await bootstrapApplication(StandaloneCmp, {
          providers: [{provide: ErrorHandler, useClass: SilentErrorHandler}],
        });

        // The `bootstrapApplication` already includes the set of providers from the
        // `BrowserModule`, so including the `BrowserModule` again will bring duplicate
        // providers and we want to avoid it.
        fail('Expected to throw');
      } catch (e: unknown) {
        expect(e).toBeInstanceOf(Error);
        expect((e as Error).message).toContain(
          'NG05100: Providers from the `BrowserModule` have already been loaded.',
        );
      }
    }),
  );

  it(
    'should throw if `BrowserModule` is imported indirectly in the standalone bootstrap scenario',
    withBody('<test-app></test-app>', async () => {
      @NgModule({
        imports: [BrowserModule],
      })
      class SomeDependencyModule {}

      @Component({
        selector: 'test-app',
        template: '...',
        standalone: true,
        imports: [SomeDependencyModule],
      })
      class StandaloneCmp {}

      try {
        await bootstrapApplication(StandaloneCmp, {
          providers: [{provide: ErrorHandler, useClass: SilentErrorHandler}],
        });

        // The `bootstrapApplication` already includes the set of providers from the
        // `BrowserModule`, so including the `BrowserModule` again will bring duplicate
        // providers and we want to avoid it.
        fail('Expected to throw');
      } catch (e: unknown) {
        expect(e).toBeInstanceOf(Error);
        expect((e as Error).message).toContain(
          'NG05100: Providers from the `BrowserModule` have already been loaded.',
        );
      }
    }),
  );

  it(
    'should trigger an app destroy when a platform is destroyed',
    withBody('<test-app></test-app>', async () => {
      let compOnDestroyCalled = false;
      let serviceOnDestroyCalled = false;
      let injectorOnDestroyCalled = false;

      @Injectable({providedIn: 'root'})
      class ServiceWithOnDestroy {
        ngOnDestroy() {
          serviceOnDestroyCalled = true;
        }
      }

      @Component({
        selector: 'test-app',
        standalone: true,
        template: 'Hello',
      })
      class ComponentWithOnDestroy {
        constructor(service: ServiceWithOnDestroy) {}

        ngOnDestroy() {
          compOnDestroyCalled = true;
        }
      }

      const appRef = await bootstrapApplication(ComponentWithOnDestroy);
      const injector = (appRef as unknown as {injector: R3Injector}).injector;
      injector.onDestroy(() => (injectorOnDestroyCalled = true));

      expect(document.body.textContent).toBe('Hello');

      const platformRef = injector.get(PlatformRef);
      platformRef.destroy();

      // Verify the callbacks were invoked.
      expect(compOnDestroyCalled).toBe(true);
      expect(serviceOnDestroyCalled).toBe(true);
      expect(injectorOnDestroyCalled).toBe(true);

      // Make sure the DOM has been cleaned up as well.
      expect(document.body.textContent).toBe('');
    }),
  );
});


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
import {
  ModuleWithProviders,
  NgModule,
  Provider,
  ɵperformanceMarkFeature as performanceMarkFeature,
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

import {BROWSER_ANIMATIONS_PROVIDERS, BROWSER_NOOP_ANIMATIONS_PROVIDERS} from './providers';

/**
 * Object used to configure the behavior of {@link BrowserAnimationsModule}
 * @publicApi
 */
export interface BrowserAnimationsModuleConfig {
  /**
   *  Whether animations should be disabled. Passing this is identical to providing the
   * `NoopAnimationsModule`, but it can be controlled based on a runtime value.
   */
  disableAnimations?: boolean;
}

/**
 * Exports `BrowserModule` with additional dependency-injection providers
 * for use with animations. See [Animations](guide/animations).
 * @publicApi
 */
@NgModule({
  exports: [BrowserModule],
  providers: BROWSER_ANIMATIONS_PROVIDERS,
})
export class BrowserAnimationsModule {
  /**
   * Configures the module based on the specified object.
   *
   * @param config Object used to configure the behavior of the `BrowserAnimationsModule`.
   * @see {@link BrowserAnimationsModuleConfig}
   *
   * @usageNotes
   * When registering the `BrowserAnimationsModule`, you can use the `withConfig`
   * function as follows:
   * ```
   * @NgModule({
   *   imports: [BrowserAnimationsModule.withConfig(config)]
   * })
   * class MyNgModule {}
   * ```
   */
  static withConfig(
    config: BrowserAnimationsModuleConfig,
  ): ModuleWithProviders<BrowserAnimationsModule> {
    return {
      ngModule: BrowserAnimationsModule,
      providers: config.disableAnimations
        ? BROWSER_NOOP_ANIMATIONS_PROVIDERS
        : BROWSER_ANIMATIONS_PROVIDERS,
    };
  }
}

/**
 * Returns the set of dependency-injection providers
 * to enable animations in an application. See [animations guide](guide/animations)
 * to learn more about animations in Angular.
 *
 * @usageNotes
 *
 * The function is useful when you want to enable animations in an application
 * bootstrapped using the `bootstrapApplication` function. In this scenario there
 * is no need to import the `BrowserAnimationsModule` NgModule at all, just add
 * providers returned by this function to the `providers` list as show below.
 *
 * ```typescript
 * bootstrapApplication(RootComponent, {
 *   providers: [
 *     provideAnimations()
 *   ]
 * });
 * ```
 *
 * @publicApi
 */
export function provideAnimations(): Provider[] {
  performanceMarkFeature('NgEagerAnimations');
  // Return a copy to prevent changes to the original array in case any in-place
  // alterations are performed to the `provideAnimations` call results in app code.
  return [...BROWSER_ANIMATIONS_PROVIDERS];
}

/**
 * A null player that must be imported to allow disabling of animations.
 * @publicApi
 */
@NgModule({
  exports: [BrowserModule],
  providers: BROWSER_NOOP_ANIMATIONS_PROVIDERS,
})
export class NoopAnimationsModule {}

/**
 * Returns the set of dependency-injection providers
 * to disable animations in an application. See [animations guide](guide/animations)
 * to learn more about animations in Angular.
 *
 * @usageNotes
 *
 * The function is useful when you want to bootstrap an application using
 * the `bootstrapApplication` function, but you need to disable animations
 * (for example, when running tests).
 *
 * ```typescript
 * bootstrapApplication(RootComponent, {
 *   providers: [
 *     provideNoopAnimations()
 *   ]
 * });
 * ```
 *
 * @publicApi
 */
export function provideNoopAnimations(): Provider[] {
  // Return a copy to prevent changes to the original array in case any in-place
  // alterations are performed to the `provideNoopAnimations` call results in app code.
  return [...BROWSER_NOOP_ANIMATIONS_PROVIDERS];
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

/**
 * @module
 * @description
 * Entry point for all animation APIs of the animation browser package.
 */
export {ANIMATION_MODULE_TYPE} from '@angular/core';
export {
  BrowserAnimationsModule,
  BrowserAnimationsModuleConfig,
  NoopAnimationsModule,
  provideAnimations,
  provideNoopAnimations,
} from './module';

export * from './private_export';


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  CommonModule,
  DOCUMENT,
  XhrFactory,
  ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID,
} from '@angular/common';
import {
  APP_ID,
  ApplicationConfig as ApplicationConfigFromCore,
  ApplicationModule,
  ApplicationRef,
  createPlatformFactory,
  ErrorHandler,
  Inject,
  InjectionToken,
  ModuleWithProviders,
  NgModule,
  NgZone,
  Optional,
  PLATFORM_ID,
  PLATFORM_INITIALIZER,
  platformCore,
  PlatformRef,
  Provider,
  RendererFactory2,
  SkipSelf,
  StaticProvider,
  Testability,
  TestabilityRegistry,
  Type,
  ɵINJECTOR_SCOPE as INJECTOR_SCOPE,
  ɵinternalCreateApplication as internalCreateApplication,
  ɵRuntimeError as RuntimeError,
  ɵsetDocument,
  ɵTESTABILITY as TESTABILITY,
  ɵTESTABILITY_GETTER as TESTABILITY_GETTER,
} from '@angular/core';

import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserGetTestability} from './browser/testability';
import {BrowserXhr} from './browser/xhr';
import {DomRendererFactory2} from './dom/dom_renderer';
import {DomEventsPlugin} from './dom/events/dom_events';
import {EVENT_MANAGER_PLUGINS, EventManager} from './dom/events/event_manager';
import {KeyEventsPlugin} from './dom/events/key_events';
import {SharedStylesHost} from './dom/shared_styles_host';
import {RuntimeErrorCode} from './errors';

/**
 * Set of config options available during the application bootstrap operation.
 *
 * @publicApi
 *
 * @deprecated
 * `ApplicationConfig` has moved, please import `ApplicationConfig` from `@angular/core` instead.
 */
// The below is a workaround to add a deprecated message.
type ApplicationConfig = ApplicationConfigFromCore;
export {ApplicationConfig};

/**
 * Bootstraps an instance of an Angular application and renders a standalone component as the
 * application's root component. More information about standalone components can be found in [this
 * guide](guide/components/importing).
 *
 * @usageNotes
 * The root component passed into this function *must* be a standalone one (should have the
 * `standalone: true` flag in the `@Component` decorator config).
 *
 * ```typescript
 * @Component({
 *   standalone: true,
 *   template: 'Hello world!'
 * })
 * class RootComponent {}
 *
 * const appRef: ApplicationRef = await bootstrapApplication(RootComponent);
 * ```
 *
 * You can add the list of providers that should be available in the application injector by
 * specifying the `providers` field in an object passed as the second argument:
 *
 * ```typescript
 * await bootstrapApplication(RootComponent, {
 *   providers: [
 *     {provide: BACKEND_URL, useValue: 'https://yourdomain.com/api'}
 *   ]
 * });
 * ```
 *
 * The `importProvidersFrom` helper method can be used to collect all providers from any
 * existing NgModule (and transitively from all NgModules that it imports):
 *
 * ```typescript
 * await bootstrapApplication(RootComponent, {
 *   providers: [
 *     importProvidersFrom(SomeNgModule)
 *   ]
 * });
 * ```
 *
 * Note: the `bootstrapApplication` method doesn't include [Testability](api/core/Testability) by
 * default. You can add [Testability](api/core/Testability) by getting the list of necessary
 * providers using `provideProtractorTestingSupport()` function and adding them into the `providers`
 * array, for example:
 *
 * ```typescript
 * import {provideProtractorTestingSupport} from '@angular/platform-browser';
 *
 * await bootstrapApplication(RootComponent, {providers: [provideProtractorTestingSupport()]});
 * ```
 *
 * @param rootComponent A reference to a standalone component that should be rendered.
 * @param options Extra configuration for the bootstrap operation, see `ApplicationConfig` for
 *     additional info.
 * @returns A promise that returns an `ApplicationRef` instance once resolved.
 *
 * @publicApi
 */
export function bootstrapApplication(
  rootComponent: Type<unknown>,
  options?: ApplicationConfig,
): Promise<ApplicationRef> {
  return internalCreateApplication({rootComponent, ...createProvidersConfig(options)});
}

/**
 * Create an instance of an Angular application without bootstrapping any components. This is useful
 * for the situation where one wants to decouple application environment creation (a platform and
 * associated injectors) from rendering components on a screen. Components can be subsequently
 * bootstrapped on the returned `ApplicationRef`.
 *
 * @param options Extra configuration for the application environment, see `ApplicationConfig` for
 *     additional info.
 * @returns A promise that returns an `ApplicationRef` instance once resolved.
 *
 * @publicApi
 */
export function createApplication(options?: ApplicationConfig) {
  return internalCreateApplication(createProvidersConfig(options));
}

function createProvidersConfig(options?: ApplicationConfig) {
  return {
    appProviders: [...BROWSER_MODULE_PROVIDERS, ...(options?.providers ?? [])],
    platformProviders: INTERNAL_BROWSER_PLATFORM_PROVIDERS,
  };
}

/**
 * Returns a set of providers required to setup [Testability](api/core/Testability) for an
 * application bootstrapped using the `bootstrapApplication` function. The set of providers is
 * needed to support testing an application with Protractor (which relies on the Testability APIs
 * to be present).
 *
 * @returns An array of providers required to setup Testability for an application and make it
 *     available for testing using Protractor.
 *
 * @publicApi
 */
export function provideProtractorTestingSupport(): Provider[] {
  // Return a copy to prevent changes to the original array in case any in-place
  // alterations are performed to the `provideProtractorTestingSupport` call results in app
  // code.
  return [...TESTABILITY_PROVIDERS];
}

export function initDomAdapter() {
  BrowserDomAdapter.makeCurrent();
}

export function errorHandler(): ErrorHandler {
  return new ErrorHandler();
}

export function _document(): any {
  // Tell ivy about the global document
  ɵsetDocument(document);
  return document;
}

export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [
  {provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
  {provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true},
  {provide: DOCUMENT, useFactory: _document, deps: []},
];

/**
 * A factory function that returns a `PlatformRef` instance associated with browser service
 * providers.
 *
 * @publicApi
 */
export const platformBrowser: (extraProviders?: StaticProvider[]) => PlatformRef =
  createPlatformFactory(platformCore, 'browser', INTERNAL_BROWSER_PLATFORM_PROVIDERS);

/**
 * Internal marker to signal whether providers from the `BrowserModule` are already present in DI.
 * This is needed to avoid loading `BrowserModule` providers twice. We can't rely on the
 * `BrowserModule` presence itself, since the standalone-based bootstrap just imports
 * `BrowserModule` providers without referencing the module itself.
 */
const BROWSER_MODULE_PROVIDERS_MARKER = new InjectionToken(
  typeof ngDevMode === 'undefined' || ngDevMode ? 'BrowserModule Providers Marker' : '',
);

const TESTABILITY_PROVIDERS = [
  {
    provide: TESTABILITY_GETTER,
    useClass: BrowserGetTestability,
    deps: [],
  },
  {
    provide: TESTABILITY,
    useClass: Testability,
    deps: [NgZone, TestabilityRegistry, TESTABILITY_GETTER],
  },
  {
    provide: Testability, // Also provide as `Testability` for backwards-compatibility.
    useClass: Testability,
    deps: [NgZone, TestabilityRegistry, TESTABILITY_GETTER],
  },
];

const BROWSER_MODULE_PROVIDERS: Provider[] = [
  {provide: INJECTOR_SCOPE, useValue: 'root'},
  {provide: ErrorHandler, useFactory: errorHandler, deps: []},
  {
    provide: EVENT_MANAGER_PLUGINS,
    useClass: DomEventsPlugin,
    multi: true,
    deps: [DOCUMENT, NgZone, PLATFORM_ID],
  },
  {provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true, deps: [DOCUMENT]},
  DomRendererFactory2,
  SharedStylesHost,
  EventManager,
  {provide: RendererFactory2, useExisting: DomRendererFactory2},
  {provide: XhrFactory, useClass: BrowserXhr, deps: []},
  typeof ngDevMode === 'undefined' || ngDevMode
    ? {provide: BROWSER_MODULE_PROVIDERS_MARKER, useValue: true}
    : [],
];

/**
 * Exports required infrastructure for all Angular apps.
 * Included by default in all Angular apps created with the CLI
 * `new` command.
 * Re-exports `CommonModule` and `ApplicationModule`, making their
 * exports and providers available to all apps.
 *
 * @publicApi
 */
@NgModu


e({
  providers: [...BROWSER_MODULE_PROVIDERS, ...TESTABILITY_PROVIDERS],
  exports: [CommonModule, ApplicationModule],
})
export class BrowserModule {
  constructor(
    @Optional()
    @SkipSelf()
    @Inject(BROWSER_MODULE_PROVIDERS_MARKER)
    providersAlreadyPresent: boolean | null,
  ) {
    if ((typeof ngDevMode === 'undefined' || ngDevMode) && providersAlreadyPresent) {
      throw new RuntimeError(
        RuntimeErrorCode.BROWSER_MODULE_ALREADY_LOADED,
        `Providers from the \`BrowserModule\` have already been loaded. If you need access ` +
          `to common directives such as NgIf and NgFor, import the \`CommonModule\` instead.`,
      );
    }
  }
}


Angular Router
=========

Managing state transitions is one of the hardest parts of building applications. This is especially true on the web, where you also need to ensure that the state is reflected in the URL. In addition, we often want to split applications into multiple bundles and load them on demand. Doing this transparently isn’t trivial.

The Angular router is designed to solve these problems. Using the router, you can declaratively specify application state, manage state transitions while taking care of the URL, and load components on demand.

## Guide
Read the dev guide [here](https://angular.io/guide/routing/common-router-tasks).


Implements the Angular Router service , which enables navigation from one view to the next as users perform application tasks.

Defines the `Route` object that maps a URL path to a component, and the `RouterOutlet` directive
that you use to place a routed view in a template, as well as a complete API for configuring, querying, and controlling the router state.

Import `RouterModule` to use the Router service in your app.
For more usage information, see the [Routing and Navigation](guide/routing/common-router-tasks) guide.


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {Component, Injectable, NgModule} from '@angular/core';
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {provideRoutes, Router, RouterModule, ROUTES} from '@angular/router';

@Component({template: '
simple standalone
', standalone: true})
export class SimpleStandaloneComponent {}

@Component({template: '
not standalone
', standalone: false})
export class NotStandaloneComponent {}

@Component({
  template: '<router-outlet></router-outlet>',
  standalone: true,
  imports: [RouterModule],
})
export class RootCmp {}

describe('standalone in Router API', () => {
  describe('loadChildren => routes', () => {
    it('can navigate to and render standalone component', fakeAsync(() => {
      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'lazy',
              component: RootCmp,
              loadChildren: () => [{path: '', component: SimpleStandaloneComponent}],
            },
          ]),
        ],
      });

      const root = TestBed.createComponent(RootCmp);

      const router = TestBed.inject(Router);
      router.navigateByUrl('/lazy');
      advance(root);
      expect(root.nativeElement.innerHTML).toContain('simple standalone');
    }));

    it('throws an error when loadChildren=>routes has a component that is not standalone', fakeAsync(() => {
      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'lazy',
              component: RootCmp,
              loadChildren: () => [{path: 'notstandalone', component: NotStandaloneComponent}],
            },
          ]),
        ],
      });

      const root = TestBed.createComponent(RootCmp);

      const router = TestBed.inject(Router);
      router.navigateByUrl('/lazy/notstandalone');
      expect(() => advance(root)).toThrowError(
        /.*lazy\/notstandalone.*component must be standalone/,
      );
    }));
  });
  describe('route providers', () => {
    it('can provide a guard on a route', fakeAsync(() => {
      @Injectable()
      class ConfigurableGuard {
        static canActivateValue = false;
        canActivate() {
          return ConfigurableGuard.canActivateValue;
        }
      }

      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'simple',
              providers: [ConfigurableGuard],
              canActivate: [ConfigurableGuard],
              component: SimpleStandaloneComponent,
            },
          ]),
        ],
      });
      const root = TestBed.createComponent(RootCmp);

      ConfigurableGuard.canActivateValue = false;
      const router = TestBed.inject(Router);
      router.navigateByUrl('/simple');
      advance(root);
      expect(root.nativeElement.innerHTML).not.toContain('simple standalone');
      expect(router.url).not.toContain('simple');

      ConfigurableGuard.canActivateValue = true;
      router.navigateByUrl('/simple');
      advance(root);
      expect(root.nativeElement.innerHTML).toContain('simple standalone');
      expect(router.url).toContain('simple');
    }));

    it('can inject provider on a route into component', fakeAsync(() => {
      @Injectable()
      class Service {
        value = 'my service';
      }

      @Component({
        template: `{{service.value}}`,
        standalone: false,
      })
      class MyComponent {
        constructor(readonly service: Service) {}
      }

      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([{path: 'home', providers: [Service], component: MyComponent}]),
        ],
        declarations: [MyComponent],
      });
      const root = TestBed.createComponent(RootCmp);

      const router = TestBed.inject(Router);
      router.navigateByUrl('/home');
      advance(root);
      expect(root.nativeElement.innerHTML).toContain('my service');
      expect(router.url).toContain('home');
    }));

    it('can not inject provider in lazy loaded ngModule from component on same level', fakeAsync(() => {
      @Injectable()
      class Service {
        value = 'my service';
      }

      @NgModule({providers: [Service]})
      class LazyModule {}

      @Component({
        template: `{{service.value}}`,
        standalone: false,
      })
      class MyComponent {
        constructor(readonly service: Service) {}
      }

      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {path: 'home', loadChildren: () => LazyModule, component: MyComponent},
          ]),
        ],
        declarations: [MyComponent],
      });
      const root = TestBed.createComponent(RootCmp);

      const router = TestBed.inject(Router);
      router.navigateByUrl('/home');
      expect(() => advance(root)).toThrowError();
    }));

    it('component from lazy module can inject provider from parent route', fakeAsync(() => {
      @Injectable()
      class Service {
        value = 'my service';
      }

      @Component({
        template: `{{service.value}}`,
        standalone: false,
      })
      class MyComponent {
        constructor(readonly service: Service) {}
      }
      @NgModule({
        providers: [Service],
        declarations: [MyComponent],
        imports: [RouterModule.forChild([{path: '', component: MyComponent}])],
      })
      class LazyModule {}

      TestBed.configureTestingModule({
        imports: [RouterModule.forRoot([{path: 'home', loadChildren: () => LazyModule}])],
      });
      const root = TestBed.createComponent(RootCmp);

      const router = TestBed.inject(Router);
      router.navigateByUrl('/home');
      advance(root);
      expect(root.nativeElement.innerHTML).toContain('my service');
    }));

    it('gets the correct injector for guards and components when combining lazy modules and route providers', fakeAsync(() => {
      const canActivateLog: string[] = [];
      abstract class ServiceBase {
        abstract name: string;
        canActivate() {
          canActivateLog.push(this.name);
          return true;
        }
      }

      @Injectable()
      class Service1 extends ServiceBase {
        override name = 'service1';
      }

      @Injectable()
      class Service2 extends ServiceBase {
        override name = 'service2';
      }

      @Injectable()
      class Service3 extends ServiceBase {
        override name = 'service3';
      }

      @Component({
        template: `parent<router-outlet></router-outlet>`,
        standalone: false,
      })
      class ParentCmp {
        constructor(readonly service: ServiceBase) {}
      }
      @Component({
        template: `child`,
        standalone: false,
      })
      class ChildCmp {
        constructor(readonly service: ServiceBase) {}
      }

      @Component({
        template: `child2`,
        standalone: false,
      })
      class ChildCmp2 {
        constructor(readonly service: ServiceBase) {}
      }
      @NgModule({
        providers: [{provide: ServiceBase, useClass: Service2}],
        declarations: [ChildCmp, ChildCmp2],
        imports: [
          RouterModule.forChild([
            {
              path: '',
              // This component and guard should get Service2 since it's provided in this module
              component: ChildCmp,
              canActivate: [ServiceBase],
            },
            {
              path: 'child2',
              providers: [{provide: ServiceBase, useFactory: () => new Service3()}],
              // This component and guard should get Service3 since it's provided on this route
              component: ChildCmp2,
              canActivate: [ServiceBase],
            },
          ]),
        ],
      })
      class LazyModule {}

      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'home',
              // This component and guard should get Service1 since it's provided on this route
              component: ParentCmp,
              canActivate: [ServiceBase],
              providers: [{provide: ServiceBase, useFactory: () => new Service1()}],
              loadChildren: () => LazyModule,
            },
          ]),
        ],
        declarations: [ParentCmp],
      });
      const root = TestBed.createComponent(RootCmp);

      const router = TestBed.inject(Router);
      router.navigateByUrl('/home');
      advance(root);
      expect(canActivateLog).toEqual(['service1', 'service2']);
      expect(
        root.debugElement.query(By.directive(ParentCmp)).componentInstance.service.name,
      ).toEqual('service1');
      expect(
        root.debugElement.query(By.directive(ChildCmp)).componentInstance.service.name,
      ).toEqual('service2');

      router.navigateByUrl('/home/child2');
      advance(root);
      expect(canActivateLog).toEqual(['service1', 'service2', 'service3']);
      expect(
        root.debugElement.query(By.directive(ChildCmp2)).componentInstance.service.name,
      ).toEqual('service3');
    }));
  });


describe('loadComponent', () => {
    it('does not load component when canActivate returns false', fakeAsync(() => {
      const loadComponentSpy = jasmine.createSpy();
      @Injectable({providedIn: 'root'})
      class Guard {
        canActivate() {
          return false;
        }
      }

      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'home',
              loadComponent: loadComponentSpy,
              canActivate: [Guard],
            },
          ]),
        ],
      });

      TestBed.inject(Router).navigateByUrl('/home');
      tick();
      expect(loadComponentSpy).not.toHaveBeenCalled();
    }));

    it('loads and renders lazy component', fakeAsync(() => {
      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'home',
              loadComponent: () => SimpleStandaloneComponent,
            },
          ]),
        ],
      });

      const root = TestBed.createComponent(RootCmp);
      TestBed.inject(Router).navigateByUrl('/home');
      advance(root);
      expect(root.nativeElement.innerHTML).toContain('simple standalone');
    }));

    it('throws error when loadComponent is not standalone', fakeAsync(() => {
      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'home',
              loadComponent: () => NotStandaloneComponent,
            },
          ]),
        ],
      });

      const root = TestBed.createComponent(RootCmp);
      TestBed.inject(Router).navigateByUrl('/home');
      expect(() => advance(root)).toThrowError(/.*home.*component must be standalone/);
    }));

    it('throws error when loadComponent is used with a module', fakeAsync(() => {
      @NgModule()
      class LazyModule {}

      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'home',
              loadComponent: () => LazyModule,
            },
          ]),
        ],
      });

      const root = TestBed.createComponent(RootCmp);
      TestBed.inject(Router).navigateByUrl('/home');
      expect(() => advance(root)).toThrowError(/.*home.*Use 'loadChildren' instead/);
    }));
  });
  describe('default export unwrapping', () => {
    it('should work for loadComponent', async () => {
      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'home',
              loadComponent: () => import('./default_export_component'),
            },
          ]),
        ],
      });

      const root = TestBed.createComponent(RootCmp);
      await TestBed.inject(Router).navigateByUrl('/home');
      root.detectChanges();

      expect(root.nativeElement.innerHTML).toContain('default exported');
    });

    it('should work for loadChildren', async () => {
      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([
            {
              path: 'home',
              loadChildren: () => import('./default_export_routes'),
            },
          ]),
        ],
      });

      const root = TestBed.createComponent(RootCmp);
      await TestBed.inject(Router).navigateByUrl('/home');
      root.detectChanges();

      expect(root.nativeElement.innerHTML).toContain('default exported');
    });
  });
});

describe('provideRoutes', () => {
  it('warns if provideRoutes is used without provideRouter, RouterModule, or RouterModule.forRoot', () => {
    spyOn(console, 'warn');
    TestBed.configureTestingModule({providers: [provideRoutes([])]});
    TestBed.inject(ROUTES);
    expect(console.warn).toHaveBeenCalled();
  });
});

function advance(fixture: ComponentFixture<unknown>) {
  tick();
  fixture.detectChanges();
}


describe('data', async () => {
    it('should set static data', async () => {
      const s = await recognize([{path: 'a', data: {one: 1}, component: ComponentA}], 'a');
      const r: ActivatedRouteSnapshot = s.root.firstChild!;
      expect(r.data).toEqual({one: 1});
    });

    it("should inherit componentless route's data", async () => {
      const s = await recognize(
        [
          {
            path: 'a',
            data: {one: 1},
            children: [{path: 'b', data: {two: 2}, component: ComponentB}],
          },
        ],
        'a/b',
      );
      const r: ActivatedRouteSnapshot = s.root.firstChild!.firstChild!;
      expect(r.data).toEqual({one: 1, two: 2});
    });

    it("should not inherit route's data if it has component", async () => {
      const s = await recognize(
        [
          {
            path: 'a',
            component: ComponentA,
            data: {one: 1},
            children: [{path: 'b', data: {two: 2}, component: ComponentB}],
          },
        ],
        'a/b',
      );
      const r: ActivatedRouteSnapshot = s.root.firstChild!.firstChild!;
      expect(r.data).toEqual({two: 2});
    });

    it("should not inherit route's data if it has loadComponent", async () => {
      const s = await recognize(
        [
          {
            path: 'a',
            loadComponent: () => ComponentA,
            data: {one: 1},
            children: [{path: 'b', data: {two: 2}, component: ComponentB}],
          },
        ],
        'a/b',
      );
      const r: ActivatedRouteSnapshot = s.root.firstChild!.firstChild!;
      expect(r.data).toEqual({two: 2});
    });

    it("should inherit route's data if paramsInheritanceStrategy is 'always'", async () => {
      const s = await recognize(
        [
          {
            path: 'a',
            component: ComponentA,
            data: {one: 1},
            children: [{path: 'b', data: {two: 2}, component: ComponentB}],
          },
        ],
        'a/b',
        'always',
      );
      const r: ActivatedRouteSnapshot = s.root.firstChild!.firstChild!;
      expect(r.data).toEqual({one: 1, two: 2});
    });

    it('should set resolved data', async () => {
      const s = await recognize(
        [{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}],
        'a',
      );
      const r: any = s.root.firstChild!;
      expect(r._resolve).toEqual({one: 'some-token'});
    });
  });


it('with overlapping loads from navigation and the preloader', fakeAsync(() => {
      const preloader = TestBed.inject(RouterPreloader);
      const router = TestBed.inject(Router);
      router.events.subscribe((e) => {
        if (e instanceof RouteConfigLoadEnd || e instanceof RouteConfigLoadStart) {
          events.push(e);
        }
      });

      lazyLoadChildrenSpy.and.returnValue(of(LoadedModule1));
      subLoadChildrenSpy.and.returnValue(of(LoadedModule2).pipe(delay(5)));
      preloader.preload().subscribe((x) => {});
      tick();

      // Load the out modules at start of test and ensure it and only
      // it is loaded
      delayLoadUnPaused.next(['lazymodule']);
      tick();
      expect(lazyLoadChildrenSpy).toHaveBeenCalledTimes(1);
      expect(events.map((e) => e.toString())).toEqual([
        'RouteConfigLoadStart(path: lazy)',
        'RouteConfigLoadEnd(path: lazy)',
      ]);

      // Cause the load from router to start (has 5 tick delay)
      router.navigateByUrl('/lazy/sub/LoadedModule2');
      tick(); // T1
      // Cause the load from preloader to start
      delayLoadUnPaused.next(['lazymodule', 'submodule']);
      tick(); // T2

      expect(lazyLoadChildrenSpy).toHaveBeenCalledTimes(1);
      expect(subLoadChildrenSpy).toHaveBeenCalledTimes(1);
      tick(5); // T2 to T7 enough time for mutiple loads to finish

      expect(subLoadChildrenSpy).toHaveBeenCalledTimes(1);
      expect(events.map((e) => e.toString())).toEqual([
        'RouteConfigLoadStart(path: lazy)',
        'RouteConfigLoadEnd(path: lazy)',
        'RouteConfigLoadStart(path: sub)',
        'RouteConfigLoadEnd(path: sub)',
      ]);
    }));

    it('cope with factory fail from broken modules', fakeAsync(() => {
      const preloader = TestBed.inject(RouterPreloader);
      const router = TestBed.inject(Router);
      router.events.subscribe((e) => {
        if (e instanceof RouteConfigLoadEnd || e instanceof RouteConfigLoadStart) {
          events.push(e);
        }
      });

      class BrokenModuleFactory extends NgModuleFactory<unknown> {
        override moduleType: Type<unknown> = LoadedModule1;
        constructor() {
          super();
        }
        override create(_parentInjector: Injector | null): NgModuleRef<unknown> {
          throw 'Error: Broken module';
        }
      }

      lazyLoadChildrenSpy.and.returnValue(of(new BrokenModuleFactory()));
      preloader.preload().subscribe((x) => {});
      tick();
      expect(lazyLoadChildrenSpy).toHaveBeenCalledTimes(0);

      router.navigateByUrl('/lazy/LoadedModule1').catch((reason) => {
        expect(reason).toEqual('Error: Broken module');
      });
      tick();

      expect(lazyLoadChildrenSpy).toHaveBeenCalledTimes(1);
      lazyLoadChildrenSpy.and.returnValue(of(LoadedModule1));
      router.navigateByUrl('/lazy/LoadedModule1').catch(() => {
        fail('navigation should not throw');
      });
      tick();

      expect(lazyLoadChildrenSpy).toHaveBeenCalledTimes(2);
      expect(subLoadChildrenSpy).toHaveBeenCalledTimes(0);
      expect(events.map((e) => e.toString())).toEqual([
        'RouteConfigLoadStart(path: lazy)',
        'RouteConfigLoadEnd(path: lazy)',
        'RouteConfigLoadStart(path: lazy)',
        'RouteConfigLoadEnd(path: lazy)',
      ]);
    }));
  });

  describe('should ignore errors', () => {
    @NgModule({
      declarations: [LazyLoadedCmp],
      imports: [RouterModule.forChild([{path: 'LoadedModule1', component: LazyLoadedCmp}])],
    })
    class LoadedModule {}

    beforeEach(() => {
      TestBed.configureTestingModule({
        providers: [
          {
            provide: ROUTES,
            multi: true,
            useValue: [
              {path: 'lazy1', loadChildren: jasmine.createSpy('expected1')},
              {path: 'lazy2', loadChildren: () => LoadedModule},
            ],
          },
          {provide: PreloadingStrategy, useExisting: PreloadAllModules},
        ],
      });
    });

    it('should work', fakeAsync(
      inject([RouterPreloader, Router], (preloader: RouterPreloader, router: Router) => {
        preloader.preload().subscribe(() => {});

        tick();

        const c = router.config;
        expect(getLoadedRoutes(c[0])).not.toBeDefined();
        expect(getLoadedRoutes(c[1])).toBeDefined();
      }),
    ));
  });

  describe('should copy loaded configs', () => {
    const configs = [{path: 'LoadedModule1', component: LazyLoadedCmp}];
    @NgModule({
      declarations: [LazyLoadedCmp],
      providers: [{provide: ROUTES, multi: true, useValue: configs}],
    })
    class LoadedModule {}

    beforeEach(() => {
      TestBed.configureTestingModule({
        providers: [
          provideLocationMocks(),
          provideRouter(
            [{path: 'lazy1', loadChildren: () => LoadedModule}],
            withPreloading(PreloadAllModules),
          ),
        ],
      });
    });

    it('should work', fakeAsync(
      inject([RouterPreloader, Router], (preloader: RouterPreloader, router: Router) => {
        preloader.preload().subscribe(() => {});

        tick();

        const c = router.config;
        expect(getLoadedRoutes(c[0])).toBeDefined();
        expect(getLoadedRoutes(c[0])).not.toBe(configs);
        expect(getLoadedRoutes(c[0])![0]).not.toBe(configs[0]);
        expect(getLoadedRoutes(c[0])![0].component).toBe(configs[0].component);
      }),
    ));
  });

  describe("should work with lazy loaded modules that don't provide RouterModule.forChild()", () => {
    @NgModule({
      declarations: [LazyLoadedCmp],
      providers: [
        {
          provide: ROUTES,
          multi: true,
          useValue: [{path: 'LoadedModule1', component: LazyLoadedCmp}],
        },
      ],
    })
    class LoadedModule {}

    @NgModule({})
    class EmptyModule {}

    beforeEach(() => {
      TestBed.configureTestingModule({
        providers: [
          provideLocationMocks(),
          provideRouter(
            [{path: 'lazyEmptyModule', loadChildren: () => EmptyModule}],
            withPreloading(PreloadAllModules),
          ),
        ],
      });
    });

    it('should work', fakeAsync(
      inject([RouterPreloader], (preloader: RouterPreloader) => {
        preloader.preload().subscribe();
      }),
    ));
  });


ribe('guards', () => {
      describe('CanActivate', () => {
        describe('should not activate a route when CanActivate returns false', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              const recordedEvents: Event[] = [];
              router.events.forEach((e) => recordedEvents.push(e));

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canActivate: ['alwaysFalse']},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);

              expect(location.path()).toEqual('');
              expectEvents(recordedEvents, [
                [NavigationStart, '/team/22'],
                [RoutesRecognized, '/team/22'],
                [GuardsCheckStart, '/team/22'],
                [ChildActivationStart],
                [ActivationStart],
                [GuardsCheckEnd, '/team/22'],
                [NavigationCancel, '/team/22'],
              ]);
              expect((recordedEvents[5] as GuardsCheckEnd).shouldActivate).toBe(false);
            }),
          ));
        });

        describe('should not activate a route when CanActivate returns false (componentless route)', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {
                  path: 'parent',
                  canActivate: ['alwaysFalse'],
                  children: [{path: 'team/:id', component: TeamCmp}],
                },
              ]);

              router.navigateByUrl('parent/team/22');
              advance(fixture);

              expect(location.path()).toEqual('');
            }),
          ));
        });

        describe('should activate a route when CanActivate returns true', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [
                {
                  provide: 'alwaysTrue',
                  useValue: (a: ActivatedRouteSnapshot, s: RouterStateSnapshot) => true,
                },
              ],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canActivate: ['alwaysTrue']},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');
            }),
          ));
        });

        describe('should work when given a class', () => {
          class AlwaysTrue {
            canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
              return true;
            }
          }

          beforeEach(() => {
            TestBed.configureTestingModule({providers: [AlwaysTrue]});
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue]},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);

              expect(location.path()).toEqual('/team/22');
            }),
          ));
        });

        describe('should work when returns an observable', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [
                {
                  provide: 'CanActivate',
                  useValue: (a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                    return new Observable<boolean>((observer) => {
                      observer.next(false);
                    });
                  },
                },
              ],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('');
            }),
          ));
        });

        describe('should work when returns a promise', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [
                {
                  provide: 'CanActivate',
                  useValue: (a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                    if (a.params['id'] === '22') {
                      return Promise.resolve(true);
                    } else {
                      return Promise.resolve(false);
                    }
                  },
                },
              ],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canActivate: ['CanActivate']},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');

              router.navigateByUrl('/team/33');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');
            }),
          ));
        });

        describe('should reset the location when cancelling a navigation', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [
                {
                  provide: 'alwaysFalse',
                  useValue: (a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                    return false;
                  },
                },
                {provide: LocationStrategy, useClass: HashLocationStrategy},
              ],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'one', component: SimpleCmp},
                {path: 'two', component: SimpleCmp, canActivate: ['alwaysFalse']},
              ]);

              router.navigateByUrl('/one');
              advance(fixture);
              expect(location.path()).toEqual('/one');

              location.go('/two');
              location.historyGo(0);
              advance(fixture);
              expect(location.path()).toEqual('/one');
            }),
          ));
        });

        describe('should redirect to / when guard returns false', () => {
          beforeEach(() =>
            TestBed.configureTestingModule({
              providers: [
                {
                  provide: 'returnFalseAndNavigate',
                  useFactory: (router: Router) => () => {
                    router.navigate(['/']);
                    return false;
                  },
                  deps: [Router],
                },
              ],
            }),
          );

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              router.resetConfig([
                {
                  path: '',
                  component: SimpleCmp,
                },
                {path: 'one', component: RouteCmp, canActivate: ['returnFalseAndNavigate']},
              ]);

              const fixture = TestBed.createComponent(RootCmp);
              router.navigateByUrl('/one');
              advance(fixture);
              expect(location.path()).toEqual('');
              expect(fixture.nativeElement).toHaveText('simple');
            }),
          ));
        });


ribe('CanDeactivate', () => {
        let log: any;

        beforeEach(() => {
          log = [];

          TestBed.configureTestingModule({
            providers: [
              {
                provide: 'CanDeactivateParent',
                useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                  return a.params['id'] === '22';
                },
              },
              {
                provide: 'CanDeactivateTeam',
                useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                  return c.route.snapshot.params['id'] === '22';
                },
              },
              {
                provide: 'CanDeactivateUser',
                useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                  return a.params['name'] === 'victor';
                },
              },
              {
                provide: 'RecordingDeactivate',
                useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                  log.push({path: a.routeConfig!.path, component: c});
                  return true;
                },
              },
              {
                provide: 'alwaysFalse',
                useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                  return false;
                },
              },
              {
                provide: 'alwaysFalseAndLogging',
                useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                  log.push('called');
                  return false;
                },
              },
              {
                provide: 'alwaysFalseWithDelayAndLogging',
                useValue: () => {
                  log.push('called');
                  let resolve: (result: boolean) => void;
                  const promise = new Promise((res) => (resolve = res));
                  setTimeout(() => resolve(false), 0);
                  return promise;
                },
              },
              {
                provide: 'canActivate_alwaysTrueAndLogging',
                useValue: () => {
                  log.push('canActivate called');
                  return true;
                },
              },
            ],
          });
        });

        describe('should not deactivate a route when CanDeactivate returns false', () => {
          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivateTeam']},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');

              let successStatus: boolean = false;
              router.navigateByUrl('/team/33')!.then((res) => (successStatus = res));
              advance(fixture);
              expect(location.path()).toEqual('/team/33');
              expect(successStatus).toEqual(true);

              let canceledStatus: boolean = false;
              router.navigateByUrl('/team/44')!.then((res) => (canceledStatus = res));
              advance(fixture);
              expect(location.path()).toEqual('/team/33');
              expect(canceledStatus).toEqual(false);
            }),
          ));

          it('works with componentless routes', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {
                  path: 'grandparent',
                  canDeactivate: ['RecordingDeactivate'],
                  children: [
                    {
                      path: 'parent',
                      canDeactivate: ['RecordingDeactivate'],
                      children: [
                        {
                          path: 'child',
                          canDeactivate: ['RecordingDeactivate'],
                          children: [
                            {
                              path: 'simple',
                              component: SimpleCmp,
                              canDeactivate: ['RecordingDeactivate'],
                            },
                          ],
                        },
                      ],
                    },
                  ],
                },
                {path: 'simple', component: SimpleCmp},
              ]);

              router.navigateByUrl('/grandparent/parent/child/simple');
              advance(fixture);
              expect(location.path()).toEqual('/grandparent/parent/child/simple');

              router.navigateByUrl('/simple');
              advance(fixture);

              const child = fixture.debugElement.children[1].componentInstance;

              expect(log.map((a: any) => a.path)).toEqual([
                'simple',
                'child',
                'parent',
                'grandparent',
              ]);
              expect(log[0].component instanceof SimpleCmp).toBeTruthy();
              [1, 2, 3].forEach((i) => expect(log[i].component).toBeNull());
              expect(child instanceof SimpleCmp).toBeTruthy();
              expect(child).not.toBe(log[0].component);
            }),
          ));

          it('works with aux routes', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {
                  path: 'two-outlets',
                  component: TwoOutletsCmp,
                  children: [
                    {path: 'a', component: BlankCmp},
                    {
                      path: 'b',
                      canDeactivate: ['RecordingDeactivate'],
                      component: SimpleCmp,
                      outlet: 'aux',
                    },
                  ],
                },
              ]);

              router.navigateByUrl('/two-outlets/(a//aux:b)');
              advance(fixture);
              expect(location.path()).toEqual('/two-outlets/(a//aux:b)');

              router.navigate(['two-outlets', {outlets: {aux: null}}]);
              advance(fixture);

              expect(log.map((a: any) => a.path)).toEqual(['b']);
              expect(location.path()).toEqual('/two-outlets/a');
            }),
          ));

          it('works with a nested route', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {
                  path: 'team/:id',
                  component: TeamCmp,
                  children: [
                    {path: '', pathMatch: 'full', component: SimpleCmp},
                    {path: 'user/:name', component: UserCmp, canDeactivate: ['CanDeactivateUser']},
                  ],
                },
              ]);

              router.navigateByUrl('/team/22/user/victor');
              advance(fixture);

              // this works because we can deactivate victor
              router.navigateByUrl('/team/33');
              advance(fixture);
              expect(location.path()).toEqual('/team/33');

              router.navigateByUrl('/team/33/user/fedor');
              advance(fixture);

              // this doesn't work cause we cannot deactivate fedor
              router.navigateByUrl('/team/44');
              advance(fixture);
              expect(location.path()).toEqual('/team/33/user/fedor');
            }),
          ));
        });

        it('should use correct component to deactivate forChild route', fakeAsync(
          inject([Router], (router: Router) => {
            @Component({
              selector: 'admin',
              template: '',
              standalone: false,
            })
            class AdminComponent {}

            @NgModule({
              declarations: [AdminComponent],
              imports: [
                RouterModule.forChild([
                  {
                    path: '',
                    component: AdminComponent,
                    canDeactivate: ['RecordingDeactivate'],
                  },
                ]),
              ],
            })
            class LazyLoadedModule {}

            const fixture = createRoot(router, RootCmp);

            router.resetConfig([
              {
                path: 'a',
                component: WrapperCmp,
                children: [{path: '', pathMatch: 'full', loadChildren: () => LazyLoadedModule}],
              },
              {path: 'b', component: SimpleCmp},
            ]);

            router.navigateByUrl('/a');
            advance(fixture);
            router.navigateByUrl('/b');
            advance(fixture);

            expect(log[0].component).toBeInstanceOf(AdminComponent);
          }),
        ));

        it('should not create a route state if navigation is canceled', fakeAsync(
          inject([Router, Location], (router: Router, location: Location) => {
            const fixture = createRoot(router, RootCmp);

            router.resetConfig([
              {
                path: 'main',
                component: TeamCmp,
                children: [
                  {path: 'component1', component: SimpleCmp, canDeactivate: ['alwaysFalse']},
                  {path: 'component2', component: SimpleCmp},
                ],
              },
            ]);

            router.navigateByUrl('/main/component1');
            advance(fixture);

            router.navigateByUrl('/main/component2');
            advance(fixture);

            const teamCmp = fixture.debugElement.children[1].componentInstance;
            expect(teamCmp.route.firstChild.url.value[0].path).toEqual('component1');
            expect(location.path()).toEqual('/main/component1');
          }),
        ));


should not run CanActivate when CanDeactivate returns false', fakeAsync(
          inject([Router, Location], (router: Router, location: Location) => {
            const fixture = createRoot(router, RootCmp);

            router.resetConfig([
              {
                path: 'main',
                component: TeamCmp,
                children: [
                  {
                    path: 'component1',
                    component: SimpleCmp,
                    canDeactivate: ['alwaysFalseWithDelayAndLogging'],
                  },
                  {
                    path: 'component2',
                    component: SimpleCmp,
                    canActivate: ['canActivate_alwaysTrueAndLogging'],
                  },
                ],
              },
            ]);

            router.navigateByUrl('/main/component1');
            advance(fixture);
            expect(location.path()).toEqual('/main/component1');

            router.navigateByUrl('/main/component2');
            advance(fixture);
            expect(location.path()).toEqual('/main/component1');
            expect(log).toEqual(['called']);
          }),
        ));

        it('should call guards every time when navigating to the same url over and over again', fakeAsync(
          inject([Router, Location], (router: Router, location: Location) => {
            const fixture = createRoot(router, RootCmp);

            router.resetConfig([
              {path: 'simple', component: SimpleCmp, canDeactivate: ['alwaysFalseAndLogging']},
              {path: 'blank', component: BlankCmp},
            ]);

            router.navigateByUrl('/simple');
            advance(fixture);

            router.navigateByUrl('/blank');
            advance(fixture);
            expect(log).toEqual(['called']);
            expect(location.path()).toEqual('/simple');

            router.navigateByUrl('/blank');
            advance(fixture);
            expect(log).toEqual(['called', 'called']);
            expect(location.path()).toEqual('/simple');
          }),
        ));

        describe('next state', () => {
          let log: string[];

          class ClassWithNextState {
            canDeactivate(
              component: TeamCmp,
              currentRoute: ActivatedRouteSnapshot,
              currentState: RouterStateSnapshot,
              nextState: RouterStateSnapshot,
            ): boolean {
              log.push(currentState.url, nextState.url);
              return true;
            }
          }

          beforeEach(() => {
            log = [];
            TestBed.configureTestingModule({
              providers: [
                ClassWithNextState,
                {
                  provide: 'FunctionWithNextState',
                  useValue: (
                    cmp: any,
                    currentRoute: ActivatedRouteSnapshot,
                    currentState: RouterStateSnapshot,
                    nextState: RouterStateSnapshot,
                  ) => {
                    log.push(currentState.url, nextState.url);
                    return true;
                  },
                },
              ],
            });
          });

          it('should pass next state as the 4 argument when guard is a class', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {
                  path: 'team/:id',
                  component: TeamCmp,
                  canDeactivate: [
                    (
                      component: TeamCmp,
                      currentRoute: ActivatedRouteSnapshot,
                      currentState: RouterStateSnapshot,
                      nextState: RouterStateSnapshot,
                    ) =>
                      coreInject(ClassWithNextState).canDeactivate(
                        component,
                        currentRoute,
                        currentState,
                        nextState,
                      ),
                  ],
                },
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');

              router.navigateByUrl('/team/33');
              advance(fixture);
              expect(location.path()).toEqual('/team/33');
              expect(log).toEqual(['/team/22', '/team/33']);
            }),
          ));

          it('should pass next state as the 4 argument when guard is a function', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canDeactivate: ['FunctionWithNextState']},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');

              router.navigateByUrl('/team/33');
              advance(fixture);
              expect(location.path()).toEqual('/team/33');
              expect(log).toEqual(['/team/22', '/team/33']);
            }),
          ));
        });

        describe('should work when given a class', () => {
          class AlwaysTrue {
            canDeactivate(): boolean {
              return true;
            }
          }

          beforeEach(() => {
            TestBed.configureTestingModule({providers: [AlwaysTrue]});
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {
                  path: 'team/:id',
                  component: TeamCmp,
                  canDeactivate: [() => coreInject(AlwaysTrue).canDeactivate()],
                },
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');

              router.navigateByUrl('/team/33');
              advance(fixture);
              expect(location.path()).toEqual('/team/33');
            }),
          ));
        });

        describe('should work when returns an observable', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [
                {
                  provide: 'CanDeactivate',
                  useValue: (c: TeamCmp, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
                    return new Observable<boolean>((observer) => {
                      observer.next(false);
                    });
                  },
                },
              ],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate']},
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');

              router.navigateByUrl('/team/33');
              advance(fixture);
              expect(location.path()).toEqual('/team/22');
            }),
          ));
        });
      });

      describe('CanActivateChild', () => {
        describe('should be invoked when activating a child', () => {
          beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [
                {
                  provide: 'alwaysFalse',
                  useValue: (a: any, b: any) => a.paramMap.get('id') === '22',
                },
              ],
            });
          });

          it('works', fakeAsync(
            inject([Router, Location], (router: Router, location: Location) => {
              const fixture = createRoot(router, RootCmp);

              router.resetConfig([
                {
                  path: '',
                  canActivateChild: ['alwaysFalse'],
                  children: [{path: 'team/:id', component: TeamCmp}],
                },
              ]);

              router.navigateByUrl('/team/22');
              advance(fixture);

              expect(location.path()).toEqual('/team/22');

              router.navigateByUrl('/team/33')!.catch(() => {});
              advance(fixture);

              expect(location.path()).toEqual('/team/22');
            }),
          ));
        });

        it('should find the guard provided in lazy loaded module', fakeAsync(
          inject([Router, Location], (router: Router, location: Location) => {
            @Component({
              selector: 'admin',
              template: '<router-outlet></router-outlet>',
              standalone: false,
            })
            class AdminComponent {}

            @Component({
              selector: 'lazy',
              template: 'lazy-loaded',
              standalone: false,
            })
            class LazyLoadedComponent {}

            @NgModule({
              declarations: [AdminComponent, LazyLoadedComponent],
              imports: [
                RouterModule.forChild([
                  {
                    path: '',
                    component: AdminComponent,
                    children: [
                      {
                        path: '',
                        canActivateChild: ['alwaysTrue'],
                        children: [{path: '', component: LazyLoadedComponent}],
                      },
                    ],
                  },
                ]),
              ],
              providers: [{provide: 'alwaysTrue', useValue: () => true}],
            })
            class LazyLoadedModule {}

            const fixture = createRoot(router, RootCmp);

            router.resetConfig([{path: 'admin', loadChildren: () => LazyLoadedModule}]);

            router.navigateByUrl('/admin');
            advance(fixture);

            expect(location.path()).toEqual('/admin');
            expect(fixture.nativeElement).toHaveText('lazy-loaded');
          }),
        ));
      });


ribe('lazy loading', () => {
      it('works', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-parent [<router-outlet></router-outlet>]',
            standalone: false,
          })
          class ParentLazyLoadedComponent {}

          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-child',
            standalone: false,
          })
          class ChildLazyLoadedComponent {}

          @NgModule({
            declarations: [ParentLazyLoadedComponent, ChildLazyLoadedComponent],
            imports: [
              RouterModule.forChild([
                {
                  path: 'loaded',
                  component: ParentLazyLoadedComponent,
                  children: [{path: 'child', component: ChildLazyLoadedComponent}],
                },
              ]),
            ],
          })
          class LoadedModule {}

          const fixture = createRoot(router, RootCmp);

          router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);

          router.navigateByUrl('/lazy/loaded/child');
          advance(fixture);

          expect(location.path()).toEqual('/lazy/loaded/child');
          expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
        }),
      ));

      it('should have 2 injector trees: module and element', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'parent[<router-outlet></router-outlet>]',
            viewProviders: [{provide: 'shadow', useValue: 'from parent component'}],
            standalone: false,
          })
          class Parent {}

          @Component({
            selector: 'lazy',
            template: 'child',
            standalone: false,
          })
          class Child {}

          @NgModule({
            declarations: [Parent],
            imports: [
              RouterModule.forChild([
                {
                  path: 'parent',
                  component: Parent,
                  children: [{path: 'child', loadChildren: () => ChildModule}],
                },
              ]),
            ],
            providers: [
              {provide: 'moduleName', useValue: 'parent'},
              {provide: 'fromParent', useValue: 'from parent'},
            ],
          })
          class ParentModule {}

          @NgModule({
            declarations: [Child],
            imports: [RouterModule.forChild([{path: '', component: Child}])],
            providers: [
              {provide: 'moduleName', useValue: 'child'},
              {provide: 'fromChild', useValue: 'from child'},
              {provide: 'shadow', useValue: 'from child module'},
            ],
          })
          class ChildModule {}

          const fixture = createRoot(router, RootCmp);
          router.resetConfig([{path: 'lazy', loadChildren: () => ParentModule}]);
          router.navigateByUrl('/lazy/parent/child');
          advance(fixture);
          expect(location.path()).toEqual('/lazy/parent/child');
          expect(fixture.nativeElement).toHaveText('parent[child]');

          const pInj = fixture.debugElement.query(By.directive(Parent)).injector!;
          const cInj = fixture.debugElement.query(By.directive(Child)).injector!;

          expect(pInj.get('moduleName')).toEqual('parent');
          expect(pInj.get('fromParent')).toEqual('from parent');
          expect(pInj.get(Parent)).toBeInstanceOf(Parent);
          expect(pInj.get('fromChild', null)).toEqual(null);
          expect(pInj.get(Child, null)).toEqual(null);

          expect(cInj.get('moduleName')).toEqual('child');
          expect(cInj.get('fromParent')).toEqual('from parent');
          expect(cInj.get('fromChild')).toEqual('from child');
          expect(cInj.get(Parent)).toBeInstanceOf(Parent);
          expect(cInj.get(Child)).toBeInstanceOf(Child);
          // The child module can not shadow the parent component
          expect(cInj.get('shadow')).toEqual('from parent component');

          const pmInj = pInj.get(NgModuleRef).injector;
          const cmInj = cInj.get(NgModuleRef).injector;

          expect(pmInj.get('moduleName')).toEqual('parent');
          expect(cmInj.get('moduleName')).toEqual('child');

          expect(pmInj.get(Parent, '-')).toEqual('-');
          expect(cmInj.get(Parent, '-')).toEqual('-');
          expect(pmInj.get(Child, '-')).toEqual('-');
          expect(cmInj.get(Child, '-')).toEqual('-');
        }),
      ));

      // https://github.com/angular/angular/issues/12889
      it('should create a single instance of lazy-loaded modules', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-parent [<router-outlet></router-outlet>]',
            standalone: false,
          })
          class ParentLazyLoadedComponent {}

          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-child',
            standalone: false,
          })
          class ChildLazyLoadedComponent {}

          @NgModule({
            declarations: [ParentLazyLoadedComponent, ChildLazyLoadedComponent],
            imports: [
              RouterModule.forChild([
                {
                  path: 'loaded',
                  component: ParentLazyLoadedComponent,
                  children: [{path: 'child', component: ChildLazyLoadedComponent}],
                },
              ]),
            ],
          })
          class LoadedModule {
            static instances = 0;
            constructor() {
              LoadedModule.instances++;
            }
          }

          const fixture = createRoot(router, RootCmp);
          router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);
          router.navigateByUrl('/lazy/loaded/child');
          advance(fixture);
          expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
          expect(LoadedModule.instances).toEqual(1);
        }),
      ));

      // https://github.com/angular/angular/issues/13870
      it('should create a single instance of guards for lazy-loaded modules', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Injectable()
          class Service {}

          @Injectable()
          class Resolver {
            constructor(public service: Service) {}
            resolve() {
              return this.service;
            }
          }

          @Component({
            selector: 'lazy',
            template: 'lazy',
            standalone: false,
          })
          class LazyLoadedComponent {
            resolvedService: Service;
            constructor(
              public injectedService: Service,
              route: ActivatedRoute,
            ) {
              this.resolvedService = route.snapshot.data['service'];
            }
          }

          @NgModule({
            declarations: [LazyLoadedComponent],
            providers: [Service, Resolver],
            imports: [
              RouterModule.forChild([
                {
                  path: 'loaded',
                  component: LazyLoadedComponent,
                  resolve: {'service': () => coreInject(Resolver).resolve()},
                },
              ]),
            ],
          })
          class LoadedModule {}

          const fixture = createRoot(router, RootCmp);
          router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);
          router.navigateByUrl('/lazy/loaded');
          advance(fixture);

          expect(fixture.nativeElement).toHaveText('lazy');
          const lzc = fixture.debugElement.query(
            By.directive(LazyLoadedComponent),
          ).componentInstance;
          expect(lzc.injectedService).toBe(lzc.resolvedService);
        }),
      ));

      it('should emit RouteConfigLoadStart and RouteConfigLoadEnd event when route is lazy loaded', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-parent [<router-outlet></router-outlet>]',
            standalone: false,
          })
          class ParentLazyLoadedComponent {}

          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-child',
            standalone: false,
          })
          class ChildLazyLoadedComponent {}

          @NgModule({
            declarations: [ParentLazyLoadedComponent, ChildLazyLoadedComponent],
            imports: [
              RouterModule.forChild([
                {
                  path: 'loaded',
                  component: ParentLazyLoadedComponent,
                  children: [{path: 'child', component: ChildLazyLoadedComponent}],
                },
              ]),
            ],
          })
          class LoadedModule {}

          const events: Array<RouteConfigLoadStart | RouteConfigLoadEnd> = [];

          router.events.subscribe((e) => {
            if (e instanceof RouteConfigLoadStart || e instanceof RouteConfigLoadEnd) {
              events.push(e);
            }
          });

          const fixture = createRoot(router, RootCmp);
          router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);

          router.navigateByUrl('/lazy/loaded/child');
          advance(fixture);

          expect(events.length).toEqual(2);
          expect(events[0].toString()).toEqual('RouteConfigLoadStart(path: lazy)');
          expect(events[1].toString()).toEqual('RouteConfigLoadEnd(path: lazy)');
        }),
      ));