it('should migrate an if then else case with no semicolons', 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 then thenTmpl else elseTmpl"></span>`,
          `<ng-template #thenTmpl><span>Content here</span></ng-template>`,
          `<ng-template #elseTmpl>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 a colon after 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: elseTmpl">Content here</span>`,
          `<ng-template #elseTmpl>Else Content</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const actual = tree.readContent('/comp.html');
      const expected = [
        `<div>`,
        `  @if (show) {`,
        `    <span>Content here</span>`,
        `  } @else {`,
        `    Else Content`,
        `  }`,
        `</div>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate an if else case with no space after ;', 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 then else case with no spaces before ;', 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;then thenBlock;else elseBlock">Ignored</span>`,
          `<ng-template #thenBlock><div>THEN Stuff</div></ng-template>`,
          `<ng-template #elseBlock>Else Content</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

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

    it('should migrate an if else case when the template is above the block', 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 #elseBlock>Else Content</ng-template>`,
          `<span *ngIf="show; else elseBlock">Content here</span>`,
          `</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 then 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; then thenBlock; else elseBlock">Ignored</span>`,
          `<ng-template #thenBlock><div>THEN Stuff</div></ng-template>`,
          `<ng-template #elseBlock>Else Content</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

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

    it('should migrate an if then else case with templates in odd places', 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 #elseBlock>Else Content</ng-template>`,
          `<span *ngIf="show; then thenBlock; else elseBlock">Ignored</span>`,
          `<ng-template #thenBlock><div>THEN Stuff</div></ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

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


it('should migrate a complex if then else case on ng-containers', 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>`,
          `  <ng-container`,
          `    *ngIf="loading; then loadingTmpl; else showTmpl"`,
          `  >`,
          `  </ng-container>`,
          `<ng-template #showTmpl>`,
          `  <ng-container`,
          `    *ngIf="selected; else emptyTmpl"`,
          `  >`,
          `    <div></div>`,
          `  </ng-container>`,
          `</ng-template>`,
          `<ng-template #emptyTmpl>`,
          `  Empty`,
          `</ng-template>`,
          `</ng-container>`,
          `<ng-template #loadingTmpl>`,
          `  <p>Loading</p>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<ng-container>`,
          `  @if (loading) {`,
          `    <p>Loading</p>`,
          `  } @else {`,
          `    @if (selected) {`,
          `      <div></div>`,
          `    } @else {`,
          `      Empty`,
          `    }`,
          `  }`,
          `</ng-container>\n`,
        ].join('\n'),
      );
    });

    it('should migrate but not remove ng-templates when referenced elsewhere', 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; then thenBlock; else elseBlock">Ignored</span>`,
          `<ng-template #thenBlock><div>THEN Stuff</div></ng-template>`,
          `<ng-template #elseBlock>Else Content</ng-template>`,
          `</div>`,
          `<ng-container *ngTemplateOutlet="elseBlock"></ng-container>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @if (show) {`,
          `    <div>THEN Stuff</div>`,
          `  } @else {`,
          `    Else Content`,
          `  }`,
          `  <ng-template #elseBlock>Else Content</ng-template>`,
          `</div>`,
          `<ng-container *ngTemplateOutlet="elseBlock"></ng-container>`,
        ].join('\n'),
      );
    });

    it('should not remove ng-templates used by other directives', 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 #blockUsedElsewhere><div>Block</div></ng-template>`,
          `<ng-container *ngTemplateOutlet="blockUsedElsewhere"></ng-container>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<ng-template #blockUsedElsewhere><div>Block</div></ng-template>`,
          `<ng-container *ngTemplateOutlet="blockUsedElsewhere"></ng-container>`,
        ].join('\n'),
      );
    });

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

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          user$ = of({ name: 'Jane' }})
        }
      `,
      );

      writeFile('/comp.html', `<div *ngIf="user$ | async as user">{{ user.name }}</div>`);

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

      expect(content).toBe(`@if (user$ | async; as user) {<div>{{ user.name }}</div>}`);
    });

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

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          user$ = of({ name: 'Jane' }})
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<div *ngIf="user$ | async as user; else noUserBlock">{{ user.name }}</div>`,
          `<ng-template #noUserBlock>No user</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @if (user$ | async; as user) {`,
          `    <div>{{ user.name }}</div>`,
          `  } @else {`,
          `    No user`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

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

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          user$ = of({ name: 'Jane' }})
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<div *ngIf="user$ | async as user; else noUserBlock;">{{ user.name }}</div>`,
          `<ng-template #noUserBlock>No user</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @if (user$ | async; as user) {`,
          `    <div>{{ user.name }}</div>`,
          `  } @else {`,
          `    No user`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

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

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          user$ = of({ name: 'Jane' }})
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<div *ngIf="user of users; else noUserBlock; let tooltipContent;">{{ user.name }}</div>`,
          `<ng-template #noUserBlock>No user</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @if (user of users; as tooltipContent) {`,
          `    <div>{{ user.name }}</div>`,
          `  } @else {`,
          `    No user`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });


