feat(ct): angular component testing
This commit is contained in:
parent
53a78a315e
commit
8ccb3bb63a
12433
package-lock.json
generated
12433
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
packages/playwright-ct-angular/.npmignore
Normal file
11
packages/playwright-ct-angular/.npmignore
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
**/*
|
||||
|
||||
!README.md
|
||||
!LICENSE
|
||||
!register.d.ts
|
||||
!register.mjs
|
||||
!registerSource.mjs
|
||||
!index.d.ts
|
||||
!index.js
|
||||
!hooks.d.ts
|
||||
!hooks.mjs
|
||||
3
packages/playwright-ct-angular/README.md
Normal file
3
packages/playwright-ct-angular/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
> **BEWARE** This package is EXPERIMENTAL and does not respect semver.
|
||||
|
||||
Read more at https://playwright.dev/docs/test-components
|
||||
17
packages/playwright-ct-angular/cli.js
Executable file
17
packages/playwright-ct-angular/cli.js
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
module.exports = require('@playwright/experimental-ct-core/cli');
|
||||
25
packages/playwright-ct-angular/hooks.d.ts
vendored
Normal file
25
packages/playwright-ct-angular/hooks.d.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import type { TestBedStatic } from '@angular/core/testing';
|
||||
import type { JsonObject } from '@playwright/experimental-ct-core/types/component';
|
||||
|
||||
export declare function beforeMount<HooksConfig extends JsonObject>(
|
||||
callback: (params: { hooksConfig?: HooksConfig, TestBed: TestBedStatic }) => Promise<void>
|
||||
): void;
|
||||
export declare function afterMount<HooksConfig extends JsonObject>(
|
||||
callback: (params: { hooksConfig?: HooksConfig }) => Promise<void>
|
||||
): void;
|
||||
29
packages/playwright-ct-angular/hooks.mjs
Normal file
29
packages/playwright-ct-angular/hooks.mjs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const __pw_hooks_before_mount = [];
|
||||
const __pw_hooks_after_mount = [];
|
||||
|
||||
window.__pw_hooks_before_mount = __pw_hooks_before_mount;
|
||||
window.__pw_hooks_after_mount = __pw_hooks_after_mount;
|
||||
|
||||
export const beforeMount = callback => {
|
||||
__pw_hooks_before_mount.push(callback);
|
||||
};
|
||||
|
||||
export const afterMount = callback => {
|
||||
__pw_hooks_after_mount.push(callback);
|
||||
};
|
||||
75
packages/playwright-ct-angular/index.d.ts
vendored
Normal file
75
packages/playwright-ct-angular/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import type {
|
||||
TestType,
|
||||
PlaywrightTestArgs,
|
||||
PlaywrightTestConfig as BasePlaywrightTestConfig,
|
||||
PlaywrightTestOptions,
|
||||
PlaywrightWorkerArgs,
|
||||
PlaywrightWorkerOptions,
|
||||
Locator,
|
||||
} from '@playwright/test';
|
||||
import type { JsonObject } from '@playwright/experimental-ct-core/types/component';
|
||||
import type { InlineConfig } from 'vite';
|
||||
import type { Type } from '@angular/core';
|
||||
|
||||
export type PlaywrightTestConfig<T = {}, W = {}> = Omit<BasePlaywrightTestConfig<T, W>, 'use'> & {
|
||||
use?: BasePlaywrightTestConfig<T, W>['use'] & {
|
||||
ctPort?: number;
|
||||
ctTemplateDir?: string;
|
||||
ctCacheDir?: string;
|
||||
ctViteConfig?: InlineConfig | (() => Promise<InlineConfig>);
|
||||
};
|
||||
};
|
||||
|
||||
type ComponentSlot = string | string[];
|
||||
type ComponentSlots = Record<string, ComponentSlot> & { default?: ComponentSlot };
|
||||
|
||||
type ComponentEvents = Record<string, Function>;
|
||||
|
||||
export interface MountOptions<HooksConfig extends JsonObject, Component> {
|
||||
props?: Partial<Component>, // TODO: filter props
|
||||
slots?: ComponentSlots;
|
||||
on?: ComponentEvents;
|
||||
hooksConfig?: HooksConfig;
|
||||
}
|
||||
|
||||
interface MountResult<Component> extends Locator {
|
||||
unmount(): Promise<void>;
|
||||
update(options: {
|
||||
props?: Partial<Component>,
|
||||
on?: Partial<ComponentEvents>,
|
||||
}): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ComponentFixtures {
|
||||
mount<HooksConfig extends JsonObject, Component = unknown>(
|
||||
component: Type<Component>,
|
||||
options?: MountOptions<HooksConfig, Component>
|
||||
): Promise<MountResult<Component>>;
|
||||
}
|
||||
|
||||
export const test: TestType<
|
||||
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
|
||||
PlaywrightWorkerArgs & PlaywrightWorkerOptions
|
||||
>;
|
||||
|
||||
export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig;
|
||||
export function defineConfig<T>(config: PlaywrightTestConfig<T>): PlaywrightTestConfig<T>;
|
||||
export function defineConfig<T, W>(config: PlaywrightTestConfig<T, W>): PlaywrightTestConfig<T, W>;
|
||||
|
||||
export { expect, devices } from '@playwright/test';
|
||||
38
packages/playwright-ct-angular/index.js
Normal file
38
packages/playwright-ct-angular/index.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
||||
const path = require('path');
|
||||
|
||||
process.env['NODE_ENV'] = 'test';
|
||||
|
||||
function plugin() {
|
||||
// Only fetch upon request to avoid resolution in workers.
|
||||
const { createPlugin } = require('@playwright/experimental-ct-core/lib/vitePlugin');
|
||||
return createPlugin(
|
||||
path.join(__dirname, 'registerSource.mjs'),
|
||||
() => import('@analogjs/vite-plugin-angular').then(plugin => {
|
||||
// TODO: remove the typeof plugin.default check
|
||||
if (typeof plugin.default === 'function')
|
||||
return plugin.default({ jit: false });
|
||||
return plugin.default.default({ jit: false });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] });
|
||||
|
||||
module.exports = { test, expect, devices, defineConfig };
|
||||
63
packages/playwright-ct-angular/package.json
Normal file
63
packages/playwright-ct-angular/package.json
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-angular",
|
||||
"version": "1.40.0-next",
|
||||
"description": "Playwright Component Testing for Angular",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sand4rt/playwright-ct-angular.git"
|
||||
},
|
||||
"homepage": "https://playwright.dev",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./register": {
|
||||
"types": "./register.d.ts",
|
||||
"default": "./register.mjs"
|
||||
},
|
||||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@analogjs/vite-plugin-angular": "0.2.10",
|
||||
"@angular-devkit/build-angular": "^16.1.0",
|
||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
||||
"vite": "^4.4.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/animations": "^16.1.7",
|
||||
"@angular/common": "^16.1.7",
|
||||
"@angular/compiler": "^16.1.7",
|
||||
"@angular/compiler-cli": "^16.1.7",
|
||||
"@angular/core": "^16.1.7",
|
||||
"@angular/platform-browser": "^16.1.7",
|
||||
"@angular/platform-browser-dynamic": "^16.1.7",
|
||||
"@angular/router": "^16.1.7",
|
||||
"@playwright/test": "1.38.1",
|
||||
"rxjs": "~7.8.1",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.4",
|
||||
"zone.js": "~0.13.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@playwright/test": ">=1.38.1",
|
||||
"typescript": ">=4.9.3",
|
||||
"@angular/common": ">=15.1.0 || >=16.0.0",
|
||||
"@angular/platform-browser": ">=15.1.0 || >=16.0.0",
|
||||
"@angular/router": ">=15.1.0 || >=16.0.0",
|
||||
"@angular/core": ">=15.1.0 || >=16.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "./cli.js"
|
||||
}
|
||||
}
|
||||
17
packages/playwright-ct-angular/register.d.ts
vendored
Normal file
17
packages/playwright-ct-angular/register.d.ts
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export default function pwRegister(components: Record<string, any>): void
|
||||
21
packages/playwright-ct-angular/register.mjs
Normal file
21
packages/playwright-ct-angular/register.mjs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { pwRegister } from './registerSource.mjs';
|
||||
|
||||
export default components => {
|
||||
pwRegister(components);
|
||||
};
|
||||
237
packages/playwright-ct-angular/registerSource.mjs
Normal file
237
packages/playwright-ct-angular/registerSource.mjs
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* 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 'zone.js';
|
||||
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 { 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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Component} component
|
||||
* @returns {component is JsxComponent | ObjectComponent}
|
||||
*/
|
||||
function isComponent(component) {
|
||||
return !(typeof component !== 'object' || Array.isArray(component));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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])
|
||||
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)))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@angular/core/testing').ComponentFixture} fixture
|
||||
*/
|
||||
function __pwUpdateProps(fixture, props = {}) {
|
||||
for (const [name, value] of Object.entries(props))
|
||||
fixture.debugElement.children[0].context[name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function __pwUpdateSlots(Component, slots = {}, tagName) {
|
||||
const wrapper = document.createElement(tagName);
|
||||
for (const [key, value] of Object.entries(slots)) {
|
||||
let slotElements;
|
||||
if (typeof value !== 'object')
|
||||
slotElements = [__pwCreateSlot(value)];
|
||||
|
||||
if (Array.isArray(value))
|
||||
slotElements = value.map(__pwCreateSlot);
|
||||
|
||||
if (!slotElements)
|
||||
throw new Error(`Invalid slot with name: \`${key}\` supplied to \`mount()\``);
|
||||
|
||||
for (const slotElement of slotElements) {
|
||||
if (!slotElement)
|
||||
throw new Error(`Invalid slot with name: \`${key}\` supplied to \`mount()\``);
|
||||
|
||||
if (key === 'default') {
|
||||
wrapper.appendChild(slotElement);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slotElement.nodeName === '#text') {
|
||||
throw new Error(
|
||||
`Invalid slot with name: \`${key}\` supplied to \`mount()\`, expected \`HTMLElement\` but received \`TextNode\`.`
|
||||
);
|
||||
}
|
||||
|
||||
slotElement.setAttribute(key, '');
|
||||
wrapper.appendChild(slotElement);
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
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();
|
||||
};
|
||||
45
tests/components/ct-angular/.gitignore
vendored
Normal file
45
tests/components/ct-angular/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
19
tests/components/ct-angular/README.md
Normal file
19
tests/components/ct-angular/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# ct-angular
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.0.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
80
tests/components/ct-angular/angular.json
Normal file
80
tests/components/ct-angular/angular.json
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"ct-angular": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/ct-angular",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/assets/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/assets/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "ct-angular:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "ct-angular:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "ct-angular:build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
tests/components/ct-angular/package.json
Normal file
32
tests/components/ct-angular/package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "ct-angular",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "npx playwright test",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^15.1.0",
|
||||
"@angular/common": "^15.1.0",
|
||||
"@angular/compiler": "^15.1.0",
|
||||
"@angular/core": "^15.1.0",
|
||||
"@angular/forms": "^15.1.0",
|
||||
"@angular/platform-browser": "^15.1.0",
|
||||
"@angular/platform-browser-dynamic": "^15.1.0",
|
||||
"@angular/router": "^15.1.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^15.1.0",
|
||||
"@angular/cli": "~15.1.0",
|
||||
"@angular/compiler-cli": "^15.1.0",
|
||||
"typescript": "~4.9.4"
|
||||
}
|
||||
}
|
||||
49
tests/components/ct-angular/playwright.config.ts
Normal file
49
tests/components/ct-angular/playwright.config.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { defineConfig, devices } from '@playwright/experimental-ct-angular';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: 'tests',
|
||||
forbidOnly: !!process.env['CI'],
|
||||
retries: process.env['CI'] ? 2 : 0,
|
||||
reporter: 'html',
|
||||
use: {
|
||||
trace: 'on-first-retry',
|
||||
ctViteConfig: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('./src'),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
],
|
||||
});
|
||||
12
tests/components/ct-angular/playwright/index.html
Normal file
12
tests/components/ct-angular/playwright/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Testing Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
tests/components/ct-angular/playwright/index.ts
Normal file
31
tests/components/ct-angular/playwright/index.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { beforeMount, afterMount } from '@playwright/experimental-ct-angular/hooks';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { ButtonComponent } from '@/components/button.component';
|
||||
import { TOKEN } from '@/components/inject.component';
|
||||
import { routes } from '@/router';
|
||||
import '@/assets/styles.css';
|
||||
|
||||
export type HooksConfig = {
|
||||
routing?: boolean;
|
||||
injectToken?: boolean;
|
||||
};
|
||||
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig, TestBed }) => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ButtonComponent],
|
||||
});
|
||||
|
||||
if (hooksConfig?.routing)
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideRouter(routes)],
|
||||
});
|
||||
|
||||
if (hooksConfig?.injectToken)
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{ provide: TOKEN, useValue: { text: 'has been overwritten' }}]
|
||||
})
|
||||
});
|
||||
|
||||
afterMount<HooksConfig>(async () => {
|
||||
console.log('After mount');
|
||||
});
|
||||
17
tests/components/ct-angular/src/app.component.ts
Normal file
17
tests/components/ct-angular/src/app.component.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [RouterModule],
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<header>
|
||||
<img alt="Angular logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
|
||||
<a routerLink="/">Login</a>
|
||||
<a routerLink="/dashboard">Dashboard</a>
|
||||
</header>
|
||||
<router-outlet></router-outlet>
|
||||
`
|
||||
})
|
||||
export class AppComponent {}
|
||||
BIN
tests/components/ct-angular/src/assets/favicon.ico
Normal file
BIN
tests/components/ct-angular/src/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 948 B |
12
tests/components/ct-angular/src/assets/logo.svg
Normal file
12
tests/components/ct-angular/src/assets/logo.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 335.88126 355.80875" width="335" xml:space="preserve">
|
||||
<g transform="matrix(1.25,0,0,-1.25,-188.84625,636.71875)">
|
||||
<g transform="scale(0.1,0.1)">
|
||||
<path d="M 2850,5093.75 1510.77,4622.45 1722.39,2867.82 2851.42,2247.28 3986.27,2876.23 4197.82,4630.79 2850,5093.75 z" style="fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
<path d="m 4064.25,4529.39 -1217.38,415.13 0,-2548.98 1020.22,564.62 197.16,1569.23 z" style="fill:#af2b2d;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
<path d="m 1661.05,4521.89 181.34,-1569.22 1004.47,-557.13 0,2549.03 -1185.81,-422.68 z" style="fill:#df2e31;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
<path d="m 3129.05,3676.7 -279.96,584.64 -246.46,-584.64 526.42,0 z m 106.74,-245.83 -742.11,0 -166.02,-415.26 -308.82,-5.71 828.04,1842.06 856.9,-1842.06 -286.23,0 -181.76,420.97 z" style="fill:#f2f2f2;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
<path d="m 2846.87,4851.96 2.21,-590.62 279.67,-585.09 -281.25,0 -0.63,-245 388.9,-0.38 181.77,-421.04 295.49,-5.48 -866.16,1847.61 z" style="fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
20
tests/components/ct-angular/src/assets/styles.css
Normal file
20
tests/components/ct-angular/src/assets/styles.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #e3e3e3;
|
||||
background-color: #1b1b1d;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<button (click)="submit.emit('hello')">{{title}}</button>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'button-component',
|
||||
templateUrl: './button.component.html',
|
||||
})
|
||||
export class ButtonComponent {
|
||||
@Input() title!: string;
|
||||
@Output() submit = new EventEmitter();
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `<div>test</div>`,
|
||||
})
|
||||
export class ComponentComponent {}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<div (click)="submit.emit('hello')">
|
||||
<div data-testid="props">{{ count }}</div>
|
||||
<div data-testid="remount-count">{{ this.remountCount }}</div>
|
||||
<ng-content select="[main]"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class CounterComponent {
|
||||
remountCount = Number(localStorage.getItem('remountCount'));
|
||||
@Input() count!: number;
|
||||
|
||||
@Output() submit = new EventEmitter();
|
||||
|
||||
constructor() {
|
||||
localStorage.setItem('remountCount', String(this.remountCount++))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<div>
|
||||
<h1>Welcome!</h1>
|
||||
<main>
|
||||
<ng-content></ng-content>
|
||||
</main>
|
||||
<footer>
|
||||
Thanks for visiting.
|
||||
</footer>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class DefaultSlotComponent {}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: ``,
|
||||
})
|
||||
export class EmptyComponent {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Component, inject, InjectionToken } from '@angular/core';
|
||||
|
||||
export const TOKEN = new InjectionToken<{ text: string }>('gets overwritten');
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `<div>{{ data.text }}</div>`,
|
||||
})
|
||||
export class InjectComponent {
|
||||
public data = inject(TOKEN);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `<div>root 1</div><div>root 2</div>`,
|
||||
})
|
||||
export class MultiRootComponent {}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<div>
|
||||
<header>
|
||||
<ng-content select="[header]"></ng-content>
|
||||
</header>
|
||||
<main>
|
||||
<ng-content select="[main]"></ng-content>
|
||||
</main>
|
||||
<footer>
|
||||
<ng-content select="[footer]"></ng-content>
|
||||
</footer>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class NamedSlotsComponent {}
|
||||
13
tests/components/ct-angular/src/index.html
Normal file
13
tests/components/ct-angular/src/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Angular</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="assets/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
10
tests/components/ct-angular/src/main.ts
Normal file
10
tests/components/ct-angular/src/main.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { AppComponent } from '@/app.component';
|
||||
import { routes } from '@/router';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
provideRouter(routes)
|
||||
]
|
||||
}).catch(err => console.error(err));
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app-dashboard',
|
||||
template: `<main>Dashboard</main>`
|
||||
})
|
||||
export class DashboardComponent {}
|
||||
8
tests/components/ct-angular/src/pages/login.component.ts
Normal file
8
tests/components/ct-angular/src/pages/login.component.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app-login',
|
||||
template: `<main>Login</main>`
|
||||
})
|
||||
export class LoginComponent {}
|
||||
8
tests/components/ct-angular/src/router/index.ts
Normal file
8
tests/components/ct-angular/src/router/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Routes } from '@angular/router';
|
||||
import { DashboardComponent } from '@/pages/dashboard.component';
|
||||
import { LoginComponent } from '@/pages/login.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: LoginComponent },
|
||||
{ path: 'dashboard', component: DashboardComponent },
|
||||
];
|
||||
14
tests/components/ct-angular/tests/angular-router.spec.ts
Normal file
14
tests/components/ct-angular/tests/angular-router.spec.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { test, expect } from '@playwright/experimental-ct-angular';
|
||||
import type { HooksConfig } from 'playwright';
|
||||
import { AppComponent } from '@/app.component';
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount<HooksConfig>(AppComponent, {
|
||||
hooksConfig: { routing: true },
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
await expect(component.getByRole('main')).toHaveText('Dashboard');
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
});
|
||||
16
tests/components/ct-angular/tests/events.spec.ts
Normal file
16
tests/components/ct-angular/tests/events.spec.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { test, expect } from '@playwright/experimental-ct-angular';
|
||||
import { ButtonComponent } from '@/components/button.component';
|
||||
|
||||
test('emit an submit event when the button is clicked', async ({ mount }) => {
|
||||
const messages: string[] = [];
|
||||
const component = await mount(ButtonComponent, {
|
||||
props: {
|
||||
title: 'Submit',
|
||||
},
|
||||
on: {
|
||||
submit: (data: string) => messages.push(data),
|
||||
},
|
||||
});
|
||||
await component.click();
|
||||
expect(messages).toEqual(['hello']);
|
||||
});
|
||||
11
tests/components/ct-angular/tests/injection.spec.ts
Normal file
11
tests/components/ct-angular/tests/injection.spec.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { test, expect } from '@playwright/experimental-ct-angular';
|
||||
import type { HooksConfig } from 'playwright';
|
||||
import { InjectComponent } from '@/components/inject.component';
|
||||
|
||||
test('inject a token', async ({ page, mount }) => {
|
||||
const component = await mount<HooksConfig>(InjectComponent, {
|
||||
hooksConfig: { injectToken: true },
|
||||
});
|
||||
await expect(component).toHaveText('has been overwritten');
|
||||
await expect(component).not.toHaveText('gets overwritten');
|
||||
});
|
||||
25
tests/components/ct-angular/tests/render.spec.ts
Normal file
25
tests/components/ct-angular/tests/render.spec.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { test, expect } from '@playwright/experimental-ct-angular';
|
||||
import { ButtonComponent } from '@/components/button.component';
|
||||
import { EmptyComponent } from '@/components/empty.component';
|
||||
import { ComponentComponent } from '@/components/component.component';
|
||||
|
||||
test('render props', async ({ mount }) => {
|
||||
const component = await mount(ButtonComponent, {
|
||||
props: {
|
||||
title: 'Submit',
|
||||
},
|
||||
});
|
||||
await expect(component).toContainText('Submit');
|
||||
});
|
||||
|
||||
test('get textContent of the empty component', async ({ mount }) => {
|
||||
const component = await mount(EmptyComponent);
|
||||
expect(await component.allTextContents()).toEqual(['']);
|
||||
expect(await component.textContent()).toBe('');
|
||||
await expect(component).toHaveText('');
|
||||
});
|
||||
|
||||
test('render a component without options', async ({ mount }) => {
|
||||
const component = await mount(ComponentComponent);
|
||||
await expect(component).toContainText('test');
|
||||
});
|
||||
47
tests/components/ct-angular/tests/slots.spec.ts
Normal file
47
tests/components/ct-angular/tests/slots.spec.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { test, expect } from '@playwright/experimental-ct-angular';
|
||||
import { DefaultSlotComponent } from '@/components/default-slot.component';
|
||||
import { NamedSlotsComponent } from '@/components/named-slots.component';
|
||||
|
||||
test('render a default slot', async ({ mount }) => {
|
||||
const component = await mount(DefaultSlotComponent, {
|
||||
slots: {
|
||||
default: '<strong>Main Content</strong>',
|
||||
},
|
||||
});
|
||||
await expect(component.getByRole('strong')).toContainText('Main Content');
|
||||
});
|
||||
|
||||
test('render a component as slot', async ({ mount }) => {
|
||||
const component = await mount(DefaultSlotComponent, {
|
||||
slots: {
|
||||
default: '<button-component title="Submit" />', // component is registered globally in /playwright/index.ts
|
||||
},
|
||||
});
|
||||
await expect(component).toContainText('Submit');
|
||||
});
|
||||
|
||||
test('render a component with multiple slots', async ({ mount }) => {
|
||||
const component = await mount(DefaultSlotComponent, {
|
||||
slots: {
|
||||
default: [
|
||||
'<div data-testid="one">One</div>',
|
||||
'<div data-testid="two">Two</div>',
|
||||
],
|
||||
},
|
||||
});
|
||||
await expect(component.getByTestId('one')).toContainText('One');
|
||||
await expect(component.getByTestId('two')).toContainText('Two');
|
||||
});
|
||||
|
||||
test('render a component with a named slots', async ({ mount }) => {
|
||||
const component = await mount(NamedSlotsComponent, {
|
||||
slots: {
|
||||
header: '<div header>Header</div>', // <div header is optional
|
||||
main: '<div>Main Content</div>',
|
||||
footer: '<div>Footer</div>',
|
||||
},
|
||||
});
|
||||
await expect(component).toContainText('Header');
|
||||
await expect(component).toContainText('Main Content');
|
||||
await expect(component).toContainText('Footer');
|
||||
});
|
||||
23
tests/components/ct-angular/tests/unmount.spec.ts
Normal file
23
tests/components/ct-angular/tests/unmount.spec.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { test, expect } from '@playwright/experimental-ct-angular';
|
||||
import { ButtonComponent } from '@/components/button.component';
|
||||
import { MultiRootComponent } from '@/components/multi-root.component';
|
||||
|
||||
test('unmount', async ({ page, mount }) => {
|
||||
const component = await mount(ButtonComponent, {
|
||||
props: {
|
||||
title: 'Submit',
|
||||
},
|
||||
});
|
||||
await expect(page.locator('#root')).toContainText('Submit');
|
||||
await component.unmount();
|
||||
await expect(page.locator('#root')).not.toContainText('Submit');
|
||||
});
|
||||
|
||||
test('unmount a multi root component', async ({ mount, page }) => {
|
||||
const component = await mount(MultiRootComponent);
|
||||
await expect(page.locator('#root')).toContainText('root 1');
|
||||
await expect(page.locator('#root')).toContainText('root 2');
|
||||
await component.unmount();
|
||||
await expect(page.locator('#root')).not.toContainText('root 1');
|
||||
await expect(page.locator('#root')).not.toContainText('root 2');
|
||||
});
|
||||
32
tests/components/ct-angular/tests/update.spec.ts
Normal file
32
tests/components/ct-angular/tests/update.spec.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { test, expect } from '@playwright/experimental-ct-angular';
|
||||
import { CounterComponent } from '@/components/counter.component';
|
||||
|
||||
test('update props without remounting', async ({ mount }) => {
|
||||
const component = await mount(CounterComponent, {
|
||||
props: { count: 9001 },
|
||||
});
|
||||
await expect(component.getByTestId('props')).toContainText('9001');
|
||||
|
||||
await component.update({
|
||||
props: { count: 1337 },
|
||||
});
|
||||
await expect(component).not.toContainText('9001');
|
||||
await expect(component.getByTestId('props')).toContainText('1337');
|
||||
|
||||
await expect(component.getByTestId('remount-count')).toContainText('1');
|
||||
});
|
||||
|
||||
test('update event listeners without remounting', async ({ mount }) => {
|
||||
const component = await mount(CounterComponent);
|
||||
|
||||
const messages: string[] = [];
|
||||
await component.update({
|
||||
on: {
|
||||
submit: (data: string) => messages.push(data),
|
||||
},
|
||||
});
|
||||
await component.click();
|
||||
expect(messages).toEqual(['hello']);
|
||||
|
||||
await expect(component.getByTestId('remount-count')).toContainText('1');
|
||||
});
|
||||
14
tests/components/ct-angular/tsconfig.app.json
Normal file
14
tests/components/ct-angular/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
36
tests/components/ct-angular/tsconfig.json
Normal file
36
tests/components/ct-angular/tsconfig.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"dom"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true,
|
||||
}
|
||||
}
|
||||
9
tests/components/ct-angular/tsconfig.spec.json
Normal file
9
tests/components/ct-angular/tsconfig.spec.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["tests/**/*.spec.ts", "tests/**/*.d.ts"]
|
||||
}
|
||||
|
|
@ -199,6 +199,11 @@ const workspace = new Workspace(ROOT_PATH, [
|
|||
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-core'),
|
||||
files: ['LICENSE'],
|
||||
}),
|
||||
new PWPackage({
|
||||
name: '@playwright/experimental-ct-angular',
|
||||
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-angular'),
|
||||
files: ['LICENSE'],
|
||||
}),
|
||||
new PWPackage({
|
||||
name: '@playwright/experimental-ct-react',
|
||||
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-react'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue