chore(ct): revert adapters imports and template

This commit is contained in:
sand4rt 2024-05-07 18:49:56 +02:00
parent d83f9ee4a9
commit 69a6db45fd
4 changed files with 73 additions and 168 deletions

View file

@ -17,24 +17,18 @@
import type { Locator } from 'playwright/test'; import type { Locator } from 'playwright/test';
import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; import type { JsonObject } from '@playwright/experimental-ct-core/types/component';
import type { TestType } from '@playwright/experimental-ct-core'; import type { TestType } from '@playwright/experimental-ct-core';
import type { Provider, Type } from '@angular/core'; import type { Type } from '@angular/core';
export type ComponentEvents = Record<string, Function>; export type ComponentEvents = Record<string, Function>;
export interface MountOptions<HooksConfig extends JsonObject, Component> { export interface MountOptions<HooksConfig extends JsonObject, Component> {
props?: Partial<Component> | Record<string, unknown>, // TODO: filter props and handle signals props?: Partial<Component> | Record<string, unknown>, // TODO: filter props and handle signals
providers?: Provider[],
on?: ComponentEvents; on?: ComponentEvents;
hooksConfig?: HooksConfig; hooksConfig?: HooksConfig;
} }
export interface MountTemplateOptions<HooksConfig extends JsonObject, Component> extends MountOptions<HooksConfig, Component> {
imports?: Type<unknown>[];
}
export interface MountResult<Component> extends Locator { export interface MountResult<Component> extends Locator {
unmount(): Promise<void>; unmount(): Promise<void>;
update(options: { update(options: {
props?: Partial<Component>, props?: Partial<Component>,
on?: Partial<ComponentEvents>, on?: Partial<ComponentEvents>,
@ -42,11 +36,6 @@ export interface MountResult<Component> extends Locator {
} }
export interface ComponentFixtures { export interface ComponentFixtures {
mount<HooksConfig extends JsonObject, Component = unknown>(
template: string,
options?: MountTemplateOptions<HooksConfig, Component>
): Promise<MountResult<Component>>;
mount<HooksConfig extends JsonObject, Component = unknown>( mount<HooksConfig extends JsonObject, Component = unknown>(
component: Type<Component>, component: Type<Component>,
options?: MountOptions<HooksConfig, Component> options?: MountOptions<HooksConfig, Component>

View file

@ -17,10 +17,7 @@
// @ts-check // @ts-check
// This file is injected into the registry as text, no dependencies are allowed. // This file is injected into the registry as text, no dependencies are allowed.
/** /** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
* @typedef {{type: string} & import('./index').MountTemplateOptions} TemplateInfo
* @typedef {{type: import('@angular/core').Type<unknown>} & import('./index').MountOptions | TemplateInfo} ComponentInfo
*/
import 'zone.js'; import 'zone.js';
import { import {
@ -33,13 +30,75 @@ import {
platformBrowserDynamicTesting, platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
/** @type {WeakMap<import('@angular/core/testing').ComponentFixture, Record<string, import('rxjs').Subscription>>} */
const __pwOutputSubscriptionRegistry = new WeakMap();
/** @type {Map<string, import('@angular/core/testing').ComponentFixture>} */
const __pwFixtureRegistry = new Map();
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting(), platformBrowserDynamicTesting(),
); );
/**
* @param {ObjectComponent} component
*/
async function __pwRenderComponent(component) {
const componentMetadata = reflectComponentType(component.type);
if (!componentMetadata?.isStandalone)
throw new Error('Only standalone components are supported');
TestBed.configureTestingModule({
imports: [component.type],
});
await TestBed.compileComponents();
const fixture = TestBed.createComponent(component.type);
fixture.nativeElement.id = 'root';
__pwUpdateProps(fixture, component.props);
__pwUpdateEvents(fixture, component.on);
fixture.autoDetectChanges();
return fixture;
}
/**
* @param {import('@angular/core/testing').ComponentFixture} fixture
*/
function __pwUpdateProps(fixture, props = {}) {
for (const [name, value] of Object.entries(props))
fixture.componentRef.setInput(name, value);
}
/**
* @param {import('@angular/core/testing').ComponentFixture} fixture
*/
function __pwUpdateEvents(fixture, events = {}) {
const outputSubscriptionRecord =
__pwOutputSubscriptionRegistry.get(fixture) ?? {};
for (const [name, listener] of Object.entries(events)) {
/* Unsubscribe previous listener. */
outputSubscriptionRecord[name]?.unsubscribe();
const subscription = fixture.componentInstance[
name
].subscribe((/** @type {unknown} */ event) => listener(event));
/* Store new subscription. */
outputSubscriptionRecord[name] = subscription;
}
/* Update output subscription registry. */
__pwOutputSubscriptionRegistry.set(fixture, outputSubscriptionRecord);
}
window.playwrightMount = async (component, rootElement, hooksConfig) => { window.playwrightMount = async (component, rootElement, hooksConfig) => {
__pwAssertIsNotJsx(component); if (component.__pw_type === 'jsx')
throw new Error('JSX mount notation is not supported');
for (const hook of window.__pw_hooks_before_mount || []) for (const hook of window.__pw_hooks_before_mount || [])
await hook({ hooksConfig, TestBed }); await hook({ hooksConfig, TestBed });
@ -66,116 +125,16 @@ window.playwrightUnmount = async rootElement => {
fixture.nativeElement.replaceChildren(); fixture.nativeElement.replaceChildren();
}; };
/**
* @param {{type: import('@angular/core').Type<unknown>} & import('./index').MountOptions | {type: string} & import('./index').MountTemplateOptions} component
*/
window.playwrightUpdate = async (rootElement, component) => { window.playwrightUpdate = async (rootElement, component) => {
__pwAssertIsNotJsx(component); if (component.__pw_type === 'jsx')
throw new Error('JSX mount notation is not supported');
const fixture = __pwFixtureRegistry.get(rootElement.id); const fixture = __pwFixtureRegistry.get(rootElement.id);
if (!fixture) if (!fixture)
throw new Error('Component was not mounted'); throw new Error('Component was not mounted');
__pwUpdateProps(fixture, component); __pwUpdateProps(fixture, component.props);
__pwUpdateEvents(fixture, component.on); __pwUpdateEvents(fixture, component.on);
fixture.detectChanges(); fixture.detectChanges();
}; };
/** @type {WeakMap<import('@angular/core/testing').ComponentFixture, Record<string, import('rxjs').Subscription>>} */
const __pwOutputSubscriptionRegistry = new WeakMap();
/** @type {Map<string, import('@angular/core/testing').ComponentFixture>} */
const __pwFixtureRegistry = new Map();
/**
* @param {ComponentInfo} component
*/
async function __pwRenderComponent(component) {
/** @type {import('@angular/core').Type<unknown>} */
let componentClass;
if (__pwIsTemplate(component)) {
const templateInfo = /** @type {TemplateInfo} */(component);
componentClass = defineComponent({
standalone: true,
selector: 'pw-template-component',
imports: templateInfo.imports,
template: templateInfo.type,
})(class {});
} else {
componentClass = /** @type {import('@angular/core').Type<unknown>} */(component.type);
}
const componentMetadata = reflectComponentType(componentClass);
if (!componentMetadata?.isStandalone)
throw new Error('Only standalone components are supported');
TestBed.configureTestingModule({
imports: [componentClass],
providers: component.providers,
});
await TestBed.compileComponents();
const fixture = TestBed.createComponent(componentClass);
fixture.nativeElement.id = 'root';
__pwUpdateProps(fixture, component);
__pwUpdateEvents(fixture, component.on);
fixture.autoDetectChanges();
return fixture;
}
/**
* @param {import('@angular/core/testing').ComponentFixture} fixture
* @param {ComponentInfo} componentInfo
*/
function __pwUpdateProps(fixture, componentInfo) {
if (!componentInfo.props)
return;
if (__pwIsTemplate(componentInfo)) {
Object.assign(fixture.componentInstance, componentInfo.props);
} else {
for (const [name, value] of Object.entries(componentInfo.props))
fixture.componentRef.setInput(name, value);
}
}
/**
* @param {import('@angular/core/testing').ComponentFixture} fixture
*/
function __pwUpdateEvents(fixture, events = {}) {
const outputSubscriptionRecord =
__pwOutputSubscriptionRegistry.get(fixture) ?? {};
for (const [name, listener] of Object.entries(events)) {
/* Unsubscribe previous listener. */
outputSubscriptionRecord[name]?.unsubscribe();
const subscription = fixture.componentInstance[
name
].subscribe((/** @type {unknown} */ event) => listener(event));
/* Store new subscription. */
outputSubscriptionRecord[name] = subscription;
}
/* Update output subscription registry. */
__pwOutputSubscriptionRegistry.set(fixture, outputSubscriptionRecord);
}
function __pwAssertIsNotJsx(component) {
if (component.__pw_type === 'jsx')
throw new Error('JSX mount notation is not supported');
}
/**
* @param {ComponentInfo} component
*/
function __pwIsTemplate(component) {
return typeof component.type === 'string';
}

View file

@ -1,9 +1,10 @@
import { InjectComponent, TOKEN } from "@/components/inject.component"; import { InjectComponent, TOKEN } from '@/components/inject.component';
import { expect, test } from "@playwright/experimental-ct-angular"; import { expect, test } from '@playwright/experimental-ct-angular';
import type { HooksConfig } from 'playwright';
test('inject a token', async ({ mount }) => { test('inject a token', async ({ mount }) => {
const component = await mount(InjectComponent, { const component = await mount<HooksConfig>(InjectComponent, {
providers: [{ provide: TOKEN, useValue: { text: 'has been overwritten' }}] hooksConfig: { injectToken: true },
}); });
await expect(component).toHaveText('has been overwritten'); await expect(component).toHaveText('has been overwritten');
}); });

View file

@ -1,44 +0,0 @@
import { ButtonComponent } from "@/components/button.component";
import { expect, test } from "@playwright/experimental-ct-angular"
test('render a template', async ({ mount }) => {
const component = await mount('<h1>{{ 1 + 1 }}</h1>');
await expect(component).toHaveText('2');
})
test('render a template with child components', async ({ mount }) => {
const component = await mount('<app-button title="Click"/>', {
imports: [ButtonComponent]
});
await expect(component.getByRole('button')).toContainText('Click');
})
test('render a template with inputs', async ({ mount }) => {
const component = await mount('<app-button [title]="title"/>', {
imports: [ButtonComponent],
props: {
title: 'Click',
}
});
await expect(component.getByRole('button')).toContainText('Click');
})
test('render a template with outputs', async ({ mount }) => {
let _message: string;
const component = await mount('<app-button (submit)="onSubmit($event)"/>', {
imports: [ButtonComponent],
props: {
title: 'Click',
onSubmit(message: string) {
_message = message;
}
}
});
await component.getByRole('button').click();
await expect(async () => {expect(_message).toBe('hello')}).toPass();
})