diff --git a/.changeset/tender-flies-remember.md b/.changeset/tender-flies-remember.md new file mode 100644 index 0000000000..5ac09dd803 --- /dev/null +++ b/.changeset/tender-flies-remember.md @@ -0,0 +1,5 @@ +--- +'@builder.io/mitosis': patch +--- + +[Angular] Update innerHtml parsing to escape quotes diff --git a/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap index 2600ea51b7..0d025871b4 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap @@ -4665,6 +4665,49 @@ export class MyComponentModule {} " `; +exports[`Angular with Preserve Imports and File Extensions > jsx > Javascript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with Preserve Imports and File Extensions > jsx > Javascript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -12922,6 +12965,53 @@ export class MyComponentModule {} " `; +exports[`Angular with Preserve Imports and File Extensions > jsx > Typescript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +export interface MyComponentProps { + text?: string; +} + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer: DomSanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with Preserve Imports and File Extensions > jsx > Typescript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap index dc64cdf00a..405024d74c 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap @@ -4735,6 +4735,50 @@ export class MyComponentModule {} " `; +exports[`Angular with Import Mapper Tests > jsx > Javascript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], + bootstrap: [SomeOtherComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with Import Mapper Tests > jsx > Javascript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -13127,6 +13171,54 @@ export class MyComponentModule {} " `; +exports[`Angular with Import Mapper Tests > jsx > Typescript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +export interface MyComponentProps { + text?: string; +} + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer: DomSanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], + bootstrap: [SomeOtherComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with Import Mapper Tests > jsx > Typescript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap index 29fa7867b6..b1fef58764 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap @@ -4847,6 +4847,49 @@ export class MyComponentModule {} " `; +exports[`Angular with manually creating and handling class properties as bindings (more stable) > jsx > Javascript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with manually creating and handling class properties as bindings (more stable) > jsx > Javascript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -13433,6 +13476,53 @@ export class MyComponentModule {} " `; +exports[`Angular with manually creating and handling class properties as bindings (more stable) > jsx > Typescript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +export interface MyComponentProps { + text?: string; +} + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer: DomSanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with manually creating and handling class properties as bindings (more stable) > jsx > Typescript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap index c8c1d3c77e..3004ad0f7c 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap @@ -4223,6 +4223,42 @@ export class MyComponentModule {} " `; +exports[`Angular with visuallyIgnoreHostElement = false > jsx > Javascript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export default class MyComponent { + constructor(protected sanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with visuallyIgnoreHostElement = false > jsx > Javascript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -11619,6 +11655,46 @@ export class MyComponentModule {} " `; +exports[`Angular with visuallyIgnoreHostElement = false > jsx > Typescript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +export interface MyComponentProps { + text?: string; +} + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export default class MyComponent { + constructor(protected sanitizer: DomSanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + exports[`Angular with visuallyIgnoreHostElement = false > jsx > Typescript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap index e9e4e770b4..1f76e348e5 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap @@ -8898,6 +8898,85 @@ export default class MyComponent { " `; +exports[`Angular > jsx > Javascript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + +exports[`Angular > jsx > Javascript Test > escapeQuotesInnerHTML 2`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; +import { CommonModule } from \\"@angular/common\\"; + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], + standalone: true, + imports: [CommonModule], +}) +export default class MyComponent { + constructor(protected sanitizer) {} +} +" +`; + exports[`Angular > jsx > Javascript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -24566,6 +24645,93 @@ export default class MyComponent { " `; +exports[`Angular > jsx > Typescript Test > escapeQuotesInnerHTML 1`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; + +export interface MyComponentProps { + text?: string; +} + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], +}) +export default class MyComponent { + constructor(protected sanitizer: DomSanitizer) {} +} + +@NgModule({ + declarations: [MyComponent], + imports: [CommonModule], + exports: [MyComponent], +}) +export class MyComponentModule {} +" +`; + +exports[`Angular > jsx > Typescript Test > escapeQuotesInnerHTML 2`] = ` +"/** + useMetadata: + {\\"angular\\":{\\"changeDetection\\":\\"OnPush\\"}} + */ + +import { Component, ChangeDetectionStrategy } from \\"@angular/core\\"; +import { DomSanitizer } from \\"@angular/platform-browser\\"; +import { CommonModule } from \\"@angular/common\\"; + +export interface MyComponentProps { + text?: string; +} + +@Component({ + selector: \\"my-component\\", + template: \` +
+ \`, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + \` + :host { + display: contents; + } + \`, + ], + standalone: true, + imports: [CommonModule], +}) +export default class MyComponent { + constructor(protected sanitizer: DomSanitizer) {} +} +" +`; + exports[`Angular > jsx > Typescript Test > eventInputAndChange 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/data/angular/escape-quotes-innerhtml.raw.tsx b/packages/core/src/__tests__/data/angular/escape-quotes-innerhtml.raw.tsx new file mode 100644 index 0000000000..7027a85251 --- /dev/null +++ b/packages/core/src/__tests__/data/angular/escape-quotes-innerhtml.raw.tsx @@ -0,0 +1,17 @@ +import { useMetadata } from '@builder.io/mitosis'; + +useMetadata({ + angular: { + changeDetection: 'OnPush', + }, +}); + +export default function MyComponent(props: { text?: string }) { + return ( +
+ + `} + /> + ); +} diff --git a/packages/core/src/__tests__/test-generator.ts b/packages/core/src/__tests__/test-generator.ts index 998400bd29..a3d7e89fe5 100644 --- a/packages/core/src/__tests__/test-generator.ts +++ b/packages/core/src/__tests__/test-generator.ts @@ -372,6 +372,7 @@ const ANGULAR_TESTS: Tests = { allSpread: getRawFile('./data/angular/all-spread.raw.tsx'), changeDetection: getRawFile('./data/angular/change-detection.raw.tsx'), sanitizeInnerHTML: getRawFile('./data/angular/sanitize-inner-html.raw.tsx'), + escapeQuotesInnerHTML: getRawFile('./data/angular/escape-quotes-innerhtml.raw.tsx'), }; const CONTEXT_TEST: Tests = { diff --git a/packages/core/src/generators/angular/blocks.ts b/packages/core/src/generators/angular/blocks.ts index 993551158d..c8a910559a 100644 --- a/packages/core/src/generators/angular/blocks.ts +++ b/packages/core/src/generators/angular/blocks.ts @@ -409,7 +409,14 @@ export const blockToAngular = ({ continue; } const value = json.properties[key]; - str += ` ${key}="${value}" `; + // Better parsing for innerHTML + if (key === 'innerHTML') { + // Replace double quotes with escaped quotes for proper Angular template handling + const escapedValue = String(value).replace(/"/g, '"'); + str += `[innerHTML]="sanitizer.bypassSecurityTrustHtml('${escapedValue}')" `; + } else { + str += ` ${key}="${value}" `; + } } for (const key in json.bindings) {