it('should migrate if/else with let variable in wrong place with no semicolons', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          user$ = of({ name: 'Jane' }})
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<div *ngIf="user of users; let tooltipContent else noUserBlock">{{ user.name }}</div>`,
          `<ng-template #noUserBlock>No user</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @if (user of users; as tooltipContent) {`,
          `    <div>{{ user.name }}</div>`,
          `  } @else {`,
          `    No user`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

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

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          user$ = of({ name: 'Jane' }})
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `<ng-container *ngIf="user$ | async as user; then userBlock; else noUserBlock">Ignored</ng-container>`,
          `<ng-template #userBlock>User</ng-template>`,
          `<ng-template #noUserBlock>No user</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @if (user$ | async; as user) {`,
          `    User`,
          `  } @else {`,
          `    No user`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

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

        function foo() {
          @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 a nested class', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';
        function foo() {
          @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>`',
      );
    });
  });


describe('ngFor', () => {
    it('should migrate an inline template', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let item of items">{{item.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (item of items; track item) {<li>{{item.text}}</li>}</ul>`',
      );
    });

    it('should migrate multiple inline templates in the same file', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }
        const items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let item of items">{{item.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = s;
        }

        @Component({
          imports: [NgFor],
          template: \`<article><div *ngFor="let item of items">{{item.text}}</div></article>\`
        })
        class OtherComp {
          items: Item[] = s;
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (item of items; track item) {<li>{{item.text}}</li>}</ul>`',
      );
      expect(content).toContain(
        'template: `<article>@for (item of items; track item) {<div>{{item.text}}</div>}</article>`',
      );
    });

    it('should migrate an external template', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        interface Item {
          id: number;
          text: string;
        }
        const items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          items: Item[] = s;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [`<ul>`, `<li *ngFor="let item of items">{{item.text}}</li>`, `</ul>`].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<ul>`,
          `  @for (item of items; track item) {`,
          `    <li>{{item.text}}</li>`,
          `  }`,
          `</ul>`,
        ].join('\n'),
      );
    });

    it('should migrate a template referenced by multiple components', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        interface Item {
          id: number;
          text: string;
        }
        const items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];

        @Component({
          templateUrl: './comp.html'
        })
        class Comp {
          items: Item[] = s;
        }
      `,
      );

      writeFile(
        '/other-comp.ts',
        `
        import {Component} from '@angular/core';
        interface Item {
          id: number;
          text: string;
        }
        const items: Item[] = [{id: 3, text: 'things'},{id: 4, text: 'yup'}];

        @Component({
          templateUrl: './comp.html'
        })
        class OtherComp {
          items: Item[] = s;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [`<ul>`, `<li *ngFor="let item of items">{{item.text}}</li>`, `</ul>`].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<ul>`,
          `  @for (item of items; track item) {`,
          `    <li>{{item.text}}</li>`,
          `  }`,
          `</ul>`,
        ].join('\n'),
      );
    });

    it('should migrate with a trackBy function', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items; trackBy: trackMeFn;">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track trackMeFn($index, itm)) {<li>{{itm.text}}</li>}</ul>`',
      );
    });

    it('should migrate with an index alias', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items; let index = index">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track itm; let index = $index) {<li>{{itm.text}}</li>}</ul>`',
      );
    });

    it('should migrate with a comma-separated index alias', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items, let index = index">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track itm; let index = $index) {<li>{{itm.text}}</li>}</ul>`',
      );
    });


it('should migrate with an old style alias', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<tbody>`,
          `  <tr *ngFor="let row of field(); let y = index; trackBy: trackByIndex">`,
          `    <td`,
          `      *ngFor="let cell of row; let x = index; trackBy: trackByIndex"`,
          `    ></td>`,
          `  </tr>`,
          `</tbody>`,
        ].join('\n'),
      );

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

      const expected = [
        `<tbody>`,
        `  @for (row of field(); track trackByIndex(y, row); let y = $index) {`,
        `    <tr>`,
        `      @for (cell of row; track trackByIndex(x, cell); let x = $index) {`,
        `        <td`,
        `        ></td>`,
        `      }`,
        `    </tr>`,
        `  }`,
        `</tbody>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate an index alias after an expression containing commas', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of foo({a: 1, b: 2}, [1, 2]), let index = index">{{itm.text}}</li></ul>\`
        })
        class Comp {}
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of foo({a: 1, b: 2}, [1, 2]); track itm; let index = $index) {<li>{{itm.text}}</li>}</ul>`',
      );
    });

    it('should migrate with an index alias with no spaces', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items; let index=index">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track itm; let index = $index) {<li>{{itm.text}}</li>}</ul>`',
      );
    });

    it('should migrate with alias declared with as', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items; index as myIndex">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track itm; let myIndex = $index) {<li>{{itm.text}}</li>}</ul>`',
      );
    });

    it('should migrate with alias declared with a comma-separated `as` expression', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items, index as myIndex">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track itm; let myIndex = $index) {<li>{{itm.text}}</li>}</ul>`',
      );
    });

    it('should not generate aliased variables declared via the `as` syntax with the same name as the original', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<div *ngFor="let item of items; index as $index;">{{$index}}</div>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'}, {id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `@for (item of items; track item) {<div>{{$index}}</div>}`',
      );
    });

    it('should not generate aliased variables declared via the `let` syntax with the same name as the original', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<div *ngFor="let item of items; let $index = index">{{$index}}</div>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'}, {id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `@for (item of items; track item) {<div>{{$index}}</div>}`',
      );
    });

    it('should migrate with a trackBy function and an aliased index', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items; trackBy: trackMeFn; index as i">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track trackMeFn(i, itm); let i = $index) {<li>{{itm.text}}</li>}</ul>`',
      );
    });


