port function toSignal<T, U = undefined>(
  source: Observable<T> | Subscribable<T>,
  options?: ToSignalOptions<T | U> & {initialValue?: U},
): Signal<T | U> {
  ngDevMode &&
    assertNotInReactiveContext(
      toSignal,
      'Invoking `toSignal` causes new subscriptions every time. ' +
        'Consider moving `toSignal` outside of the reactive context and read the signal value where needed.',
    );

  const requiresCleanup = !options?.manualCleanup;
  requiresCleanup && !options?.injector && assertInInjectionContext(toSignal);
  const cleanupRef = requiresCleanup
    ? (options?.injector?.get(DestroyRef) ?? inject(DestroyRef))
    : null;

  const equal = makeToSignalEqual(options?.equal);

  // Note: T is the Observable value type, and U is the initial value type. They don't have to be
  // the same - the returned signal gives values of type `T`.
  let state: WritableSignal<State<T | U>>;
  if (options?.requireSync) {
    // Initially the signal is in a `NoValue` state.
    state = signal({kind: StateKind.NoValue}, {equal});
  } else {
    // If an initial value was passed, use it. Otherwise, use `undefined` as the initial value.
    state = signal<State<T | U>>(
      {kind: StateKind.Value, value: options?.initialValue as U},
      {equal},
    );
  }

  // Note: This code cannot run inside a reactive context (see assertion above). If we'd support
  // this, we would subscribe to the observable outside of the current reactive context, avoiding
  // that side-effect signal reads/writes are attribute to the current consumer. The current
  // consumer only needs to be notified when the `state` signal changes through the observable
  // subscription. Additional context (related to async pipe):
  // https://github.com/angular/angular/pull/50522.
  const sub = source.subscribe({
    next: (value) => state.set({kind: StateKind.Value, value}),
    error: (error) => {
      if (options?.rejectErrors) {
        // Kick the error back to RxJS. It will be caught and rethrown in a macrotask, which causes
        // the error to end up as an uncaught exception.
        throw error;
      }
      state.set({kind: StateKind.Error, error});
    },
    // Completion of the Observable is meaningless to the signal. Signals don't have a concept of
    // "complete".
  });

  if (options?.requireSync && state().kind === StateKind.NoValue) {
    throw new ɵRuntimeError(
      ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT,
      (typeof ngDevMode === 'undefined' || ngDevMode) &&
        '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.',
    );
  }

  // Unsubscribe when the current context is destroyed, if requested.
  cleanupRef?.onDestroy(sub.unsubscribe.bind(sub));

  // The actual returned signal is a `computed` of the `State` signal, which maps the various states
  // to either values or errors.
  return computed(
    () => {
      const current = state();
      switch (current.kind) {
        case StateKind.Value:
          return current.value;
        case StateKind.Error:
          throw current.error;
        case StateKind.NoValue:
          // This shouldn't really happen because the error is thrown on creation.
          throw new ɵRuntimeError(
            ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT,
            (typeof ngDevMode === 'undefined' || ngDevMode) &&
              '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.',
          );
      }
    },
    {equal: options?.equal},
  );
}

function makeToSignalEqual<T>(
  userEquality: ValueEqualityFn<T> = Object.is,
): ValueEqualityFn<State<T>> {
  return (a, b) =>
    a.kind === StateKind.Value && b.kind === StateKind.Value && userEquality(a.value, b.value);
}

const enum StateKind {
  NoValue,
  Value,
  Error,
}

interface NoValueState {
  kind: StateKind.NoValue;
}

interface ValueState<T> {
  kind: StateKind.Value;
  value: T;
}

interface ErrorState {
  kind: StateKind.Error;
  error: unknown;
}

type State<T> = NoValueState | ValueState<T> | ErrorState;


/**
 * @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
 */

// tslint:disable

import {Component, Input} from '@angular/core';

@Component({
  template: `
    {{bla?.myInput}}
  `,
})
class WithSafePropertyReads {
  @Input() myInput = 0;

  bla: this | undefined = this;
}


