diff --git a/angular/test/base/e2e/src/output-target.spec.ts b/angular/test/base/e2e/src/output-target.spec.ts new file mode 100644 index 0000000000..1973759d0e --- /dev/null +++ b/angular/test/base/e2e/src/output-target.spec.ts @@ -0,0 +1,28 @@ +describe('Angular Output Target', () => { + beforeEach(() => { + cy.visit('/output-target'); + }); + + it('should emit one event per emission', () => { + /** + * Angular @Output() events aren't actual DOM events, + * instead they are an "EventEmitter" (RxJS Subject) + * that emits a change. + * + * In the Angular output target, we manually create + * a RxJS Subject (EventEmitter) for each DOM event, + * so that developers can use the same binding syntax + * and have the expected behavior that Angular events + * do not bubble up the DOM tree. + * + * We additionally "trick" Angular by creating + * @Output decorated properties for each DOM event + * on the component proxy class and manually clearing + * the decorated metadata. This allows Angular to + * not add its own event listener and cause duplicate + * event emissions for the web component events. + */ + cy.get('#ionChangeInput').type('a'); + cy.get('#ionChangeEmittedCount').should('have.text', '1'); + }); +}); diff --git a/angular/test/base/src/app/app-routing.module.ts b/angular/test/base/src/app/app-routing.module.ts index e03345759f..149b44eaf9 100644 --- a/angular/test/base/src/app/app-routing.module.ts +++ b/angular/test/base/src/app/app-routing.module.ts @@ -73,6 +73,10 @@ const routes: Routes = [ } ] }, + { + path: 'output-target', + loadChildren: () => import('./output-target/output-target.module').then(m => m.OutputTargetModule) + } ]; @NgModule({ diff --git a/angular/test/base/src/app/output-target/output-target-routing.module.ts b/angular/test/base/src/app/output-target/output-target-routing.module.ts new file mode 100644 index 0000000000..bee37805a7 --- /dev/null +++ b/angular/test/base/src/app/output-target/output-target-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { OutputTargetComponent } from "./output-target.component"; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: "", + component: OutputTargetComponent + } + ]) + ], + exports: [RouterModule] +}) +export class OutputTargetRoutingModule { } diff --git a/angular/test/base/src/app/output-target/output-target.component.html b/angular/test/base/src/app/output-target/output-target.component.html new file mode 100644 index 0000000000..9d25995a6b --- /dev/null +++ b/angular/test/base/src/app/output-target/output-target.component.html @@ -0,0 +1,11 @@ + +

This test template verifies key behaviors in the @stencil/angular-output-target.

+ + + + Events are emitted once + +

{{ ionChangeEmittedCount }}

+
+
+
diff --git a/angular/test/base/src/app/output-target/output-target.component.ts b/angular/test/base/src/app/output-target/output-target.component.ts new file mode 100644 index 0000000000..59b3ce3369 --- /dev/null +++ b/angular/test/base/src/app/output-target/output-target.component.ts @@ -0,0 +1,15 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "app-output-target", + templateUrl: "./output-target.component.html", +}) +export class OutputTargetComponent { + + ionChangeEmittedCount = 0; + + onIonChange() { + this.ionChangeEmittedCount++; + } + +} diff --git a/angular/test/base/src/app/output-target/output-target.module.ts b/angular/test/base/src/app/output-target/output-target.module.ts new file mode 100644 index 0000000000..383fd361ee --- /dev/null +++ b/angular/test/base/src/app/output-target/output-target.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from "@angular/core"; +import { IonicModule } from "@ionic/angular"; + +import { OutputTargetRoutingModule } from "./output-target-routing.module"; +import { OutputTargetComponent } from "./output-target.component"; + +@NgModule({ + imports: [IonicModule, OutputTargetRoutingModule], + declarations: [OutputTargetComponent] +}) +export class OutputTargetModule { }