Skip to content

Commit

Permalink
fix(migrations): control flow migration fails for async pipe with unb…
Browse files Browse the repository at this point in the history
…oxing of observable

Update control flow syntax to use 'as' for proper async pipe handling, accounting for variable whitespace before 'let'.

Fixes #52756
  • Loading branch information
AnthonyFr75 committed Nov 20, 2023
1 parent 060ea83 commit 8dd98ac
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 4 deletions.
15 changes: 12 additions & 3 deletions packages/core/schematics/ng-generate/control-flow-migration/ifs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ function migrateNgIf(etm: ElementToMigrate, tmpl: string, offset: number): Resul
function buildIfBlock(etm: ElementToMigrate, tmpl: string, offset: number): Result {
// includes the mandatory semicolon before as
const lbString = etm.hasLineBreaks ? '\n' : '';
const condition = etm.attr.value.replace(' as ', '; as ');
const condition = etm.attr.value
.replace(' as ', '; as ')
// replace 'let' with 'as' whatever spaces are between ; and 'let'
.replace(/;\s*let/g, '; as');

const originals = getOriginals(etm, tmpl, offset);

Expand All @@ -106,7 +109,10 @@ function buildIfBlock(etm: ElementToMigrate, tmpl: string, offset: number): Resu
function buildStandardIfElseBlock(
etm: ElementToMigrate, tmpl: string, elseString: string, offset: number): Result {
// includes the mandatory semicolon before as
const condition = etm.getCondition(elseString).replace(' as ', '; as ');
const condition = etm.getCondition(elseString)
.replace(' as ', '; as ')
// replace 'let' with 'as' whatever spaces are between ; and 'let'
.replace(/;\s*let/g, '; as');
const elsePlaceholder = `#${etm.getTemplateName(elseString)}|`;
return buildIfElseBlock(etm, tmpl, condition, elsePlaceholder, offset);
}
Expand Down Expand Up @@ -151,7 +157,10 @@ function buildStandardIfThenElseBlock(
etm: ElementToMigrate, tmpl: string, thenString: string, elseString: string,
offset: number): Result {
// includes the mandatory semicolon before as
const condition = etm.getCondition(thenString).replace(' as ', '; as ');
const condition = etm.getCondition(thenString)
.replace(' as ', '; as ')
// replace 'let' with 'as' whatever spaces are between ; and 'let'
.replace(/;\s*let/g, '; as');
const thenPlaceholder = `#${etm.getTemplateName(thenString, elseString)}|`;
const elsePlaceholder = `#${etm.getTemplateName(elseString)}|`;
return buildIfThenElseBlock(etm, tmpl, condition, thenPlaceholder, elsePlaceholder, offset);
Expand Down
64 changes: 63 additions & 1 deletion packages/core/schematics/test/control_flow_migration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ describe('control flow migration', () => {
} catch (e: any) {
error = e.message;
}

expect(error).toBe('Cannot run control flow migration outside of the current project.');
});

Expand Down Expand Up @@ -3408,5 +3407,68 @@ describe('control flow migration', () => {
expect(content).toContain(
'template: `<div>@if (toggle) {<div>@if (show) {<span>shrug</span>}</div>}</div>`');
});

it('should update let value in a build if block to as value for the new control flow',
async () => {
writeFile('/comp.ts', `
import {Component} from '@angular/core';
import {NgIf} from '@angular/common';
@Component({
imports: [NgIf],
template: \`<ng-container *ngIf="value$ | async; let value"> {{value}} </ng-container>\`
})
class Comp {
value$ = of('Rica');
}
`);

await runMigration();
const content = tree.readContent('/comp.ts');
expect(content).toContain('template: `@if (value$ | async; as value) { {{value}} }`');
});

it('should update let value in a standard if else block to as value for the new control flow',
async () => {
writeFile('/comp.ts', `
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
imports: [CommonModule],
template: \`<div *ngIf="(logStatus$ | async)?.name; let userName; else loggedOut"> Hello {{ userName }} ! </div><ng-template #loggedOut></ng-template>\`
})
class Comp {
logStatus$ = of({ name: 'Robert' });
}
`);

await runMigration();
const content = tree.readContent('/comp.ts');
expect(content).toContain(
'template: `@if ((logStatus$ | async)?.name; as userName) {<div> Hello {{ userName }} ! </div>} @else {}`');
});

it('should update let value in a standard if else, then block to as value for the new control flow',
async () => {
writeFile('/comp.ts', `
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
imports: [CommonModule],
template: \`<div *ngIf="isLoggedIn$ | async; let logIn; then loggedIn; else loggedOut"></div><ng-template #loggedIn>Log In</ng-template>
<ng-template #loggedOut>Log Out</ng-template>\`
})
class Comp {
isLoggedIn$ = of(true);
}
`);

await runMigration();
const content = tree.readContent('/comp.ts');
expect(content).toContain(
'template: `@if (isLoggedIn$ | async; as logIn) {\n Log In\n} @else {\n Log Out\n}`');
});
});
});

0 comments on commit 8dd98ac

Please sign in to comment.