From ac9cd6108f6fe25e9c7a11db9816c6e07d241515 Mon Sep 17 00:00:00 2001 From: anthonyfr75 Date: Sun, 19 Nov 2023 18:28:59 +0100 Subject: [PATCH] fix(migrations): control flow migration fails for async pipe with unboxing of observable (#52756) (#52972) Update control flow syntax to use 'as' for proper async pipe handling, accounting for variable whitespace before 'let'. Fixes #52756 PR Close #52972 --- .../ng-generate/control-flow-migration/ifs.ts | 15 ++++- .../test/control_flow_migration_spec.ts | 64 ++++++++++++++++++- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts index 34527b4a9b546..959623a4820bf 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts @@ -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); @@ -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); } @@ -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); diff --git a/packages/core/schematics/test/control_flow_migration_spec.ts b/packages/core/schematics/test/control_flow_migration_spec.ts index 5452e32edae20..c06e74cd898ef 100644 --- a/packages/core/schematics/test/control_flow_migration_spec.ts +++ b/packages/core/schematics/test/control_flow_migration_spec.ts @@ -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.'); }); @@ -3513,5 +3512,68 @@ describe('control flow migration', () => { expect(content).toContain( 'template: `
@if (toggle) {
@if (show) {shrug}
}
`'); }); + + 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: \` {{value}} \` + }) + 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: \`
Hello {{ userName }} !
\` + }) + class Comp { + logStatus$ = of({ name: 'Robert' }); + } + `); + + await runMigration(); + const content = tree.readContent('/comp.ts'); + expect(content).toContain( + 'template: `@if ((logStatus$ | async)?.name; as userName) {
Hello {{ userName }} !
} @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: \`
Log In + Log Out\` + }) + 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}`'); + }); }); });