refactor(ct-angular): fix component resolution by temporary removing analogjs plugin

Co-authored-by: Edouard Bozon <bozonedouard@gmail.com>
This commit is contained in:
Younes Jaaidi 2024-02-23 00:28:51 +01:00
parent 0cb6fe3337
commit 12ed2e761a
No known key found for this signature in database
GPG key ID: 3126C5717BDF3241
4 changed files with 117 additions and 168 deletions

View file

@ -27,8 +27,7 @@ const defineConfig = (config, ...configs) => {
packageJSON: require.resolve('./package.json'),
},
'@playwright/experimental-ct-core': {
registerSourceFile: path.join(__dirname, 'registerSource.mjs'),
frameworkPluginFactory: () => {},
registerSourceFile: path.join(__dirname, 'registerSource.mjs')
},
}, ...configs);
};

View file

@ -1,22 +1,23 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// /**
// * Copyright (c) Microsoft Corporation.
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
// @ts-check
// This file is injected into the registry as text, no dependencies are allowed.
import '@angular/compiler';
import 'zone.js';
import {
Component as defineComponent,
@ -29,67 +30,102 @@ import {
} from '@angular/platform-browser-dynamic/testing';
import { Router } from '@angular/router';
/** @typedef {import('@playwright/experimental-ct-core/types/component').Component} Component */
/** @typedef {import('@playwright/experimental-ct-core/types/component').JsxComponent} JsxComponent */
/** @typedef {import('@playwright/experimental-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {import('@angular/core').Type} FrameworkComponent */
/** @type {Map<string, () => Promise<FrameworkComponent>>} */
const __pwLoaderRegistry = new Map();
/** @type {Map<string, FrameworkComponent>} */
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,
platformBrowserDynamicTesting(),
);
/**
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
*/
export function pwRegister(components) {
for (const [name, value] of Object.entries(components))
__pwLoaderRegistry.set(name, value);
}
window.playwrightMount = async (component, rootElement, hooksConfig) => {
for (const hook of window.__pw_hooks_before_mount || [])
await hook({ hooksConfig, TestBed });
/**
* @param {Component} component
* @returns {component is JsxComponent | ObjectComponent}
*/
function isComponent(component) {
return !(typeof component !== 'object' || Array.isArray(component));
}
const fixture = await __pwRenderComponent(component);
for (const hook of window.__pw_hooks_after_mount || [])
await hook({ hooksConfig });
__pwFixtureRegistry.set(rootElement.id, fixture);
};
window.playwrightUnmount = async rootElement => {
const fixture = __pwFixtureRegistry.get(rootElement.id);
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();
};
window.playwrightUpdate = async (rootElement, component) => {
await resolveComponent(component);
if (component.kind === 'jsx')
throw new Error('JSX mount notation is not supported');
if (component.options?.slots)
throw new Error('Update slots is not supported yet');
const fixture = __pwFixtureRegistry.get(rootElement.id);
if (!fixture)
throw new Error('Component was not mounted');
__pwUpdateProps(fixture, component.options?.props);
__pwUpdateEvents(fixture, component.options?.on);
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 {Component} component
*/
async function __pwResolveComponent(component) {
if (!isComponent(component))
return;
let componentFactory = __pwLoaderRegistry.get(component.type);
if (!componentFactory) {
// Lookup by shorthand.
for (const [name, value] of __pwLoaderRegistry) {
if (component.type.endsWith(`_${name}`)) {
componentFactory = value;
break;
}
}
}
if (!componentFactory && component.type[0].toUpperCase() === component.type[0])
async function __pwRenderComponent(component) {
const Component = await __pwResolveComponent(component);
if (!Component)
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if(componentFactory)
__pwRegistry.set(component.type, await componentFactory())
if ('children' in component)
await Promise.all(component.children.map(child => __pwResolveComponent(child)))
const componentMetadata = reflectComponentType(Component);
if (!componentMetadata?.isStandalone)
throw new Error('Only standalone components are supported');
const WrapperComponent = defineComponent({
selector: 'pw-wrapper-component',
template: ``,
})(class {});
TestBed.configureTestingModule({
imports: [Component],
declarations: [WrapperComponent]
});
await TestBed.compileComponents();
__pwUpdateSlots(WrapperComponent, component.options?.slots, componentMetadata.selector);
// TODO: only inject when router is provided
TestBed.inject(Router).initialNavigation();
const fixture = TestBed.createComponent(WrapperComponent);
fixture.nativeElement.id = 'root';
__pwUpdateProps(fixture, component.options?.props);
__pwUpdateEvents(fixture, component.options?.on);
fixture.autoDetectChanges();
return fixture;
}
async function __pwResolveComponent(component) {
return await window.__pwRegistry.resolveImportRef(component.type);
}
/**
@ -122,6 +158,19 @@ function __pwUpdateEvents(fixture, events = {}) {
__pwOutputSubscriptionRegistry.set(fixture, outputSubscriptionRecord);
}
/**
* @param {any} value
* @return {?HTMLElement}
*/
function __pwCreateSlot(value) {
return /** @type {?HTMLElement} */ (
document
.createRange()
.createContextualFragment(value)
.firstChild
);
}
function __pwUpdateSlots(Component, slots = {}, tagName) {
const wrapper = document.createElement(tagName);
for (const [key, value] of Object.entries(slots)) {
@ -157,103 +206,3 @@ function __pwUpdateSlots(Component, slots = {}, tagName) {
TestBed.overrideTemplate(Component, wrapper.outerHTML);
}
/**
* @param {any} value
* @return {?HTMLElement}
*/
function __pwCreateSlot(value) {
return /** @type {?HTMLElement} */ (
document
.createRange()
.createContextualFragment(value)
.firstChild
);
}
/**
* @param {Component} component
*/
async function __pwRenderComponent(component) {
const Component = __pwRegistry.get(component.type);
if (!Component)
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if (component.kind !== 'object')
throw new Error('JSX mount notation is not supported');
const componentMetadata = reflectComponentType(Component);
if (!componentMetadata?.isStandalone)
throw new Error('Only standalone components are supported');
const WrapperComponent = defineComponent({
selector: 'pw-wrapper-component',
template: ``,
})(class {});
TestBed.configureTestingModule({
imports: [Component],
declarations: [WrapperComponent]
});
await TestBed.compileComponents();
__pwUpdateSlots(WrapperComponent, component.options?.slots, componentMetadata.selector);
// TODO: only inject when router is provided
TestBed.inject(Router).initialNavigation();
const fixture = TestBed.createComponent(WrapperComponent);
fixture.nativeElement.id = 'root';
__pwUpdateProps(fixture, component.options?.props);
__pwUpdateEvents(fixture, component.options?.on);
fixture.autoDetectChanges();
return fixture;
}
window.playwrightMount = async (component, rootElement, hooksConfig) => {
await __pwResolveComponent(component);
for (const hook of window.__pw_hooks_before_mount || [])
await hook({ hooksConfig, TestBed });
const fixture = await __pwRenderComponent(component);
for (const hook of window.__pw_hooks_after_mount || [])
await hook({ hooksConfig });
__pwFixtureRegistry.set(rootElement.id, fixture);
};
window.playwrightUnmount = async rootElement => {
const fixture = __pwFixtureRegistry.get(rootElement.id);
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();
};
window.playwrightUpdate = async (rootElement, component) => {
await __pwResolveComponent(component);
if (component.kind === 'jsx')
throw new Error('JSX mount notation is not supported');
if (component.options?.slots)
throw new Error('Update slots is not supported yet');
const fixture = __pwFixtureRegistry.get(rootElement.id);
if (!fixture)
throw new Error('Component was not mounted');
__pwUpdateProps(fixture, component.options?.props);
__pwUpdateEvents(fixture, component.options?.on);
fixture.detectChanges();
};

View file

@ -26,9 +26,9 @@ export default defineConfig({
use: {
trace: 'on-first-retry',
ctViteConfig: {
plugins: [
...analogVitePlugin()
],
// plugins: [
// ...analogVitePlugin()
// ],
resolve: {
alias: {
'@': resolve('./src'),

View file

@ -1,3 +1,4 @@
import '@angular/compiler';
import { beforeMount, afterMount } from '@playwright/experimental-ct-angular/hooks';
import { provideRouter } from '@angular/router';
import { ButtonComponent } from '@/components/button.component';