mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-27 05:46:28 +03:00
feat(ct): react component as props (#28382)
closes: https://github.com/microsoft/playwright/issues/28367#issuecomment-1830298864
This commit is contained in:
parent
afb2582eaa
commit
4d62784eeb
@ -140,7 +140,7 @@ async function innerMount(page: Page, jsxOrType: JsxComponent | string, options:
|
||||
|
||||
function createComponent(jsxOrType: JsxComponent | string, options: Omit<MountOptions, 'hooksConfig'> = {}): Component {
|
||||
if (typeof jsxOrType !== 'string') return jsxOrType;
|
||||
return { kind: 'object', type: jsxOrType, options };
|
||||
return { __pw_component_marker: true, kind: 'object', type: jsxOrType, options };
|
||||
}
|
||||
|
||||
function wrapFunctions(object: any, page: Page, callbacks: Function[]) {
|
||||
|
@ -121,7 +121,8 @@ export default declare((api: BabelAPI) => {
|
||||
children.push(t.spreadElement(child.expression));
|
||||
}
|
||||
|
||||
const component = [
|
||||
const component: T.ObjectProperty[] = [
|
||||
t.objectProperty(t.identifier('__pw_component_marker'), t.booleanLiteral(true)),
|
||||
t.objectProperty(t.identifier('kind'), t.stringLiteral('jsx')),
|
||||
t.objectProperty(t.identifier('type'), t.stringLiteral(componentName)),
|
||||
t.objectProperty(t.identifier('props'), t.objectExpression(props)),
|
||||
|
@ -22,6 +22,7 @@ export type JsonObject = { [Key in string]?: JsonValue };
|
||||
// JsxComponentChild can be anything, consider cases like: <>{1}</>, <>{null}</>
|
||||
export type JsxComponentChild = JsxComponent | string | number | boolean | null;
|
||||
export type JsxComponent = {
|
||||
__pw_component_marker: true,
|
||||
kind: 'jsx',
|
||||
type: string,
|
||||
props: Record<string, any>,
|
||||
@ -36,6 +37,7 @@ export type MountOptions = {
|
||||
};
|
||||
|
||||
export type ObjectComponent = {
|
||||
__pw_component_marker: true,
|
||||
kind: 'object',
|
||||
type: string,
|
||||
options?: MountOptions
|
||||
|
@ -32,7 +32,7 @@ const __pwRegistry = new Map();
|
||||
const __pwRootRegistry = new Map();
|
||||
|
||||
/**
|
||||
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
|
||||
* @param {Record<string, () => Promise<FrameworkComponent>>} components
|
||||
*/
|
||||
export function pwRegister(components) {
|
||||
for (const [name, value] of Object.entries(components))
|
||||
@ -44,7 +44,7 @@ export function pwRegister(components) {
|
||||
* @returns {component is JsxComponent}
|
||||
*/
|
||||
function isComponent(component) {
|
||||
return !(typeof component !== 'object' || Array.isArray(component));
|
||||
return component.__pw_component_marker === true && component.kind === 'jsx';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,6 +73,23 @@ async function __pwResolveComponent(component) {
|
||||
|
||||
if (component.children?.length)
|
||||
await Promise.all(component.children.map(child => __pwResolveComponent(child)));
|
||||
|
||||
if (component.props)
|
||||
await __resolveProps(component.props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, any>} props
|
||||
*/
|
||||
async function __resolveProps(props) {
|
||||
for (const prop of Object.values(props)) {
|
||||
if (Array.isArray(prop))
|
||||
await Promise.all(prop.map(child => __pwResolveComponent(child)));
|
||||
else if (isComponent(prop))
|
||||
await __pwResolveComponent(prop);
|
||||
else if (typeof prop === 'object' && prop !== null)
|
||||
await __resolveProps(prop);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,18 +103,37 @@ function __renderChild(child) {
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, any>} props
|
||||
*/
|
||||
function __renderProps(props) {
|
||||
const newProps = {};
|
||||
for (const [key, prop] of Object.entries(props)) {
|
||||
if (Array.isArray(prop))
|
||||
newProps[key] = prop.map(child => __renderChild(child));
|
||||
else if (isComponent(prop))
|
||||
newProps[key] = __renderChild(prop);
|
||||
else if (typeof prop === 'object' && prop !== null)
|
||||
newProps[key] = __renderProps(prop);
|
||||
else
|
||||
newProps[key] = prop;
|
||||
}
|
||||
return newProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JsxComponent} component
|
||||
*/
|
||||
function __pwRender(component) {
|
||||
const componentFunc = __pwRegistry.get(component.type);
|
||||
const props = __renderProps(component.props || {});
|
||||
const children = component.children?.map(child => __renderChild(child)).filter(child => {
|
||||
if (typeof child === 'string')
|
||||
return !!child.trim();
|
||||
return true;
|
||||
});
|
||||
const reactChildren = Array.isArray(children) && children.length === 1 ? children[0] : children;
|
||||
return __pwReact.createElement(componentFunc || component.type, component.props, reactChildren);
|
||||
return __pwReact.createElement(componentFunc || component.type, props, reactChildren);
|
||||
}
|
||||
|
||||
window.playwrightMount = async (component, rootElement, hooksConfig) => {
|
||||
@ -117,7 +153,6 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
|
||||
'Attempting to mount a component into an container that already has a React root'
|
||||
);
|
||||
}
|
||||
|
||||
const root = __pwCreateRoot(rootElement);
|
||||
__pwRootRegistry.set(rootElement, root);
|
||||
root.render(App());
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type ComponentAsProp = {
|
||||
component: ReactNode[] | ReactNode;
|
||||
};
|
||||
|
||||
export function ComponentAsProp({ component }: ComponentAsProp) {
|
||||
return <div>{component}</div>
|
||||
}
|
@ -1,12 +1,24 @@
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import Button from '@/components/Button';
|
||||
import EmptyFragment from '@/components/EmptyFragment';
|
||||
import { ComponentAsProp } from '@/components/ComponentAsProp';
|
||||
|
||||
test('render props', async ({ mount }) => {
|
||||
const component = await mount(<Button title="Submit" />);
|
||||
await expect(component).toContainText('Submit');
|
||||
});
|
||||
|
||||
test('render component as props', async ({ mount }) => {
|
||||
const component = await mount(<ComponentAsProp component={<Button title="Submit" />} />);
|
||||
await expect(component.getByRole('button', { name: 'submit' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('render jsx array as props', async ({ mount }) => {
|
||||
const component = await mount(<ComponentAsProp component={[<h4>{[4]}</h4>,[[<p>[2,3]</p>]]]} />);
|
||||
await expect(component.getByRole('heading', { level: 4 })).toHaveText('4');
|
||||
await expect(component.getByRole('paragraph')).toHaveText('[2,3]');
|
||||
});
|
||||
|
||||
test('render attributes', async ({ mount }) => {
|
||||
const component = await mount(<Button className="primary" title="Submit" />);
|
||||
await expect(component).toHaveClass('primary');
|
||||
|
Loading…
Reference in New Issue
Block a user