Merge pull request #3 from yjaaidi/handle-angular-non-event-emitter-outputs

Handle angular non-EventEmitter outputs
This commit is contained in:
Sander 2023-11-24 14:18:04 +01:00 committed by GitHub
commit 2f57e08606
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 9 deletions

View file

@ -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 */
@ -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,
@ -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) {
@ -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();

View file

@ -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;
}

View file

@ -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[] = [];
@ -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);
});