s imports to the `imports` in a test', async () => {
    writeFile(
      'app.spec.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {TestBed} from '@angular/core/testing';
      import {MatCardModule} from '@angular/material/card';

      describe('bootstrapping an app', () => {
        it('should work', () => {
          TestBed.configureTestingModule({
            declarations: [App],
            imports: [MatCardModule.forRoot({})]
          });
          const fixture = TestBed.createComponent(App);
          expect(fixture.nativeElement.innerHTML).toBe('hello');
        });
      });

      @Component({template: 'hello', standalone: false})
      class App {}
    `,
    );

    await runMigration('convert-to-standalone');

    const content = stripWhitespace(tree.readContent('app.spec.ts'));

    expect(content).toContain(
      stripWhitespace(`
      @Component({template: 'hello'})
      class App {}
    `),
    );

    expect(content).toContain(
      stripWhitespace(`
      it('should work', () => {
        TestBed.configureTestingModule({
          imports: [MatCardModule.forRoot({}), App]
        });
        const fixture = TestBed.createComponent(App);
        expect(fixture.nativeElement.innerHTML).toBe('hello');
      });
    `),
    );
  });

  it('should not change testing objects with no declarations', async () => {
    const initialContent = `
      import {NgModule, Component} from '@angular/core';
      import {TestBed} from '@angular/core/testing';
      import {ButtonModule} from './button.module';
      import {MatCardModule} from '@angular/material/card';

      describe('bootstrapping an app', () => {
        it('should work', () => {
          TestBed.configureTestingModule({
            imports: [ButtonModule, MatCardModule]
          });
          const fixture = TestBed.createComponent(App);
          expect(fixture.nativeElement.innerHTML).toBe('<hello>Hello</hello>');
        });
      });

      @Component({template: 'hello', standalone: false})
      class App {}
    `;

    writeFile('app.spec.ts', initialContent);

    await runMigration('convert-to-standalone');

    expect(tree.readContent('app.spec.ts')).toBe(initialContent);
  });

  it('should migrate tests with a component declared through Catalyst', async () => {
    writeFile(
      'app.spec.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {bootstrap, setupModule} from 'some_internal_path/angular/testing/catalyst';
      import {ButtonModule} from './button.module';
      import {MatCardModule} from '@angular/material/card';

      describe('bootstrapping an app', () => {
        it('should work', () => {
          setupModule({
            declarations: [App, Hello],
            imports: [ButtonModule, MatCardModule]
          });
          const fixture = bootstrap(App);
          expect(fixture.nativeElement.innerHTML).toBe('<hello>Hello</hello>');
        });

        it('should work in a different way', () => {
          setupModule({declarations: [App, Hello], imports: [MatCardModule]});
          const fixture = bootstrap(App);
          expect(fixture.nativeElement.innerHTML).toBe('<hello>Hello</hello>');
        });
      });

      @Component({selector: 'hello', template: 'Hello', standalone: false})
      class Hello {}

      @Component({template: '<hello></hello>', standalone: false})
      class App {}
    `,
    );

    await runMigration('convert-to-standalone');

    const content = stripWhitespace(tree.readContent('app.spec.ts'));

    expect(content).toContain(
      stripWhitespace(`
      @Component({
        selector: 'hello',
        template: 'Hello',
        imports: [ButtonModule, MatCardModule]
      })
      class Hello {}
    `),
    );

    expect(content).toContain(
      stripWhitespace(`
      @Component({
        template: '<hello></hello>',
        imports: [ButtonModule, MatCardModule]
      })
      class App {}
    `),
    );

    expect(content).toContain(
      stripWhitespace(`
      it('should work', () => {
        setupModule({
          imports: [ButtonModule, MatCardModule, App, Hello]
        });
        const fixture = bootstrap(App);
        expect(fixture.nativeElement.innerHTML).toBe('<hello>Hello</hello>');
      });
    `),
    );

    expect(content).toContain(
      stripWhitespace(`
      it('should work in a different way', () => {
        setupModule({imports: [MatCardModule, App, Hello]});
        const fixture = bootstrap(App);
        expect(fixture.nativeElement.innerHTML).toBe('<hello>Hello</hello>');
      });
    `),
    );
  });

  it('should not copy over the NoopAnimationsModule into the imports of a test component', async () => {
    writeFile(
      'app.spec.ts',
      `
          import {NgModule, Component} from '@angular/core';
          import {TestBed} from '@angular/core/testing';
          import {MatCardModule} from '@angular/material/card';
          import {NoopAnimationsModule} from '@angular/platform-browser/animations';

          describe('bootstrapping an app', () => {
            it('should work', () => {
              TestBed.configureTestingModule({
                imports: [MatCardModule, NoopAnimationsModule],
                declarations: [App]
              });
              const fixture = TestBed.createComponent(App);
              expect(fixture.nativeElement.innerHTML).toBe('<hello>Hello</hello>');
            });
          });

          @Component({template: 'hello'})
          class App {}
        `,
    );

    await runMigration('convert-to-standalone');

    const content = stripWhitespace(tree.readContent('app.spec.ts'));

    expect(content).toContain(
      stripWhitespace(`
          TestBed.configureTestingModule({
            imports: [MatCardModule, NoopAnimationsModule, App]
          });
        `),
    );
    expect(content).toContain(
      stripWhitespace(`
          @Component({template: 'hello', imports: [MatCardModule]})
          class App {}
        `),
    );
  });

  it('should not copy over the BrowserAnimationsModule into the imports of a test component', async () => {
    writeFile(
      'app.spec.ts',
      `
          import {NgModule, Component} from '@angular/core';
          import {TestBed} from '@angular/core/testing';
          import {MatCardModule} from '@angular/material/card';
          import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

          describe('bootstrapping an app', () => {
            it('should work', () => {
              TestBed.configureTestingModule({
                imports: [MatCardModule, BrowserAnimationsModule],
                declarations: [App]
              });
              const fixture = TestBed.createComponent(App);
              expect(fixture.nativeElement.innerHTML).toBe('<hello>Hello</hello>');
            });
          });

          @Component({template: 'hello', standalone: false})
          class App {}
        `,
    );

    await runMigration('convert-to-standalone');

    const content = stripWhitespace(tree.readContent('app.spec.ts'));

    expect(content).toContain(
      stripWhitespace(`
          TestBed.configureTestingModule({
            imports: [MatCardModule, BrowserAnimationsModule, App]
          });
        `),
    );
    expect(content).toContain(
      stripWhitespace(`
          @Component({template: 'hello', imports: [MatCardModule]})
          class App {}
        `),
    );
  });

  it('should not move declarations that are not being migrated out of the declarations array', async () => {
    const appComponentContent = `
        import {Component} from '@angular/core';

        @Component({selector: 'app', template: '', standalone: false})
        export class AppComponent {}
      `;

    const appModuleContent = `
        import {NgModule} from '@angular/core';
        import {AppComponent} from './app.component';

        @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]})
        export class AppModule {}
      `;

    writeFile('app.component.ts', appComponentContent);
    writeFile('app.module.ts', appModuleContent);

    writeFile(
      'app.spec.ts',
      `
        import {Component} from '@angular/core';
        import {TestBed} from '@angular/core/testing';
        import {ButtonModule} from './button.module';
        import {MatCardModule} from '@angular/material/card';
        import {AppComponent} from './app.component';

        describe('bootstrapping an app', () => {
          it('should work', () => {
            TestBed.configureTestingModule({
              declarations: [AppComponent, TestComp],
              imports: [ButtonModule, MatCardModule]
            });
            const fixture = TestBed.createComponent(App);
            expect(fixture.nativeElement.innerHTML).toBe('');
          });
        });

        @Component({template: '', standalone: false})
        class TestComp {}
      `,
    );

    await runMigration('convert-to-standalone');

    const testContent = stripWhitespace(tree.readContent('app.spec.ts'));

    expect(tree.readContent('app.module.ts')).toBe(appModuleContent);
    expect(tree.readContent('app.component.ts')).toBe(appComponentContent);
    expect(testContent).toContain(
      stripWhitespace(`
        it('should work', () => {
          TestBed.configureTestingModule({
            declarations: [AppComponent],
            imports: [ButtonModule, MatCardModule, TestComp]
          });
          const fixture = TestBed.createComponent(App);
          expect(fixture.nativeElement.innerHTML).toBe('');
        });
      `),
    );
    expect(testContent).toContain(
      stripWhitespace(`
        @Component({
          template: '',
          imports: [ButtonModule, MatCardModule]
        })
        class TestComp {}
      `),
    );
  });

  it('should not migrate `configure


.bootstrapModule call to bootstrapApplication', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}

      @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]})
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';

      bootstrapApplication(AppComponent).catch(e => console.error(e));
    `),
    );

    expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(
      stripWhitespace(`
      import {NgModule, Component} from '@angular/core';

      @Component({template: 'hello'})
      export class AppComponent {}
    `),
    );
  });

  it('should switch a platformBrowserDynamic().bootstrapModule call to bootstrapApplication', async () => {
    writeFile(
      'main.ts',
      `
          import {AppModule} from './app/app.module';
          import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';

          platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e));
        `,
    );

    writeFile(
      './app/app.module.ts',
      `
          import {NgModule, Component} from '@angular/core';

          @Component({template: 'hello', standalone: false})
          export class AppComponent {}

          @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]})
          export class AppModule {}
        `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
          import {AppComponent} from './app/app.module';
          import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
          import {bootstrapApplication} from '@angular/platform-browser';

          bootstrapApplication(AppComponent).catch(e => console.error(e));
        `),
    );

    expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(
      stripWhitespace(`
          import {NgModule, Component} from '@angular/core';

          @Component({template: 'hello'})
          export class AppComponent {}
        `),
    );
  });

  it('should switch a PlatformRef.bootstrapModule call to bootstrapApplication', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {PlatformRef} from '@angular/core';

      const foo: PlatformRef = null!;

      foo.bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}

      @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]})
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {PlatformRef} from '@angular/core';
      import {bootstrapApplication} from '@angular/platform-browser';

      const foo: PlatformRef = null!;

      bootstrapApplication(AppComponent).catch(e => console.error(e));
    `),
    );

    expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(
      stripWhitespace(`
      import {NgModule, Component} from '@angular/core';

      @Component({template: 'hello'})
      export class AppComponent {}
    `),
    );
  });

  it('should convert the root module declarations to standalone', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.component.ts',
      `
      import {Component} from '@angular/core';

      @Component({template: '
hello
', standalone: false})
      export class AppComponent {
        show = true;
      }
    `,
    );

    writeFile(
      './app/dir.ts',
      `
      import {Directive} from '@angular/core';

      @Directive({selector: '[dir]', standalone: false})
      export class Dir {}
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule} from '@angular/core';
      import {CommonModule} from '@angular/common';
      import {AppComponent} from './app.component';
      import {Dir} from './dir';

      @NgModule({
        imports: [CommonModule],
        declarations: [AppComponent, Dir],
        bootstrap: [AppComponent]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(tree.exists('./app/app.module.ts')).toBe(false);
    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {CommonModule} from '@angular/common';
      import {AppComponent} from './app/app.component';
      import {importProvidersFrom} from '@angular/core';

      bootstrapApplication(AppComponent, {
        providers: [importProvidersFrom(CommonModule)]
      }).catch(e => console.error(e));
    `),
    );

    expect(stripWhitespace(tree.readContent('./app/app.component.ts'))).toBe(
      stripWhitespace(`
      import {Component} from '@angular/core';
      import {NgIf} from '@angular/common';
      import {Dir} from './dir';

      @Component({
        template: '
hello
',
        imports: [NgIf, Dir]
      })
      export class AppComponent {
        show = true;
      }
    `),
    );

    expect(stripWhitespace(tree.readContent('./app/dir.ts'))).toBe(
      stripWhitespace(`
      import {Directive} from '@angular/core';

      @Directive({selector: '[dir]'})
      export class Dir {}
    `),
    );
  });

  it('should migrate the root component tests when converting to standalone', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.component.ts',
      `
      import {Component} from '@angular/core';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}
    `,
    );

    writeFile(
      './app/app.component.spec.ts',
      `
      import {TestBed} from '@angular/core/testing';
      import {AppComponent} from './app.component';

      describe('bootstrapping an app', () => {
        it('should work', () => {
          TestBed.configureTestingModule({declarations: [AppComponent]});
          const fixture = TestBed.createComponent(AppComponent);
          expect(fixture.nativeElement.innerHTML).toBe('hello');
        });
      });
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule} from '@angular/core';
      import {AppComponent} from './app.component';

      @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]})
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('./app/app.component.ts'))).toBe(
      stripWhitespace(`
      import {Component} from '@angular/core';

      @Component({template: 'hello'})
      export class AppComponent {}
    `),
    );

    expect(stripWhitespace(tree.readContent('./app/app.component.spec.ts'))).toBe(
      stripWhitespace(`
      import {TestBed} from '@angular/core/testing';
      import {AppComponent} from './app.component';

      describe('bootstrapping an app', () => {
        it('should work', () => {
          TestBed.configureTestingModule({imports: [AppComponent]});
          const fixture = TestBed.createComponent(AppComponent);
          expect(fixture.nativeElement.innerHTML).toBe('hello');
        });
      });
    `),
    );
  });

  it('should copy providers and the


orts` array to the `providers` and wrap them in `importProvidersFrom`', async () => {
    writeFile(
      'main.ts',
      `
        import {AppModule} from './app/app.module';
        import {platformBrowser} from '@angular/platform-browser';

        platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
      `,
    );

    writeFile(
      'token.ts',
      `
        import {InjectionToken} from '@angular/core';
        export const token = new InjectionToken<string>('token');
      `,
    );

    writeFile(
      './modules/internal.module.ts',
      `
        import {NgModule} from '@angular/core';
        import {token} from '../token';

        @NgModule({providers: [{provide: token, useValue: 'InternalModule'}]})
        export class InternalModule {}
      `,
    );

    writeFile(
      './app/app.module.ts',
      `
        import {NgModule, Component} from '@angular/core';
        import {CommonModule} from '@angular/common';
        import {InternalModule} from '../modules/internal.module';
        import {token} from '../token';

        @Component({template: 'hello', standalone: true})
        export class AppComponent {}

        @NgModule({providers: [{provide: token, useValue: 'SameFileModule'}]})
        export class SameFileModule {}

        @NgModule({
          imports: [CommonModule, InternalModule, SameFileModule],
          declarations: [AppComponent],
          bootstrap: [AppComponent]
        })
        export class AppModule {}
      `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
        import {SameFileModule, AppComponent} from './app/app.module';
        import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
        import {CommonModule} from '@angular/common';
        import {InternalModule} from './modules/internal.module';
        import {NgModule, importProvidersFrom} from '@angular/core';
        import {token} from './token';

        bootstrapApplication(AppComponent, {
          providers: [importProvidersFrom(CommonModule, InternalModule, SameFileModule)]
        }).catch(e => console.error(e));
      `),
    );
  });

  it('should switch RouterModule.forRoot calls with one argument to provideRouter', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {RouterModule} from '@angular/router';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [
          RouterModule.forRoot([
            {path: 'internal-comp', loadComponent: () => import("./routes/internal-comp").then(c => c.InternalComp) },
            {path: 'external-comp', loadComponent: () => import('@external/external-comp').then(c => c.ExternalComp) }
          ])
        ]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {provideRouter} from '@angular/router';

      bootstrapApplication(AppComponent, {
        providers: [provideRouter([
          {path: 'internal-comp', loadComponent: () => import("./app/routes/internal-comp").then(c => c.InternalComp)},
          {path: 'external-comp', loadComponent: () => import('@external/external-comp').then(c => c.ExternalComp)}
        ])]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should migrate a RouterModule.forRoot call with an empty config', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {RouterModule} from '@angular/router';
      import {APP_ROUTES} from './routes';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [RouterModule.forRoot(APP_ROUTES, {})]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {provideRouter} from '@angular/router';
      import {APP_ROUTES} from './app/routes';

      bootstrapApplication(AppComponent, {
        providers: [provideRouter(APP_ROUTES)]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should migrate a router config with a preloadingStrategy to withPreloading', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {RouterModule} from '@angular/router';
      import {of} from 'rxjs';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [RouterModule.forRoot([], {
          preloadingStrategy: () => of(true)
        })]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {withPreloading, provideRouter} from '@angular/router';
      import {of} from 'rxjs';

      bootstrapApplication(AppComponent, {
        providers: [provideRouter([], withPreloading(() => of(true)))]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should migrate a router config with enableTracing to withDebugTracing', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {RouterModule} from '@angular/router';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [RouterModule.forRoot([], {
          enableTracing: true
        })]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {withDebugTracing, provideRouter} from '@angular/router';

      bootstrapApplication(AppComponent, {
        providers: [provideRouter([], withDebugTracing())]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should migrate a router confi


th `initialNavigation: "enabledBlocking"` to withEnabledBlockingInitialNavigation', async () => {
    writeFile(
      'main.ts',
      `
          import {AppModule} from './app/app.module';
          import {platformBrowser} from '@angular/platform-browser';

          platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
        `,
    );

    writeFile(
      './app/app.module.ts',
      `
          import {NgModule, Component} from '@angular/core';
          import {RouterModule} from '@angular/router';

          @Component({template: 'hello', standalone: false})
          export class AppComponent {}

          @NgModule({
            declarations: [AppComponent],
            bootstrap: [AppComponent],
            imports: [RouterModule.forRoot([], {
              initialNavigation: 'enabledBlocking'
            })]
          })
          export class AppModule {}
        `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
          import {AppComponent} from './app/app.module';
          import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
          import {withEnabledBlockingInitialNavigation, provideRouter} from '@angular/router';

          bootstrapApplication(AppComponent, {
            providers: [provideRouter([], withEnabledBlockingInitialNavigation())]
          }).catch(e => console.error(e));
        `),
    );
  });

  it('should migrate a router config with `initialNavigation: "enabled"` to withEnabledBlockingInitialNavigation', async () => {
    writeFile(
      'main.ts',
      `
          import {AppModule} from './app/app.module';
          import {platformBrowser} from '@angular/platform-browser';

          platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
        `,
    );

    writeFile(
      './app/app.module.ts',
      `
          import {NgModule, Component} from '@angular/core';
          import {RouterModule} from '@angular/router';

          @Component({template: 'hello', standalone: false})
          export class AppComponent {}

          @NgModule({
            declarations: [AppComponent],
            bootstrap: [AppComponent],
            imports: [RouterModule.forRoot([], {
              initialNavigation: 'enabled'
            })]
          })
          export class AppModule {}
        `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
          import {AppComponent} from './app/app.module';
          import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
          import {withEnabledBlockingInitialNavigation, provideRouter} from '@angular/router';

          bootstrapApplication(AppComponent, {
            providers: [provideRouter([], withEnabledBlockingInitialNavigation())]
          }).catch(e => console.error(e));
        `),
    );
  });

  it('should migrate a router config with `initialNavigation: "disabled"` to withDisabledInitialNavigation', async () => {
    writeFile(
      'main.ts',
      `
          import {AppModule} from './app/app.module';
          import {platformBrowser} from '@angular/platform-browser';

          platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
        `,
    );

    writeFile(
      './app/app.module.ts',
      `
          import {NgModule, Component} from '@angular/core';
          import {RouterModule} from '@angular/router';

          @Component({template: 'hello', standalone: false})
          export class AppComponent {}

          @NgModule({
            declarations: [AppComponent],
            bootstrap: [AppComponent],
            imports: [RouterModule.forRoot([], {
              initialNavigation: 'disabled'
            })]
          })
          export class AppModule {}
        `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
          import {AppComponent} from './app/app.module';
          import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
          import {withDisabledInitialNavigation, provideRouter} from '@angular/router';

          bootstrapApplication(AppComponent, {
            providers: [provideRouter([], withDisabledInitialNavigation())]
          }).catch(e => console.error(e));
        `),
    );
  });

  it('should migrate a router config with useHash to withHashLocation', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {RouterModule} from '@angular/router';

      @Component({template: 'hello', standalone: false})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [RouterModule.forRoot([], {
          useHash: true
        })]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {withHashLocation, provideRouter} from '@angular/router';

      bootstrapApplication(AppComponent, {
        providers: [provideRouter([], withHashLocation())]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should migrate a router config with errorHandler to withNavigationErrorHandler', async () => {
    writeFile(
      'main.ts',
      `
          import {AppModule} from './app/app.module';
          import {platformBrowser} from '@angular/platform-browser';

          platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
        `,
    );

    writeFile(
      './app/app.module.ts',
      `
          import {NgModule, Component} from '@angular/core';
          import {RouterModule} from '@angular/router';

          @Component({template: 'hello', standalone: false})
          export class AppComponent {}

          @NgModule({
            declarations: [AppComponent],
            bootstrap: [AppComponent],
            imports: [RouterModule.forRoot([], {
              errorHandler: () => {}
            })]
          })
          export class AppModule {}
        `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
          import {AppComponent} from './app/app.module';
          import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
          import {withNavigationErrorHandler, provideRouter} from '@angular/router';

          bootstrapApplication(AppComponent, {
            providers: [provideRouter([], withNavigationErrorHandler(() => {}))]
          }).catch(e => console.error(e));
        `),
    );
  });

  it('should combine the anchorScrolling and scrollPositionRestoration of a router config into withInMemoryScrolling', async () => {
    writeFile(
      'main.ts',
      `
          import {AppModule} from './app/app.module';
          import {platformBrowser} from '@angular/platform-browser';

          platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
        `,
    );

    writeFile(
      './app/app.module.ts',
      `
          import {NgModule, Component} from '@angular/core';
          import {RouterModule} from '@angular/router';

          @Component({template: 'hello', standalone: false})
          export class AppComponent {}

          @NgModule({
            declarations: [AppComponent],
            bootstrap: [AppComponent],
            imports: [RouterModule.forRoot([], {
              anchorScrolling: 'enabled',
              scrollPositionRestoration: 'top'
            })]
          })
          export class AppModule {}
        `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
          import {AppComponent} from './app/app.module';
          import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
          import {withInMemoryScrolling, provideRouter} from '@angular/router';

          bootstrapApplication(AppComponent, {
            providers: [provideRouter([], withInMemoryScrolling({
              anchorScrolling: 'enabled',
              scrollPositionRestoration: 'top'
            }))]
          }).catch(e => console.error(e));
        `),
    );
  });

  it('should copy properties that d


t map to a feature into withRouterConfig', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {RouterModule} from '@angular/router';

      @Component({template: 'hello'})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [RouterModule.forRoot([], {
          canceledNavigationResolution: 'replace',
          useHash: true,
          paramsInheritanceStrategy: 'always'
        })]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {withHashLocation, withRouterConfig, provideRouter} from '@angular/router';

      bootstrapApplication(AppComponent, {
        providers: [provideRouter([], withHashLocation(), withRouterConfig({
          canceledNavigationResolution: 'replace',
          paramsInheritanceStrategy: 'always'
        }))]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should preserve a RouterModule.forRoot where the config cannot be statically analyzed', async () => {
    writeFile(
      'main.ts',
      `
        import {AppModule} from './app/app.module';
        import {platformBrowser} from '@angular/platform-browser';

        platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
      `,
    );

    writeFile(
      './app/app.module.ts',
      `
        import {NgModule, Component} from '@angular/core';
        import {RouterModule} from '@angular/router';

        @Component({template: 'hello'})
        export class AppComponent {}

        const extraOptions = {useHash: true};

        @NgModule({
          declarations: [AppComponent],
          bootstrap: [AppComponent],
          imports: [RouterModule.forRoot([], {
            enableTracing: true,
            ...extraOptions
          })]
        })
        export class AppModule {}
      `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
        import {AppComponent} from './app/app.module';
        import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
        import {RouterModule} from '@angular/router';
        import {importProvidersFrom} from '@angular/core';

        const extraOptions = {useHash: true};

        bootstrapApplication(AppComponent, {
          providers: [importProvidersFrom(RouterModule.forRoot([], {
            enableTracing: true,
            ...extraOptions
          }))]
        }).catch(e => console.error(e));
      `),
    );
  });

  it('should convert BrowserAnimationsModule references to provideAnimations', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

      @Component({template: 'hello'})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [BrowserAnimationsModule]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {provideAnimations} from '@angular/platform-browser/animations';

      bootstrapApplication(AppComponent, {
        providers: [provideAnimations()]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should preserve BrowserAnimationsModule.withConfig calls', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

      @Component({template: 'hello'})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [BrowserAnimationsModule.withConfig({disableAnimations: true})]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
      import {importProvidersFrom} from '@angular/core';

      bootstrapApplication(AppComponent, {
        providers: [importProvidersFrom(BrowserAnimationsModule.withConfig({disableAnimations: true}))]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should convert NoopAnimationsModule references to provideNoopAnimations', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {NoopAnimationsModule} from '@angular/platform-browser/animations';

      @Component({template: 'hello'})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [NoopAnimationsModule]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {provideNoopAnimations} from '@angular/platform-browser/animations';

      bootstrapApplication(AppComponent, {
        providers: [provideNoopAnimations()]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should convert HttpClientModule references to provideHttpClient(withInterceptorsFromDi())', async () => {
    writeFile(
      'main.ts',
      `
      import {AppModule} from './app/app.module';
      import {platformBrowser} from '@angular/platform-browser';

      platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
    `,
    );

    writeFile(
      './app/app.module.ts',
      `
      import {NgModule, Component} from '@angular/core';
      import {HttpClientModule} from '@angular/common/http';

      @Component({template: 'hello'})
      export class AppComponent {}

      @NgModule({
        declarations: [AppComponent],
        bootstrap: [AppComponent],
        imports: [HttpClientModule]
      })
      export class AppModule {}
    `,
    );

    await runMigration('standalone-bootstrap');

    expect(stripWhitespace(tree.readContent('main.ts'))).toBe(
      stripWhitespace(`
      import {AppComponent} from './app/app.module';
      import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
      import {withInterceptorsFromDi, provideHttpClient} from '@angular/common/http';

      bootstrapApplication(AppComponent, {
        providers: [provideHttpClient(withInterceptorsFromDi())]
      }).catch(e => console.error(e));
    `),
    );
  });

  it('should omit standalone direct


it('should migrate nested children components', async () => {
    writeFile(
      'app.module.ts',
      `
      import {NgModule} from '@angular/core';
      import {RouterModule} from '@angular/router';
      import {routes} from './routes';
      @NgModule({ imports: [RouterModule.forRoot(routes] })
      export class AppModule {}
    `,
    );

    writeFile(
      'routes.ts',
      `
      import {Routes} from '@angular/router';
      import {TestComponent} from './test';
      import {Test1Component} from './cmp1';
      export const routes: Routes = [
        {path: 'test', component: TestComponent},
        {path: 'test1', component: Test1Component},
        {
          path:'nested',
          children: [
            {
              path: 'test',
              component: TestComponent,
              children: [
                {path: 'test', component: TestComponent},
                {path: 'test1', component: Test1Component},
                {
                  path: 'nested1',
                  children: [
                    {path: 'test', component: TestComponent},
                  ],
                },
              ],
            },
            {path: 'test', component: TestComponent},
          ]
        },
      ];
    `,
    );

    writeFile(
      'test.ts',
      `
      import {Component} from '@angular/core';
      @Component({template: 'hello', standalone: true})
      export class TestComponent {}
    `,
    );
    writeFile(
      'cmp1.ts',
      `
      import {Component} from '@angular/core';
      @Component({template: 'hello', standalone: true})
      export class Test1Component {}
    `,
    );

    await runMigration('route-lazy-loading');

    expect(stripWhitespace(tree.readContent('routes.ts'))).toContain(
      stripWhitespace(`
         import {Routes} from '@angular/router';
         export const routes: Routes = [
          {path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)},
          {path: 'test1', loadComponent: () => import('./cmp1').then(m => m.Test1Component)},
          {
            path:'nested',
            children: [
              {
                path: 'test',
                loadComponent: () => import('./test').then(m => m.TestComponent),
                children: [
                  {path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)},
                  {path: 'test1', loadComponent: () => import('./cmp1').then(m => m.Test1Component)},
                  {
                    path: 'nested1',
                    children: [
                      {path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)},
                    ],
                  },
                ],
              },
              {path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)},
            ]
          },
         ];
      `),
    );
  });

  it('should migrate routes if the routes file in is another file with type', async () => {
    writeFile(
      'app.module.ts',
      `
      import {NgModule} from '@angular/core';
      import {RouterModule} from '@angular/router';
      import {routes} from './routes';

      @NgModule({
        imports: [RouterModule.forRoot(routes],
      })
      export class AppModule {}
    `,
    );

    writeFile(
      'routes.ts',
      `
      import {Routes, Route} from '@angular/router';
      import {TestComponent} from './test';
      import {Test2 as Test2Alias} from './test2';
      export const routes: Routes = [{path: 'test', component: TestComponent}];
      export const routes1: Route[] = [{path: 'test', component: Test2Alias}];
    `,
    );

    writeFile(
      'test.ts',
      `
      import {Component} from '@angular/core';
      @Component({template: 'hello', standalone: true})
      export class TestComponent {}
    `,
    );
    writeFile(
      'test2.ts',
      `
      import {Component} from '@angular/core';
      @Component({template: 'hello', standalone: true})
      export class Test2 {}
    `,
    );

    await runMigration('route-lazy-loading');

    expect(stripWhitespace(tree.readContent('routes.ts'))).toContain(
      stripWhitespace(`
         import {Routes, Route} from '@angular/router';
         export const routes: Routes = [{path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)}];
         export const routes1: Route[] = [{path: 'test', loadComponent: () => import('./test2').then(m => m.Test2)}];
      `),
    );
  });

  it('should migrate routes array if the Routes type is aliased', async () => {
    writeFile(
      'routes.ts',
      `
      import {Routes as Routes1, Route as Route1} from '@angular/router';
      import {TestComponent} from './test';
      export const routes: Routes1 = [{path: 'test', component: TestComponent}];
      export const routes1: Route1[] = [{path: 'test', component: TestComponent}];
    `,
    );

    writeFile(
      'test.ts',
      `
      import {Component} from '@angular/core';
      @Component({template: 'hello', standalone: true})
      export class TestComponent {}
    `,
    );

    await runMigration('route-lazy-loading');

    expect(stripWhitespace(tree.readContent('routes.ts'))).toContain(
      stripWhitespace(`
        import {Routes as Routes1, Route as Route1} from '@angular/router';
        export const routes: Routes1 = [{path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)}];
        export const routes1: Route1[] = [{path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)}];
      `),
    );
  });

  it('should not migrate routes if the routes array doesnt have type and is not referenced', async () => {
    writeFile(
      'routes.ts',
      `
     import {TestComponent} from './test';
     export const routes = [{path: 'test', component: TestComponent}];
    `,
    );

    writeFile(
      'test.ts',
      `
      import {Component} from '@angular/core';
      @Component({template: 'hello', standalone: true})
      export class TestComponent {}
    `,
    );

    let error: string | null = null;
    try {
      await runMigration('route-lazy-loading');
    } catch (e: any) {
      error = e.message;
    }

    expect(error).toMatch(/Could not find any files to migrate under the path/);
  });

  xit('should migrate routes if the routes file in is another file without type', async () => {
    writeFile(
      'app.module.ts',
      `
      import {NgModule} from '@angular/core';
      import {RouterModule} from '@angular/router';
      import {routes} from './routes';

      @NgModule({
        imports: [RouterModule.forRoot(routes],
      })
      export class AppModule {}
    `,
    );

    writeFile(
      'routes.ts',
      `
     import {TestComponent} from './test';
     export const routes = [{path: 'test', component: TestComponent}];
    `,
    );

    writeFile(
      'test.ts',
      `
      import {Component} from '@angular/core';
      @Component({template: 'hello', standalone: true})
      export class TestComponent {}
    `,
    );

    await runMigration('route-lazy-loading');

    expect(stripWhitespace(tree.readContent('routes.ts'))).toContain(
      stripWhitespace(`
        export const routes = [{path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)}];
      `),
    );
  });

  // TODO: support multiple imports of components
  // ex import * as Components from './components';
  // export const MenuRoutes: Routes = [
  //   {
  //     path: 'menu',
  //     component: Components.MenuListComponent
  //   },
  // ];
});


/**
 * @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.io/license
 */

import {getSystemPath, normalize, virtualFs} from '@angular-devkit/core';
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
import {HostTree} from '@angular-devkit/schematics';
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
import {runfiles} from '@bazel/runfiles';
import shx from 'shelljs';

describe('Provide initializer migration', () => {
  it('should transform APP_INITIALIZER + useValue into provideAppInitializer', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: () => { console.log('hello'); },
        multi: true,
      }];
    `);

    expect(content).toContain(`import { provideAppInitializer } from '@angular/core';`);
    expect(content).toContain(
      `const providers = [provideAppInitializer(() => { console.log('hello'); })]`,
    );
    expect(content).not.toContain('APP_INITIALIZER');
  });

  it('should not remove other imported symbols by mistake', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER, input } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: () => { console.log('hello'); },
        multi: true,
      }];
    `);

    expect(content).toContain(`import { input, provideAppInitializer } from '@angular/core';`);
  });

  it('should reuse provideAppInitializer if already imported', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER, input, provideAppInitializer } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: () => { console.log('hello'); },
        multi: true,
      }];
    `);

    expect(content).toContain(`import { input, provideAppInitializer } from '@angular/core';`);
  });

  it('should transform APP_INITIALIZER + useValue async function into provideAppInitializer', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: async () => { await Promise.resolve(); return 42; },
        multi: true,
      }];
    `);

    expect(content).toContain(
      `const providers = [provideAppInitializer(async () => { await Promise.resolve(); return 42; })]`,
    );
  });
  it('should transform APP_INITIALIZER + useValue symbol into provideAppInitializer', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@angular/core';

      function initializerFn() {}

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: initializerFn,
        multi: true,
      }];
    `);

    expect(content).toContain(`const providers = [provideAppInitializer(initializerFn)];`);
  });

  it('should transform APP_INITIALIZER + useFactory into provideAppInitializer', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useFactory: () => {
          const service = inject(Service);
          return () => service.init();
        },
        multi: true,
      }];
    `);

    expect(content).toContain(`const providers = [provideAppInitializer(() => { return (() => {
          const service = inject(Service);
          return () => service.init();
        })(); })];`);
  });

  it('should transform APP_INITIALIZER + useExisting into provideAppInitializer', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useExisting: MY_INITIALIZER,
        multi: true,
      }];
    `);

    expect(content).toContain(`import { inject, provideAppInitializer } from '@angular/core';`);
    expect(content).toContain(
      `const providers = [provideAppInitializer(() => inject(MY_INITIALIZER)())]`,
    );
  });

  it('should transform APP_INITIALIZER + deps into provideAppInitializer', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useFactory: (a: ServiceA, b: ServiceB) => {
          return () => a.init();
        },
        deps: [ServiceA, ServiceB],
        multi: true,
      }];
    `);

    expect(content).toContain(`import { inject, provideAppInitializer } from '@angular/core';`);
    expect(content).toContain(
      `const providers = [provideAppInitializer(() => { return ((a: ServiceA, b: ServiceB) => {
          return () => a.init();
        })(inject(ServiceA), inject(ServiceB)); })];`,
    );
  });

  it('should not transform APP_INITIALIZER if multi is not set to true', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: [initializer],
      }];
    `);

    expect(content).toBe(`
      import { APP_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: [initializer],
      }];
    `);
  });

  it('should not transform APP_INITIALIZER if it is not imported from @angular/core', async () => {
    const content = await migrateCode(`
      import { APP_INITIALIZER } from '@not-angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: () => { console.log('hello'); },
        multi: true,
      }];
    `);

    expect(content).toBe(`
      import { APP_INITIALIZER } from '@not-angular/core';

      const providers = [{
        provide: APP_INITIALIZER,
        useValue: () => { console.log('hello'); },
        multi: true,
      }];
    `);
  });

  it('should transform ENVIRONMENT_INITIALIZER + useValue into provideEnvironmentInitializer', async () => {
    const content = await migrateCode(`
      import { ENVIRONMENT_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: ENVIRONMENT_INITIALIZER,
        useValue: () => { console.log('hello'); },
        multi: true,
      }];
    `);

    expect(content).toContain(`import { provideEnvironmentInitializer } from '@angular/core';`);
    expect(content).toContain(
      `const providers = [provideEnvironmentInitializer(() => { console.log('hello'); })]`,
    );
    expect(content).not.toContain('ENVIRONMENT_INITIALIZER');
  });

  it('should transform PLATFORM_INITIALIZER + useValue into providePlatformInitializer', async () => {
    const content = await migrateCode(`
      import { PLATFORM_INITIALIZER } from '@angular/core';

      const providers = [{
        provide: PLATFORM_INITIALIZER,
        useValue: () => { console.log('hello'); },
        multi: true,
      }];
    `);

    expect(content).toContain(`import { providePlatformInitializer } from '@angular/core';`);
    expect(content).toContain(
      `const providers = [providePlatformInitializer(() => { console.log('hello'); })]`,
    );
    expect(content).not.toContain('PLATFORM_INITIALIZER');
  });
});

async function migrateCode(content: string) {
  const {readFile, writeFile, runMigration} = setUpMigration();

  writeFile('/index.ts', content);

  await runMigration();

  return readFile('/index.ts');
}

function setUpMigration() {
  const host = new TempScopedNodeJsSyncHost();
  const tree = new UnitTestTree(new HostTree(host));
  const runner = new SchematicTestRunner(
    'test',
    runfiles.resolvePackageRelative('../migrations.json'),
  );

  function writeFile(filePath: string, content: string) {
    host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(content));
  }

  writeFile(
    '/tsconfig.json',
    JSON.stringify({
      compilerOptions: {
        lib: ['es2015'],
        strictNullChecks: true,
      },
    }),
  );

  writeFile(
    '/angular.json',
    JSON.stringify({
      version: 1,
      projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}},
    }),
  );

  const previousWorkingDir = shx.pwd();
  const tmpDirPath = getSystemPath(host.root);

  // Switch into the temporary directory path. This allows us to run
  // the schematic against our custom unit test tree.
  shx.cd(tmpDirPath);

  // Get back to current directory after test.
  cleanupFns.push(() => shx.cd(previousWorkingDir));

  return {
    readFile(filePath: string) {
      return tree.readContent(filePath);
    },
    writeFile,
    async runMigration() {
      return await runner.runSchematic('provide-initializer', {}, tree);
    },
  };
}

let cleanupFns: Array<() => void> = [];
afterEach(() => {
  for (const cleanupFn of cleanupFns) {
    cleanupFn();
  }
  cleanupFns = [];
});


/**
 * @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 {getSystemPath, logging, normalize, virtualFs} from '@angular-devkit/core';
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
import {HostTree} from '@angular-devkit/schematics';
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
import {runfiles} from '@bazel/runfiles';
import shx from 'shelljs';

describe('control flow migration', () => {
  let runner: SchematicTestRunner;
  let host: TempScopedNodeJsSyncHost;
  let tree: UnitTestTree;
  let tmpDirPath: string;
  let previousWorkingDir: string;
  let errorOutput: string[] = [];
  let warnOutput: string[] = [];

  function writeFile(filePath: string, contents: string) {
    host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
  }

  function runMigration(path: string | undefined = undefined, format: boolean = true) {
    return runner.runSchematic('control-flow-migration', {path, format}, tree);
  }

  beforeEach(() => {
    runner = new SchematicTestRunner('test', runfiles.resolvePackageRelative('../collection.json'));
    host = new TempScopedNodeJsSyncHost();
    tree = new UnitTestTree(new HostTree(host));

    errorOutput = [];
    warnOutput = [];
    runner.logger.subscribe((e: logging.LogEntry) => {
      if (e.level === 'error') {
        errorOutput.push(e.message);
      } else if (e.level === 'warn') {
        warnOutput.push(e.message);
      }
    });

    writeFile('/tsconfig.json', '{}');
    writeFile(
      '/angular.json',
      JSON.stringify({
        version: 1,
        projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}},
      }),
    );

    previousWorkingDir = shx.pwd();
    tmpDirPath = getSystemPath(host.root);

    // Switch into the temporary directory path. This allows us to run
    // the schematic against our custom unit test tree.
    shx.cd(tmpDirPath);
  });

  afterEach(() => {
    shx.cd(previousWorkingDir);
    shx.rm('-r', tmpDirPath);
  });

  describe('path', () => {
    it('should throw an error if no files match the passed-in path', async () => {
      let error: string | null = null;

      writeFile(
        'dir.ts',
        `
        import {Directive} from '@angular/core';

        @Directive({selector: '[dir]'})
        export class MyDir {}
      `,
      );

      try {
        await runMigration('./foo');
      } catch (e: any) {
        error = e.message;
      }

      expect(error).toMatch(
        /Could not find any files to migrate under the path .*\/foo\. Cannot run the control flow migration/,
      );
    });

    it('should throw an error if a path outside of the project is passed in', async () => {
      let error: string | null = null;

      writeFile(
        'dir.ts',
        `
        import {Directive} from '@angular/core';

        @Directive({selector: '[dir]'})
        export class MyDir {}
      `,
      );

      try {
        await runMigration('../foo');
      } catch (e: any) {
        error = e.message;
      }
      expect(error).toBe('Cannot run control flow migration outside of the current project.');
    });

    it('should only migrate the paths that were passed in', async () => {
      writeFile(
        'comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          imports: [NgIf, NgFor,NgSwitch,NgSwitchCase ,NgSwitchDefault],
          template: \`<div><span *ngIf="toggle">This should be hidden</span></div>\`
        })
        class Comp {
          toggle = false;
        }
      `,
      );

      writeFile(
        'skip.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          imports: [NgIf],
          template: \`<div *ngIf="show">Show me</div>\`
        })
        class Comp {
          show = false;
        }
      `,
      );

      await runMigration('./comp.ts');
      const migratedContent = tree.readContent('/comp.ts');
      const skippedContent = tree.readContent('/skip.ts');

      expect(migratedContent).toContain(
        'template: `<div>@if (toggle) {<span>This should be hidden</span>}</div>`',
      );
      expect(migratedContent).toContain('imports: []');
      expect(migratedContent).not.toContain(`import {NgIf} from '@angular/common';`);
      expect(skippedContent).toContain('template: `<div *ngIf="show">Show me</div>`');
      expect(skippedContent).toContain('imports: [NgIf]');
      expect(skippedContent).toContain(`import {NgIf} from '@angular/common';`);
    });
  });