it('should migrate with multiple aliases', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of items; let count = count; let first = first; let last = last; let ev = even; let od = odd;">{{itm.text}}</li></ul>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (itm of items; track itm; let count = $count; let first = $first; let last = $last; let ev = $even; let od = $odd) {<li>{{itm.text}}</li>}</ul>`',
      );
    });

    it('should remove unneeded ng-containers', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ng-container *ngFor="let item of items"><p>{{item.text}}</p></ng-container>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `@for (item of items; track item) {<p>{{item.text}}</p>}`',
      );
    });

    it('should leave ng-containers with additional attributes', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          template: \`<ng-container *ngFor="let item of items" [bindMe]="stuff"><p>{{item.text}}</p></ng-container>\`
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

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

      expect(content).toContain(
        'template: `@for (item of items; track item) {<ng-container [bindMe]="stuff"><p>{{item.text}}</p></ng-container>}`',
      );
    });

    it('should migrate a nested class', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        function foo() {
          @Component({
            imports: [NgFor],
            template: \`<ul><li *ngFor="let item of items">{{item.text}}</li></ul>\`
          })
          class Comp {
            items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
          }
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (item of items; track item) {<li>{{item.text}}</li>}</ul>`',
      );
    });

    it('should migrate a nested class', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }
        function foo() {
          @Component({
            imports: [NgFor],
            template: \`<ul><li *ngFor="let item of items">{{item.text}}</li></ul>\`
          })
          class Comp {
            items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
          }
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<ul>@for (item of items; track item) {<li>{{item.text}}</li>}</ul>`',
      );
    });

    it('should migrate an ngFor with quoted semicolon in expression', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of '1;2;3'">{{itm}}</li></ul>\`
        })
        class Comp {}
      `,
      );

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

      expect(content).toContain(
        "template: `<ul>@for (itm of '1;2;3'; track itm) {<li>{{itm}}</li>}</ul>`",
      );
    });

    it('should migrate an ngFor with quoted semicolon in expression', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';

        @Component({
          imports: [NgFor],
          template: \`<ul><li *ngFor="let itm of '1,2,3'">{{itm}}</li></ul>\`
        })
        class Comp {}
      `,
      );

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

      expect(content).toContain(
        "template: `<ul>@for (itm of '1,2,3'; track itm) {<li>{{itm}}</li>}</ul>`",
      );
    });

    it('should migrate ngForTemplate', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-container *ngFor="let item of things; template: itemTemplate"></ng-container>`,
          `<ng-container *ngFor="let item of items; template: itemTemplate"></ng-container>`,
          `<ng-template #itemTemplate let-item>`,
          `  <p>Stuff</p>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      const expected = [
        `@for (item of things; track item) {`,
        `  <ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item}"></ng-container>`,
        `}`,
        `@for (item of items; track item) {`,
        `  <ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item}"></ng-container>`,
        `}`,
        `<ng-template #itemTemplate let-item>`,
        `  <p>Stuff</p>`,
        `</ng-template>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });


it('should migrate ngForTemplate when template is only used once', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-container *ngFor="let item of items; template: itemTemplate"></ng-container>`,
          `<ng-template #itemTemplate let-item>`,
          `  <p>Stuff</p>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      const expected = [`@for (item of items; track item) {`, `  <p>Stuff</p>`, `}\n`].join('\n');

      expect(actual).toBe(expected);
    });
  });


describe('ngForOf', () => {
    it('should migrate a basic ngForOf', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor,NgForOf],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<tbody>`,
          `  <ng-template ngFor let-rowData [ngForOf]="things">`,
          `    <tr><td>{{rowData}}</td></tr>`,
          `  </ng-template>`,
          `</tbody>`,
        ].join('\n'),
      );

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

      const expected = [
        `<tbody>`,
        `  @for (rowData of things; track rowData) {`,
        `    <tr><td>{{rowData}}</td></tr>`,
        `  }`,
        `</tbody>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate ngForOf with an alias', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor,NgForOf],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<tbody>`,
          `  <ng-template ngFor let-rowData let-rowIndex="index" [ngForOf]="things">`,
          `    <tr><td>{{rowIndex}}</td><td>{{rowData}}</td></tr>`,
          `  </ng-template>`,
          `</tbody>`,
        ].join('\n'),
      );

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

      const expected = [
        `<tbody>`,
        `  @for (rowData of things; track rowData; let rowIndex = $index) {`,
        `    <tr><td>{{rowIndex}}</td><td>{{rowData}}</td></tr>`,
        `  }`,
        `</tbody>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate ngForOf with track by', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor,NgForOf],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<tbody>`,
          `  <ng-template ngFor let-rowData [ngForOf]="things" [ngForTrackBy]="trackMe">`,
          `    <tr><td>{{rowData}}</td></tr>`,
          `  </ng-template>`,
          `</tbody>`,
        ].join('\n'),
      );

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

      const expected = [
        `<tbody>`,
        `  @for (rowData of things; track trackMe($index, rowData)) {`,
        `    <tr><td>{{rowData}}</td></tr>`,
        `  }`,
        `</tbody>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate ngForOf with track by and alias', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor,NgForOf],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<tbody>`,
          `  <ng-template ngFor let-rowData let-rowIndex="index" [ngForOf]="things" [ngForTrackBy]="trackMe">`,
          `    <tr><td>{{rowIndex}}</td><td>{{rowData}}</td></tr>`,
          `  </ng-template>`,
          `</tbody>`,
        ].join('\n'),
      );

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

      const expected = [
        `<tbody>`,
        `  @for (rowData of things; track trackMe(rowIndex, rowData); let rowIndex = $index) {`,
        `    <tr><td>{{rowIndex}}</td><td>{{rowData}}</td></tr>`,
        `  }`,
        `</tbody>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate ngFor with a long ternary and trackby', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor,NgForOf],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div`,
          `  *ngFor="`,
          `    let item of section === 'manage'`,
          `      ? filteredPermissions?.manage`,
          `      : section === 'customFields'`,
          `      ? filteredPermissions?.customFields`,
          `      : section === 'createAndDelete'`,
          `      ? filteredPermissions?.createAndDelete`,
          `      : filteredPermissions?.team;`,
          `    trackBy: trackById`,
          `  "`,
          `>`,
          `  {{ item }}`,
          `</div>`,
        ].join('\n'),
      );

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

      const expected = [
        `@for (`,
        `  item of section === 'manage'`,
        `  ? filteredPermissions?.manage`,
        `  : section === 'customFields'`,
        `  ? filteredPermissions?.customFields`,
        `  : section === 'createAndDelete'`,
        `  ? filteredPermissions?.createAndDelete`,
        `  : filteredPermissions?.team; track trackById($index,`,
        `  item)) {`,
        `  <div`,
        `    >`,
        `    {{ item }}`,
        `  </div>`,
        `}`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate ngForOf with track by and multiple aliases', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor,NgForOf],
          templateUrl: 'comp.html',
        })
        class Comp {
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<tbody>`,
          `  <ng-template ngFor let-rowData let-rowIndex="index" let-rCount="count" [ngForOf]="things" [ngForTrackBy]="trackMe">`,
          `    <tr><td>{{rowIndex}}</td><td>{{rowData}}</td></tr>`,
          `  </ng-template>`,
          `</tbody>`,
        ].join('\n'),
      );

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

      const expected = [
        `<tbody>`,
        `  @for (rowData of things; track trackMe(rowIndex, rowData); let rowIndex = $index; let rCount = $count) {`,
        `    <tr><td>{{rowIndex}}</td><td>{{rowData}}</td></tr>`,
        `  }`,
        `</tbody>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });
  });


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

        @Component({
          template: \`<div [ngSwitch]="testOpts">` +
          `<p *ngSwitchCase="1">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 (1) {
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 {ngSwitch, ngSwitchCase} from '@angular/common';

        @Component({
          template: \`<div [ngSwitch]="testOpts">` +
          `<p *ngSwitchCase="1">Option 1</p>` +
          `<p *ngSwitchCase="2">Option 2</p>` +
          `</div>\`
        })
        class Comp1 {
          testOpts = "1";
        }

        @Component({
          template: \`<div [ngSwitch]="choices">` +
          `<p *ngSwitchCase="A">Choice A</p>` +
          `<p *ngSwitchCase="B">Choice B</p>` +
          `<p *ngSwitchDefault>Choice Default</p>` +
          `</div>\`
        })
        class Comp2 {
          choices = "C";
        }
      `,
      );

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

      expect(content).toContain(
        'template: `<div>@switch (testOpts) { @case (1) { <p>Option 1</p> } @case (2) { <p>Option 2</p> }}</div>`',
      );
      expect(content).toContain(
        'template: `<div>@switch (choices) { @case (A) { <p>Choice A</p> } @case (B) { <p>Choice B</p> } @default { <p>Choice Default</p> }}</div>`',
      );
    });

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

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

      writeFile(
        '/comp.html',
        [
          `<div [ngSwitch]="testOpts">`,
          `<p *ngSwitchCase="1">Option 1</p>`,
          `<p *ngSwitchCase="2">Option 2</p>`,
          `<p *ngSwitchDefault>Option 3</p>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');
      expect(content).toBe(
        [
          `<div>`,
          `  @switch (testOpts) {`,
          `    @case (1) {`,
          `      <p>Option 1</p>`,
          `    }`,
          `    @case (2) {`,
          `      <p>Option 2</p>`,
          `    }`,
          `    @default {`,
          `      <p>Option 3</p>`,
          `    }`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

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

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

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

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

      writeFile(
        '/comp.html',
        [
          `<div [ngSwitch]="testOpts">`,
          `<p *ngSwitchCase="1">Option 1</p>`,
          `<p *ngSwitchCase="2">Option 2</p>`,
          `<p *ngSwitchDefault>Option 3</p>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @switch (testOpts) {`,
          `    @case (1) {`,
          `      <p>Option 1</p>`,
          `    }`,
          `    @case (2) {`,
          `      <p>Option 2</p>`,
          `    }`,
          `    @default {`,
          `      <p>Option 3</p>`,
          `    }`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });

    it('should remove unnecessary ng-containers', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {ngSwitch, ngSwitchCase} from '@angular/common';

        @Component({
          template: \`<div [ngSwitch]="testOpts">` +
          `<ng-container *ngSwitchCase="1"><p>Option 1</p></ng-container>` +
          `<ng-container *ngSwitchCase="2"><p>Option 2</p></ng-container>` +
          `</div>\`
        })
        class Comp {
          testOpts = "1";
        }
      `,
      );

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

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

    it('should remove unnecessary ng-container on ngswitch', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {ngSwitch, ngSwitchCase} from '@angular/common';

        @Component({
          template: \`<div>` +
          `<ng-container [ngSwitch]="testOpts">` +
          `<p *ngSwitchCase="1">Option 1</p>` +
          `<p *ngSwitchCase="2">Option 2</p>` +
          `</ng-container>` +
          `</div>\`
        })
        class Comp {
          testOpts = "1";
        }
      `,
      );

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

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

    it('should handle cases with missing star', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {ngSwitch, ngSwitchCase} from '@angular/common';

        @Component({
          template: \`<div [ngSwitch]="testOpts">` +
          `<ng-template ngSwitchCase="1"><p>Option 1</p></ng-template>` +
          `<ng-template ngSwitchCase="2"><p>Option 2</p></ng-template>` +
          `<ng-template ngSwitchDefault><p>Option 3</p></ng-template>` +
          `</div>\`
        })
        class Comp {
          testOpts = "1";
        }
      `,
      );

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

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


it('should handle cases with binding', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {ngSwitch, ngSwitchCase} from '@angular/common';

        @Component({
          template: \`<div [ngSwitch]="testOpts">` +
          `<ng-template [ngSwitchCase]="1"><p>Option 1</p></ng-template>` +
          `<ng-template [ngSwitchCase]="2"><p>Option 2</p></ng-template>` +
          `<ng-template ngSwitchDefault><p>Option 3</p></ng-template>` +
          `</div>\`
        })
        class Comp {
          testOpts = "1";
        }
      `,
      );

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

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

    it('should handle empty default cases', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {ngSwitch, ngSwitchCase} from '@angular/common';

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

      writeFile(
        '/comp.html',
        [
          `<ng-container [ngSwitch]="type">`,
          `<ng-container *ngSwitchCase="'foo'"> Foo </ng-container>`,
          `<ng-container *ngSwitchCase="'bar'"> Bar </ng-container>`,
          `<ng-container *ngSwitchDefault></ng-container>`,
          `</ng-container>`,
        ].join('\n'),
      );

      await runMigration();
      const actual = tree.readContent('/comp.html');
      const expected = [
        `\n@switch (type) {`,
        `  @case ('foo') {`,
        `    Foo`,
        `  }`,
        `  @case ('bar') {`,
        `    Bar`,
        `  }`,
        `  @default {`,
        `  }`,
        `}\n`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate a nested class', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {ngSwitch, ngSwitchCase} from '@angular/common';
        function foo() {
          @Component({
            template: \`<div [ngSwitch]="testOpts">` +
          `<p *ngSwitchCase="1">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: `<div>@switch (testOpts) { @case (1) { <p>Option 1</p> } @case (2) { <p>Option 2</p> }}</div>`',
      );
    });

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

      @Component({
        imports: [NgFor, NgIf],
        templateUrl: './comp.html'
      })
      class Comp {
        show = false;
        nest = true;
        again = true;
        more = true;
      }`,
      );

      writeFile(
        '/comp.html',
        [
          `<div [ngSwitch]="thing">`,
          `  <div *ngSwitchCase="'item'" [ngSwitch]="anotherThing">`,
          `    <img *ngSwitchCase="'png'" src="/img.png" alt="PNG" />`,
          `    <img *ngSwitchDefault src="/default.jpg" alt="default" />`,
          `  </div>`,
          `  <img *ngSwitchDefault src="/default.jpg" alt="default" />`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<div>`,
          `  @switch (thing) {`,
          `    @case ('item') {`,
          `      <div>`,
          `        @switch (anotherThing) {`,
          `          @case ('png') {`,
          `            <img src="/img.png" alt="PNG" />`,
          `          }`,
          `          @default {`,
          `            <img src="/default.jpg" alt="default" />`,
          `          }`,
          `        }`,
          `      </div>`,
          `    }`,
          `    @default {`,
          `      <img src="/default.jpg" alt="default" />`,
          `    }`,
          `  }`,
          `</div>`,
        ].join('\n'),
      );
    });
  });


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

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
          nest = true;
          again = true;
          more = true;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ul *ngIf="show">`,
          `<li *ngFor="let h of heroes" [ngValue]="h">{{h.name}} ({{h.emotion}})</li>`,
          `</ul>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (show) {`,
          `  <ul>`,
          `    @for (h of heroes; track h) {`,
          `      <li [ngValue]="h">{{h.name}} ({{h.emotion}})</li>`,
          `    }`,
          `  </ul>`,
          `}`,
        ].join('\n'),
      );
    });

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

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
          heroes = [{name: 'cheese', emotion: 'happy'},{name: 'stuff', emotion: 'sad'}];
          show = true;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<select id="hero">`,
          `<span *ngFor="let h of heroes">`,
          `<span *ngIf="show">`,
          `<option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>`,
          `</span>`,
          `</span>`,
          `</select>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<select id="hero">`,
          `  @for (h of heroes; track h) {`,
          `    <span>`,
          `      @if (show) {`,
          `        <span>`,
          `          <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>`,
          `        </span>`,
          `      }`,
          `    </span>`,
          `  }`,
          `</select>`,
        ].join('\n'),
      );
    });

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

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
          heroes = [{name: 'cheese', emotion: 'happy'},{name: 'stuff', emotion: 'sad'}];
          show = true;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<select id="hero">`,
          `<ng-container *ngFor="let h of heroes">`,
          `<ng-container *ngIf="show">`,
          `<option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>`,
          `</ng-container>`,
          `</ng-container>`,
          `</select>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `<select id="hero">`,
          `  @for (h of heroes; track h) {`,
          `    @if (show) {`,
          `      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>`,
          `    }`,
          `  }`,
          `</select>`,
        ].join('\n'),
      );
    });

    it('should migrate an inline template with if and for loops', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf, NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
          nest = true;
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div *ngIf="show">`,
          `<ul *ngIf="nest">`,
          `<li *ngFor="let item of items">{{item.text}}</li>`,
          `</ul>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (show) {`,
          `  <div>`,
          `    @if (nest) {`,
          `      <ul>`,
          `        @for (item of items; track item) {`,
          `          <li>{{item.text}}</li>`,
          `        }`,
          `      </ul>`,
          `    }`,
          `  </div>`,
          `}`,
        ].join('\n'),
      );
    });

    it('should migrate template with if, else, and for loops', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf, NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
          nest = true;
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div *ngIf="show">`,
          `<ul *ngIf="nest; else elseTmpl">`,
          `<li *ngFor="let item of items">{{item.text}}</li>`,
          `</ul>`,
          `<ng-template #elseTmpl><p>Else content</p></ng-template>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (show) {`,
          `  <div>`,
          `    @if (nest) {`,
          `      <ul>`,
          `        @for (item of items; track item) {`,
          `          <li>{{item.text}}</li>`,
          `        }`,
          `      </ul>`,
          `    } @else {`,
          `      <p>Else content</p>`,
          `    }`,
          `  </div>`,
          `}`,
        ].join('\n'),
      );
    });


it('should migrate an inline template with if, for loops, and switches', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf, NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
          show = false;
          nest = true;
          again = true;
          more = true;
          items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
          testOpts = 2;
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div *ngIf="show">`,
          `<div *ngIf="again">`,
          `<div *ngIf="more">`,
          `<div [ngSwitch]="testOpts">`,
          `<p *ngSwitchCase="1">Option 1</p>`,
          `<p *ngSwitchCase="2">Option 2</p>`,
          `<p *ngSwitchDefault>Option 3</p>`,
          `</div>`,
          `</div>`,
          `</div>`,
          `<ul *ngIf="nest">`,
          `<li *ngFor="let item of items">{{item.text}}</li>`,
          `</ul>`,
          `</div>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (show) {`,
          `  <div>`,
          `    @if (again) {`,
          `      <div>`,
          `        @if (more) {`,
          `          <div>`,
          `            <div>`,
          `              @switch (testOpts) {`,
          `                @case (1) {`,
          `                  <p>Option 1</p>`,
          `                }`,
          `                @case (2) {`,
          `                  <p>Option 2</p>`,
          `                }`,
          `                @default {`,
          `                  <p>Option 3</p>`,
          `                }`,
          `              }`,
          `            </div>`,
          `          </div>`,
          `        }`,
          `      </div>`,
          `    }`,
          `    @if (nest) {`,
          `      <ul>`,
          `        @for (item of items; track item) {`,
          `          <li>{{item.text}}</li>`,
          `        }`,
          `      </ul>`,
          `    }`,
          `  </div>`,
          `}`,
        ].join('\n'),
      );
    });

    it('complicated case', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf, NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div class="docs-breadcrumb" *ngFor="let breadcrumb of breadcrumbItems()">`,
          `<ng-container *ngIf="breadcrumb.path; else breadcrumbWithoutLinkTemplate">`,
          `<ng-container *ngIf="breadcrumb.isExternal; else internalLinkTemplate">`,
          `<a [href]="breadcrumb.path">{{ breadcrumb.label }}</a>`,
          `</ng-container>`,
          `<ng-template #internalLinkTemplate>`,
          `<a [routerLink]="'/' + breadcrumb.path">{{ breadcrumb.label }}</a>`,
          `</ng-template>`,
          `</ng-container>`,
          `<ng-template #breadcrumbWithoutLinkTemplate>`,
          `<span>{{ breadcrumb.label }}</span>`,
          `</ng-template>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();
      const content = tree.readContent('/comp.html');
      const result = [
        `@for (breadcrumb of breadcrumbItems(); track breadcrumb) {`,
        `  <div class="docs-breadcrumb">`,
        `    @if (breadcrumb.path) {`,
        `      @if (breadcrumb.isExternal) {`,
        `        <a [href]="breadcrumb.path">{{ breadcrumb.label }}</a>`,
        `      } @else {`,
        `        <a [routerLink]="'/' + breadcrumb.path">{{ breadcrumb.label }}</a>`,
        `      }`,
        `    } @else {`,
        `      <span>{{ breadcrumb.label }}</span>`,
        `    }`,
        `  </div>`,
        `}`,
      ].join('\n');

      expect(content).toBe(result);
    });


it('long file', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf, NgFor} from '@angular/common';
        interface Item {
          id: number;
          text: string;
        }

        @Component({
          imports: [NgFor, NgIf],
          templateUrl: './comp.html'
        })
        class Comp {
        }
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div class="class">`,
          `<iframe `,
          `#preview `,
          `class="special-class" `,
          `*ngIf="stuff" `,
          `></iframe>`,
          `<ng-container *ngIf="shouldDoIt">`,
          `<ng-container `,
          `*ngComponentOutlet="outletComponent; inputs: {errorMessage: errorMessage()}" `,
          `/>`,
          `</ng-container>`,
          `<div `,
          `class="what"`,
          `*ngIf="`,
          `shouldWhat`,
          `"`,
          `>`,
          `<ng-container [ngSwitch]="currentCase()">`,
          `<span `,
          `class="case-stuff"`,
          `*ngSwitchCase="A"`,
          `>`,
          `Case A`,
          `</span>`,
          `<span class="b-class" *ngSwitchCase="B">`,
          `Case B`,
          `</span>`,
          `<span `,
          `class="thing-1" `,
          `*ngSwitchCase="C" `,
          `>`,
          `Case C`,
          `</span>`,
          `<span `,
          `class="thing-2"`,
          `*ngSwitchCase="D"`,
          `>`,
          `Case D`,
          `</span>`,
          `<span `,
          `class="thing-3" `,
          `*ngSwitchCase="E" `,
          `>`,
          `Case E`,
          `</span>`,
          `</ng-container>`,
          `<progress [value]="progress()" [max]="ready"></progress>`,
          `</div>`,
          `</div>`,
        ].join('\n'),
      );

      await runMigration();

      const content = tree.readContent('/comp.html');
      const result = [
        `<div class="class">`,
        `  @if (stuff) {`,
        `    <iframe`,
        `      #preview`,
        `      class="special-class"`,
        `    ></iframe>`,
        `  }`,
        `  @if (shouldDoIt) {`,
        `    <ng-container`,
        `      *ngComponentOutlet="outletComponent; inputs: {errorMessage: errorMessage()}"`,
        `      />`,
        `  }`,
        `  @if (`,
        `    shouldWhat`,
        `    ) {`,
        `    <div`,
        `      class="what"`,
        `      >`,
        `      @switch (currentCase()) {`,
        `        @case (A) {`,
        `          <span`,
        `            class="case-stuff"`,
        `            >`,
        `            Case A`,
        `          </span>`,
        `        }`,
        `        @case (B) {`,
        `          <span class="b-class">`,
        `            Case B`,
        `          </span>`,
        `        }`,
        `        @case (C) {`,
        `          <span`,
        `            class="thing-1"`,
        `            >`,
        `            Case C`,
        `          </span>`,
        `        }`,
        `        @case (D) {`,
        `          <span`,
        `            class="thing-2"`,
        `            >`,
        `            Case D`,
        `          </span>`,
        `        }`,
        `        @case (E) {`,
        `          <span`,
        `            class="thing-3"`,
        `            >`,
        `            Case E`,
        `          </span>`,
        `        }`,
        `      }`,
        `      <progress [value]="progress()" [max]="ready"></progress>`,
        `    </div>`,
        `  }`,
        `</div>`,
      ].join('\n');

      expect(content).toBe(result);
    });

    it('should handle empty cases safely without offset issues', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          selector: 'declare-comp',
          templateUrl: 'comp.html',
        })
        class DeclareComp {}
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<ng-container *ngIf="generic; else specific">`,
          `  <ng-container [ngSwitch]="dueWhen">`,
          `    <ng-container *ngSwitchCase="due.NOW">`,
          `      <p>Now></p>`,
          `    </ng-container>`,
          `    <ng-container *ngSwitchCase="due.SOON">`,
          `      <p>Soon></p>`,
          `    </ng-container>`,
          `    <ng-container *ngSwitchDefault></ng-container>`,
          `  </ng-container>`,
          `</ng-container>`,
          `<ng-template #specific>`,
          `  <ng-container [ngSwitch]="dueWhen">`,
          `    <ng-container *ngSwitchCase="due.NOW">`,
          `      <p>Now></p>`,
          `    </ng-container>`,
          `    <ng-container *ngSwitchCase="due.SOON">`,
          `      <p>Soon></p>`,
          `    </ng-container>`,
          `    <ng-container *ngSwitchDefault>`,
          `      <p>Default></p>`,
          `    </ng-container>`,
          `  </ng-container>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      const expected = [
        `@if (generic) {`,
        `  @switch (dueWhen) {`,
        `    @case (due.NOW) {`,
        `      <p>Now></p>`,
        `    }`,
        `    @case (due.SOON) {`,
        `      <p>Soon></p>`,
        `    }`,
        `    @default {`,
        `    }`,
        `  }`,
        `} @else {`,
        `  @switch (dueWhen) {`,
        `    @case (due.NOW) {`,
        `      <p>Now></p>`,
        `    }`,
        `    @case (due.SOON) {`,
        `      <p>Soon></p>`,
        `    }`,
        `    @default {`,
        `      <p>Default></p>`,
        `    }`,
        `  }`,
        `}\n`,
      ].join('\n');

      expect(actual).toBe(expected);
    });
  });


it('should move templates after they were migrated to new syntax', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          selector: 'declare-comp',
          templateUrl: 'comp.html',
        })
        class DeclareComp {}
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `  <ng-container *ngIf="cond; else testTpl">`,
          `    bla bla`,
          `  </ng-container>`,
          `</div>`,
          `<ng-template #testTpl>`,
          `  <div class="test" *ngFor="let item of items"></div>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      const expected = [
        `<div>`,
        `  @if (cond) {`,
        `    bla bla`,
        `  } @else {`,
        `    @for (item of items; track item) {`,
        `      <div class="test"></div>`,
        `    }`,
        `  }`,
        `</div>\n`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should preserve i18n attribute on ng-templates in an if/else', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          selector: 'declare-comp',
          templateUrl: 'comp.html',
        })
        class DeclareComp {}
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `  <ng-container *ngIf="cond; else testTpl">`,
          `    bla bla`,
          `  </ng-container>`,
          `</div>`,
          `<ng-template #testTpl i18n="@@test_key">`,
          `  <div class="test" *ngFor="let item of items"></div>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      const expected = [
        `<div>`,
        `  @if (cond) {`,
        `    bla bla`,
        `  } @else {`,
        `    <ng-container i18n="@@test_key">`,
        `  @for (item of items; track item) {`,
        `<div class="test"></div>`,
        `}`,
        `</ng-container>`,
        `  }`,
        `</div>\n`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should migrate multiple if/else using the same ng-template', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          selector: 'declare-comp',
          templateUrl: 'comp.html',
        })
        class DeclareComp {}
      `,
      );

      writeFile(
        '/comp.html',
        [
          `<div>`,
          `  <div *ngIf="hasPermission; else noPermission">presentation</div>`,
          `  <div *ngIf="someOtherPermission; else noPermission">presentation</div>`,
          `</div>`,
          `<ng-template #noPermission>`,
          `  <p>No Permissions</p>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      const expected = [
        `<div>`,
        `  @if (hasPermission) {`,
        `    <div>presentation</div>`,
        `  } @else {`,
        `    <p>No Permissions</p>`,
        `  }`,
        `  @if (someOtherPermission) {`,
        `    <div>presentation</div>`,
        `  } @else {`,
        `    <p>No Permissions</p>`,
        `  }`,
        `</div>\n`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should remove a template with no overlap with following elements', 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="stuff">`,
          `  <div>`,
          `    <ul>`,
          `      <li>`,
          `        <span>`,
          `          <ng-container *ngIf="things; else elseTmpl">`,
          `            <p>Hmm</p>`,
          `          </ng-container>`,
          `          <ng-template #elseTmpl> 0 </ng-template></span>`,
          `      </li>`,
          `    </ul>`,
          `  </div>`,
          `</ng-container>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (stuff) {`,
          `  <div>`,
          `    <ul>`,
          `      <li>`,
          `        <span>`,
          `          @if (things) {`,
          `            <p>Hmm</p>`,
          `          } @else {`,
          `            0`,
          `          }`,
          `        </span>`,
          `      </li>`,
          `    </ul>`,
          `  </div>`,
          `}`,
        ].join('\n'),
      );
    });

    it('should migrate nested template usage correctly', 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="!(condition$ | async); else template">`,
          `  Hello!`,
          `</ng-container>`,
          `<ng-template #bar>Bar</ng-template>`,
          `<ng-template #foo>Foo</ng-template>`,
          `<ng-template #template>`,
          `  <ng-container`,
          `    *ngIf="(foo$ | async) === true; then foo; else bar"`,
          `  ></ng-container>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (!(condition$ | async)) {`,
          `  Hello!`,
          `} @else {`,
          `  @if ((foo$ | async) === true) {`,
          `    Foo`,
          `  } @else {`,
          `    Bar`,
          `  }`,
          `}\n`,
        ].join('\n'),
      );
    });

    it('should add an ngTemplateOutlet when the template placeholder does not match a 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',
        [`<button *ngIf="active; else defaultTemplate">`, `  Hello!`, `</button>`].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (active) {`,
          `  <button>`,
          `    Hello!`,
          `  </button>`,
          `} @else {`,
          `  <ng-template [ngTemplateOutlet]="defaultTemplate"></ng-template>`,
          `}`,
        ].join('\n'),
      );
    });


it('should handle OR logic in ngIf 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 *ngIf="condition; else titleTemplate || defaultTemplate">Hello!</div>`,
          `<ng-template #defaultTemplate> Default </ng-template>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (condition) {`,
          `  <div>Hello!</div>`,
          `} @else {`,
          `  <ng-template [ngTemplateOutlet]="titleTemplate || defaultTemplate"></ng-template>`,
          `}`,
          `<ng-template #defaultTemplate> Default </ng-template>`,
        ].join('\n'),
      );
    });

    it('should handle ternaries in ngIfElse', 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]="customClearTemplate"`,
          `  [ngIfElse]="isSidebarV3 || variant === 'v3' ? clearTemplateV3 : clearTemplate"`,
          `  [ngTemplateOutlet]="customClearTemplate"`,
          `></ng-template>`,
          `<ng-template #clearTemplateV3>v3</ng-template>`,
          `<ng-template #clearTemplate>clear</ng-template>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (customClearTemplate) {`,
          `  <ng-template`,
          `    [ngTemplateOutlet]="customClearTemplate"`,
          `  ></ng-template>`,
          `} @else {`,
          `  <ng-template [ngTemplateOutlet]="isSidebarV3 || variant === 'v3' ? clearTemplateV3 : clearTemplate"></ng-template>`,
          `}`,
          `<ng-template #clearTemplateV3>v3</ng-template>`,
          `<ng-template #clearTemplate>clear</ng-template>`,
        ].join('\n'),
      );
    });

    it('should handle ternaries in ngIf', 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 *ngIf="!vm.isEmpty; else vm.loading ? loader : empty"></div>`,
          `<ng-template #loader>Loading</ng-template>`,
          `<ng-template #empty>Empty</ng-template>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (!vm.isEmpty) {`,
          `  <div></div>`,
          `} @else {`,
          `  <ng-template [ngTemplateOutlet]="vm.loading ? loader : empty"></ng-template>`,
          `}`,
          `<ng-template #loader>Loading</ng-template>`,
          `<ng-template #empty>Empty</ng-template>`,
        ].join('\n'),
      );
    });

    it('should replace all instances of template placeholders', 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 *ngIf="condition; else otherTemplate">`,
          `  <ng-container *ngIf="!defaultTemplate; else defaultTemplate">`,
          `    Hello!`,
          `  </ng-container>`,
          `</div>`,
          `<ng-template #otherTemplate>`,
          `  <div>`,
          `    <ng-container *ngIf="!defaultTemplate; else defaultTemplate">`,
          `      Hello again!`,
          `    </ng-container>`,
          `  </div>`,
          `</ng-template>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (condition) {`,
          `  <div>`,
          `    @if (!defaultTemplate) {`,
          `      Hello!`,
          `    } @else {`,
          `      <ng-template [ngTemplateOutlet]="defaultTemplate"></ng-template>`,
          `    }`,
          `  </div>`,
          `} @else {`,
          `  <div>`,
          `    @if (!defaultTemplate) {`,
          `      Hello again!`,
          `    } @else {`,
          `      <ng-template [ngTemplateOutlet]="defaultTemplate"></ng-template>`,
          `    }`,
          `  </div>`,
          `}\n`,
        ].join('\n'),
      );
    });

    it('should trim newlines in ngIf conditions', 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]="customClearTemplate"`,
          `  [ngIfElse]="`,
          `    isSidebarV3 || variant === 'v3' ? clearTemplateV3 : clearTemplate`,
          `  "`,
          `></ng-template>`,
        ].join('\n'),
      );

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

      expect(content).toBe(
        [
          `@if (customClearTemplate) {`,
          `} @else {`,
          `  <ng-template [ngTemplateOutlet]="isSidebarV3 || variant === 'v3' ? clearTemplateV3 : clearTemplate"></ng-template>`,
          `}`,
        ].join('\n'),
      );
    });

    it('should migrate a template using the θδ characters', async () => {
      writeFile(
        '/comp.ts',
        `
        import {Component} from '@angular/core';
        import {NgIf} from '@angular/common';

        @Component({
          selector: 'declare-comp',
          templateUrl: './comp.html',
          standalone: true,
          imports: [NgIf],
        })
        class DeclareComp {
          show = false;
        }
      `,
      );

      writeFile('/comp.html', `<div *ngIf="show">Some greek characters: θδ!</div>`);

      await runMigration();

      expect(tree.readContent('/comp.html')).toBe(
        '@if (show) {
Some greek characters: θδ!
}',
      );
    });
  });

  de


