Skip to content

Commit

Permalink
Merge pull request microsoft#3 from yjaaidi/handle-angular-non-event-…
Browse files Browse the repository at this point in the history
…emitter-outputs

Handle angular non-EventEmitter outputs
  • Loading branch information
sand4rt authored Nov 24, 2023
2 parents 8ccb3bb + 09a0b38 commit 2f57e08
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 9 deletions.
40 changes: 31 additions & 9 deletions packages/playwright-ct-angular/registerSource.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@
// This file is injected into the registry as text, no dependencies are allowed.

import 'zone.js';
import {
Component as defineComponent,
reflectComponentType
} from '@angular/core';
import { getTestBed, TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { EventEmitter, reflectComponentType, Component as defineComponent } from '@angular/core';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import { Router } from '@angular/router';

/** @typedef {import('@playwright/experimental-ct-core/types/component').Component} Component */
Expand All @@ -34,6 +40,8 @@ const __pwLoaderRegistry = new Map();
const __pwRegistry = new Map();
/** @type {Map<string, import('@angular/core/testing').ComponentFixture>} */
const __pwFixtureRegistry = new Map();
/** @type {WeakMap<import('@angular/core/testing').ComponentFixture, Record<string, import('rxjs').Subscription>>} */
const __pwOutputSubscriptionRegistry = new WeakMap();

getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
Expand Down Expand Up @@ -96,12 +104,22 @@ function __pwUpdateProps(fixture, props = {}) {
* @param {import('@angular/core/testing').ComponentFixture} fixture
*/
function __pwUpdateEvents(fixture, events = {}) {
for (const [name, value] of Object.entries(events)) {
fixture.debugElement.children[0].componentInstance[name] = {
...new EventEmitter(),
emit: event => value(event)
};
const outputSubscriptionRecord =
__pwOutputSubscriptionRegistry.get(fixture) ?? {};
for (const [name, listener] of Object.entries(events)) {
/* Unsubscribe previous listener. */
outputSubscriptionRecord[name]?.unsubscribe();

const subscription = fixture.debugElement.children[0].componentInstance[
name
].subscribe((event) => listener(event));

/* Store new subscription. */
outputSubscriptionRecord[name] = subscription;
}

/* Update output subscription registry. */
__pwOutputSubscriptionRegistry.set(fixture, outputSubscriptionRecord);
}

function __pwUpdateSlots(Component, slots = {}, tagName) {
Expand Down Expand Up @@ -211,8 +229,12 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {

window.playwrightUnmount = async rootElement => {
const fixture = __pwFixtureRegistry.get(rootElement.id);
if (!fixture)
throw new Error('Component was not mounted');
if (!fixture) throw new Error('Component was not mounted');

/* Unsubscribe from all outputs. */
for (const subscription of Object.values(__pwOutputSubscriptionRegistry.get(fixture) ?? {}))
subscription?.unsubscribe();
__pwOutputSubscriptionRegistry.delete(fixture);

fixture.destroy();
fixture.nativeElement.replaceChildren();
Expand Down
17 changes: 17 additions & 0 deletions tests/components/ct-angular/src/components/output.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DOCUMENT } from "@angular/common";
import { Component, Output, inject } from "@angular/core";
import { Subject, finalize } from "rxjs";

@Component({
standalone: true,
template: `OutputComponent`,
})
export class OutputComponent {
@Output() answerChange = new Subject().pipe(
/* Detect when observable is unsubscribed from,
* and set a global variable `hasUnsubscribed` to true. */
finalize(() => ((this._window as any).hasUnsubscribed = true))
);

private _window = inject(DOCUMENT).defaultView;
}
47 changes: 47 additions & 0 deletions tests/components/ct-angular/tests/events.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, expect } from '@playwright/experimental-ct-angular';
import { ButtonComponent } from '@/components/button.component';
import { OutputComponent } from '@/components/output.component';

test('emit an submit event when the button is clicked', async ({ mount }) => {
const messages: string[] = [];
Expand All @@ -14,3 +15,49 @@ test('emit an submit event when the button is clicked', async ({ mount }) => {
await component.click();
expect(messages).toEqual(['hello']);
});

test('replace existing listener when new listener is set', async ({
mount,
}) => {
let count = 0;

const component = await mount(ButtonComponent, {
props: {
title: 'Submit',
},
on: {
submit() {
count++;
},
},
});

component.update({
on: {
submit() {
count++;
},
},
});

await component.click();
expect(count).toBe(1);
});

test('unsubscribe from events when the component is unmounted', async ({
mount,
page,
}) => {
const component = await mount(OutputComponent, {
on: {
answerChange() {},
},
});

await component.unmount();

/* Check that the output observable had been unsubscribed from
* as it sets a global variable `hasUnusbscribed` to true
* when it detects unsubscription. Cf. OutputComponent. */
expect(await page.evaluate(() => (window as any).hasUnsubscribed)).toBe(true);
});

0 comments on commit 2f57e08

Please sign in to comment.