describe('ngIf', () => {
    it('should migrate an inline template', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          imports: [NgIf],
          template: \`<div><span *ngIf="toggle">This should be hidden</span></div>\`
        })
        class Comp {
          toggle = false;
        }
      `,
      );

      await runMigration();
      const content = tree.readContent('/comp.ts');

      expect(content).toContain(
        'template: `<div>@if (toggle) {<span>This should be hidden</span>}</div>`',
      );
    });

    it('should migrate an empty case', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {ngSwitch, ngSwitchCase} from '@angular/common';
        @Component({
          template: \`<div [ngSwitch]="testOpts">` +
          `<p *ngSwitchCase="">Option 1</p>` +
          `<p *ngSwitchCase="2">Option 2</p>` +
          `</div>\`
        })
        class Comp {
          testOpts = "1";
        }
      `,
      );

      await runMigration();
      const content = tree.readContent('/comp.ts');

      expect(content).toContain(
        `template: \`
@switch (testOpts) { @case ('') {
Option 1
 } @case (2) {
Option 2
 }}
`,
      );
    });

    it('should migrate multiple inline templates in the same file', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          imports: [NgIf],
          template: \`<div><span *ngIf="toggle">This should be hidden</span></div>\`
        })
        class Comp {
          toggle = false;
        }

        @Component({
          template: \`<article *ngIf="show === 5">An Article</article>\`
        })
        class OtherComp {
          show = 5
        }
      `,
      );

      await runMigration();
      const content = tree.readContent('/comp.ts');

      expect(content).toContain(
        'template: `<div>@if (toggle) {<span>This should be hidden</span>}</div>`',
      );
      expect(content).toContain('template: `@if (show === 5) {<article>An Article</article>}`');
    });

    it('should migrate an external template', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [`<div>`, `<span *ngIf="show">Content here</span>`, `</div>`].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [`<div>`, `  @if (show) {`, `    <span>Content here</span>`, `  }`, `</div>`].join('\n'),
      );
    });

    it('should migrate a template referenced by multiple components', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {}
      `,
      );

      writeFile(
        '/other-comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class OtherComp {}
      `,
      );

      writeFile(
        '/comp.html',
        [`<div>`, `<span *ngIf="show">Content here</span>`, `</div>`].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [`<div>`, `  @if (show) {`, `    <span>Content here</span>`, `  }`, `</div>`].join('\n'),
      );
    });

    it('should migrate an if case with no star', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [`<div>`, `<span ngIf="show">Content here</span>`, `</div>`].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [`<div>`, `  @if (show) {`, `    <span>Content here</span>`, `  }`, `</div>`].join('\n'),
      );
    });

    it('should migrate an if case as a binding', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [`<div>`, `<span [ngIf]="show">Content here</span>`, `</div>`].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [`<div>`, `  @if (show) {`, `    <span>Content here</span>`, `  }`, `</div>`].join('\n'),
      );
    });

    it('should migrate an if case as a binding with let variables', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-template [ngIf]="data$ | async" let-data="ngIf">`,
          `  {{ data }}`,
          `</ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe([`@if (data$ | async; as data) {`, `  {{ data }}`, `}`].join('\n'));
    });

    it('should migrate an if case as a binding with let variable with no value', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-template [ngIf]="viewModel$ | async" let-vm>`,
          `  {{vm | json}}`,
          `</ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [`@if (viewModel$ | async; as vm) {`, `  {{vm | json}}`, `}`].join('\n'),
      );
    });

    it('should migrate an if else case as bindings', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf,NgIfElse} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-template [ngIf]="show" [ngIfElse]="tmplName"><span>Content here</span></ng-template>`,
          `</div>`,
          `<ng-template #tmplName><p>Stuff</p></ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <span>Content here</span>`,
          `  } @else {`,
          `    <p>Stuff</p>`,
          `  }`,
          `</div>\n`,
        ].join('\n'),
      );
    });


