diff --git a/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.test.ts b/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.test.ts index 68c19d8..67b1ad7 100644 --- a/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.test.ts +++ b/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.test.ts @@ -167,6 +167,53 @@ describe("migrateComponents", () => { ); }); + it("should detect and import icons in conditional used in the template", async () => { + const project = new Project({ useInMemoryFileSystem: true }); + + const component = ` + import { Component } from "@angular/core"; + + @Component({ + selector: 'my-component', + template: \`\`, + standalone: true + }) + export class MyComponent { + isLogo = true; + } + `; + + const componentSourceFile = project.createSourceFile( + "foo.component.ts", + dedent(component), + ); + + await migrateComponents(project, { dryRun: false }); + + expect(dedent(componentSourceFile.getText())).toBe( + dedent(` + import { Component } from "@angular/core"; + import { addIcons } from "ionicons"; + import { logoIonic, alert } from "ionicons/icons"; + import { IonIcon } from "@ionic/angular/standalone"; + + @Component({ + selector: 'my-component', + template: \`\`, + standalone: true, + imports: [IonIcon] + }) + export class MyComponent { + isLogo = true; + + constructor() { + addIcons({ logoIonic, alert }); + } + } + `), + ); + }); + it("should remove duplicate imports from existing declarations", async () => { const project = new Project({ useInMemoryFileSystem: true }); diff --git a/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.ts b/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.ts index d370ea1..ece332e 100644 --- a/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.ts +++ b/packages/cli/src/angular/migrations/standalone/0002-import-standalone-component.ts @@ -292,10 +292,38 @@ function detectIonicComponentsAndIcons(htmlAsString: string, filePath: string) { */ const iconNameMatch = skippedIcon.match(iconNameRegex); + const deepGetIconConditional = ( + ast: typeof boundNameAttribute.value.ast, + icons: string[], + ): string[] => { + if (ast.trueExp.type === "LiteralPrimitive") { + if (!ionIcons.includes(ast.trueExp.value)) { + ionIcons.push(ast.trueExp.value); + } + } else if (ast.trueExp.type === "Conditional") { + deepGetIconConditional(ast.trueExp, icons); + } else { + skippedIconsHtml.push(skippedIcon); + } + + if (ast.falseExp.type === "LiteralPrimitive") { + if (!ionIcons.includes(ast.falseExp.value)) { + ionIcons.push(ast.falseExp.value); + } + } else if (ast.falseExp.type === "Conditional") { + deepGetIconConditional(ast.falseExp, icons); + } else { + skippedIconsHtml.push(skippedIcon); + } + return icons; + }; + if (iconNameMatch) { if (!ionIcons.includes(iconNameMatch[1])) { ionIcons.push(iconNameMatch[1]); } + } else if (boundNameAttribute.value.ast.type === "Conditional") { + deepGetIconConditional(boundNameAttribute.value.ast, ionIcons); } else { // IonIcon name is a calculated value from a variable or function. // We can't determine the value of the name at this time.