ould indent multi-line attribute strings as single quotes to the right place', 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 *ngIf="show">show</div>`,
          `<span i18n-message='this is a multi-`,
          `                    line attribute`,
          `                    with cool things'>`,
          `  Content here`,
          `</span>`,
          `<span`,
          `    i18n-message='this is a multi-`,
          `                    line attribute`,
          `                    that starts`,
          `                    on a newline'>`,
          `  Different here`,
          `</span>`,
        ].join('\n'),
      );

      await runMigration();
      const actual = tree.readContent('/comp.html');
      const expected = [
        `@if (show) {`,
        `  <div>show</div>`,
        `}`,
        `<span i18n-message='this is a multi-`,
        `                    line attribute`,
        `                    with cool things'>`,
        `  Content here`,
        `</span>`,
        `<span`,
        `    i18n-message='this is a multi-`,
        `                    line attribute`,
        `                    that starts`,
        `                    on a newline'>`,
        `  Different here`,
        `</span>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should handle dom nodes with underscores mixed in', 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 *ngIf="show">show</div>`,
          `<a-very-long-component-name-that-has_underscores-too`,
          `  [selected]="selected | async "`,
          `>`,
          `</a-very-long-component-name-that-has_underscores-too>`,
        ].join('\n'),
      );

      await runMigration();
      const actual = tree.readContent('/comp.html');
      const expected = [
        `@if (show) {`,
        `  <div>show</div>`,
        `}`,
        `<a-very-long-component-name-that-has_underscores-too`,
        `  [selected]="selected | async "`,
        `  >`,
        `</a-very-long-component-name-that-has_underscores-too>`,
      ].join('\n');

      expect(actual).toBe(expected);
    });

    it('should handle single-line element with a log tag name and a closing bracket on a new line', 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">`,
          `<component-name-with-several-dashes></component-name-with-several-dashes`,
          `></ng-container>`,
        ].join('\n'),
      );

      await runMigration();
      const actual = tree.readContent('/comp.html');
      const expected = [
        `@if (true) {`,
        `  <component-name-with-several-dashes></component-name-with-several-dashes`,
        `    >`,
        `  }`,
      ].join('\n');

      expect(actual).toBe(expected);
    });
  });

  de