it('should migrate an if then else case as bindings', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf,NgIfElse} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-template [ngIf]="show" [ngIfElse]="elseTmpl" [ngIfThenElse]="thenTmpl">Ignore Me</ng-template>`,
          `</div>`,
          `<ng-template #elseTmpl><p>Stuff</p></ng-template>`,
          `<ng-template #thenTmpl><span>Content here</span></ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <span>Content here</span>`,
          `  } @else {`,
          `    <p>Stuff</p>`,
          `  }`,
          `</div>\n`,
        ].join('\n'),
      );
    });

    it('should migrate an if else case with condition that has `then` in the string', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf,NgIfElse} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [`<div *ngIf="!(isAuthenticated$ | async) && !reauthRequired">`, `  Hello!`, `</div>`].join(
          '\n',
        ),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `@if (!(isAuthenticated$ | async) && !reauthRequired) {`,
          `  <div>`,
          `    Hello!`,
          `  </div>`,
          `}`,
        ].join('\n'),
      );
    });

    it('should migrate an if case on a container', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-container *ngIf="show"><span>Content here</span></ng-container>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [`<div>`, `  @if (show) {`, `    <span>Content here</span>`, `  }`, `</div>`].join('\n'),
      );
    });

    it('should migrate an if case on an empty container', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-container`,
          `  *ngIf="true; then template"`,
          `></ng-container>`,
          `<ng-template #template>`,
          `  Hello!`,
          `</ng-template>  `,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe([`@if (true) {`, `  Hello!`, `}\n`].join('\n'));
    });

    it('should migrate an if case with an ng-template with i18n', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-template *ngIf="show" i18n="@@something"><span>Content here</span></ng-template>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <ng-container i18n="@@something"><span>Content here</span></ng-container>`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

    it('should migrate an if case with an ng-template with empty i18n', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-template *ngIf="show" i18n><span>Content here</span></ng-template>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <ng-container i18n><span>Content here</span></ng-container>`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

    it('should migrate an if case with an ng-container with i18n', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-container *ngIf="show" i18n="@@something"><span>Content here</span></ng-container>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <ng-container i18n="@@something"><span>Content here</span></ng-container>`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

    it('should migrate a bound if case on an ng-template with i18n', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-template`,
          `  [ngIf]="data$ | async"`,
          `  let-data="ngIf"`,
          `  i18n="@@i18n-label">`,
          `  {{ data }}`,
          `</ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `@if (data$ | async; as data) {`,
          `  <ng-container i18n="@@i18n-label">`,
          `  {{ data }}`,
          `</ng-container>`,
          `}`,
        ].join('\n'),
      );
    });


it('should migrate an if case with an ng-container with empty i18n', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-container *ngIf="show" i18n><span>Content here</span></ng-container>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <ng-container i18n><span>Content here</span></ng-container>`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

    it('should migrate a bound NgIfElse case with ng-templates and remove all unnecessary attributes', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-template`,
          `  [ngIf]="fooTemplate"`,
          `  [ngIfElse]="barTemplate"`,
          `  [ngTemplateOutlet]="fooTemplate"`,
          `></ng-template>`,
          `<ng-template #fooTemplate>Foo</ng-template>`,
          `<ng-template #barTemplate>Bar</ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `@if (fooTemplate) {`,
          `  <ng-template`,
          `    [ngTemplateOutlet]="fooTemplate"`,
          `  ></ng-template>`,
          `} @else {`,
          `  Bar`,
          `}`,
          `<ng-template #fooTemplate>Foo</ng-template>\n`,
        ].join('\n'),
      );
    });

    it('should migrate a bound NgIfThenElse case with ng-templates with i18n', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-template [ngIf]="show" [ngIfThenElse]="thenTmpl" [ngIfElse]="elseTmpl">ignored</ng-template>`,
          `</div>`,
          `<ng-template #thenTmpl i18n="@@something"><span>Content here</span></ng-template>`,
          `<ng-template #elseTmpl i18n="@@somethingElse"><p>other</p></ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <ng-container i18n="@@something"><span>Content here</span></ng-container>`,
          `  } @else {`,
          `    <ng-container i18n="@@somethingElse"><p>other</p></ng-container>`,
          `  }`,
          `</div>\n`,
        ].join('\n'),
      );
    });

    it('should migrate a bound NgIfElse case with ng-templates with i18n', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-template [ngIf]="show" [ngIfElse]="elseTmpl" i18n="@@something"><span>Content here</span></ng-template>`,
          `</div>`,
          `<ng-template #elseTmpl i18n="@@somethingElse"><p>other</p></ng-template>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <ng-container i18n="@@something"><span>Content here</span></ng-container>`,
          `  } @else {`,
          `    <ng-container i18n="@@somethingElse"><p>other</p></ng-container>`,
          `  }`,
          `</div>\n`,
        ].join('\n'),
      );
    });

    it('should migrate an NgIfElse case with ng-templates with multiple i18n attributes', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-template *ngIf="false; else barTempl" i18n="@@foo">`,
          `  Foo`,
          `</ng-template>`,
          `<ng-template i18n="@@bar" #barTempl> Bar </ng-template>`,
          `<a *ngIf="true" i18n="@@bam">Bam</a>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `@if (false) {`,
          `  <ng-container i18n="@@foo">`,
          `  Foo`,
          `</ng-container>`,
          `} @else {`,
          `  <ng-container i18n="@@bar"> Bar </ng-container>`,
          `}`,
          `@if (true) {`,
          `  <a i18n="@@bam">Bam</a>`,
          `}`,
        ].join('\n'),
      );
    });

    it('should migrate an if else case', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<span *ngIf="show; else elseBlock">Content here</span>`,
          `<ng-template #elseBlock>Else Content</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <span>Content here</span>`,
          `  } @else {`,
          `    Else Content`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

    it('should migrate an if else case with no semicolon before else', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<span *ngIf="show else elseBlock">Content here</span>`,
          `<ng-template #elseBlock>Else Content</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <span>Content here</span>`,
          `  } @else {`,
          `    Else Content`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });