fix(ct): empty slots or children (#28225)

closes: https://github.com/microsoft/playwright/issues/28212
This commit is contained in:
Sander 2023-11-28 00:53:50 +01:00 committed by GitHub
parent 96ce1a8f88
commit f58c1f37eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 55 additions and 26 deletions

View File

@ -121,12 +121,15 @@ export default declare((api: BabelAPI) => {
children.push(t.spreadElement(child.expression)); children.push(t.spreadElement(child.expression));
} }
path.replaceWith(t.objectExpression([ const component = [
t.objectProperty(t.identifier('kind'), t.stringLiteral('jsx')), t.objectProperty(t.identifier('kind'), t.stringLiteral('jsx')),
t.objectProperty(t.identifier('type'), t.stringLiteral(componentName)), t.objectProperty(t.identifier('type'), t.stringLiteral(componentName)),
t.objectProperty(t.identifier('props'), t.objectExpression(props)), t.objectProperty(t.identifier('props'), t.objectExpression(props)),
t.objectProperty(t.identifier('children'), t.arrayExpression(children)), ];
])); if (children.length)
component.push(t.objectProperty(t.identifier('children'), t.arrayExpression(children)));
path.replaceWith(t.objectExpression(component));
} }
} }
}; };

View File

@ -25,7 +25,7 @@ export type JsxComponent = {
kind: 'jsx', kind: 'jsx',
type: string, type: string,
props: Record<string, any>, props: Record<string, any>,
children: JsxComponentChild[], children?: JsxComponentChild[],
}; };
export type MountOptions = { export type MountOptions = {

View File

@ -71,7 +71,7 @@ async function __pwResolveComponent(component) {
if (componentFactory) if (componentFactory)
__pwRegistry.set(component.type, await componentFactory()); __pwRegistry.set(component.type, await componentFactory());
if ('children' in component) if (component.children?.length)
await Promise.all(component.children.map(child => __pwResolveComponent(child))); await Promise.all(component.children.map(child => __pwResolveComponent(child)));
} }
@ -91,7 +91,7 @@ function __renderChild(child) {
*/ */
function __pwRender(component) { function __pwRender(component) {
const componentFunc = __pwRegistry.get(component.type); const componentFunc = __pwRegistry.get(component.type);
const children = component.children.map(child => __renderChild(child)).filter(child => { const children = component.children?.map(child => __renderChild(child)).filter(child => {
if (typeof child === 'string') if (typeof child === 'string')
return !!child.trim(); return !!child.trim();
return true; return true;

View File

@ -70,7 +70,7 @@ async function __pwResolveComponent(component) {
if (componentFactory) if (componentFactory)
__pwRegistry.set(component.type, await componentFactory()); __pwRegistry.set(component.type, await componentFactory());
if ('children' in component) if (component.children?.length)
await Promise.all(component.children.map(child => __pwResolveComponent(child))); await Promise.all(component.children.map(child => __pwResolveComponent(child)));
} }
@ -90,7 +90,7 @@ function __renderChild(child) {
*/ */
function __pwRender(component) { function __pwRender(component) {
const componentFunc = __pwRegistry.get(component.type); const componentFunc = __pwRegistry.get(component.type);
const children = component.children.map(child => __renderChild(child)).filter(child => { const children = component.children?.map(child => __renderChild(child)).filter(child => {
if (typeof child === 'string') if (typeof child === 'string')
return !!child.trim(); return !!child.trim();
return true; return true;

View File

@ -69,7 +69,7 @@ async function __pwResolveComponent(component) {
if (componentFactory) if (componentFactory)
__pwRegistry.set(component.type, await componentFactory()); __pwRegistry.set(component.type, await componentFactory());
if ('children' in component) if (component.children?.length)
await Promise.all(component.children.map(child => __pwResolveComponent(child))); await Promise.all(component.children.map(child => __pwResolveComponent(child)));
} }
@ -89,7 +89,7 @@ function __pwCreateChild(child) {
*/ */
function __pwCreateComponent(component) { function __pwCreateComponent(component) {
const componentFunc = __pwRegistry.get(component.type); const componentFunc = __pwRegistry.get(component.type);
const children = component.children.map(child => __pwCreateChild(child)).filter(child => { const children = component.children?.map(child => __pwCreateChild(child)).filter(child => {
if (typeof child === 'string') if (typeof child === 'string')
return !!child.trim(); return !!child.trim();
return true; return true;

View File

@ -72,7 +72,7 @@ async function __pwResolveComponent(component) {
if (componentFactory) if (componentFactory)
__pwRegistry.set(component.type, await componentFactory()); __pwRegistry.set(component.type, await componentFactory());
if ('children' in component) if ('children' in component && component.children?.length)
await Promise.all(component.children.map(child => __pwResolveComponent(child))); await Promise.all(component.children.map(child => __pwResolveComponent(child)));
} }
@ -156,7 +156,7 @@ function __pwCreateComponent(component) {
if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') { if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') {
const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:')); const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:'));
const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default'; const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default';
slots[slot] = child.children.map(__pwCreateChild); slots[slot] = child.children?.map(__pwCreateChild);
} else { } else {
children.push(__pwCreateChild(child)); children.push(__pwCreateChild(child));
} }

View File

@ -71,7 +71,7 @@ async function __pwResolveComponent(component) {
if (componentFactory) if (componentFactory)
__pwRegistry.set(component.type, await componentFactory()); __pwRegistry.set(component.type, await componentFactory());
if ('children' in component) if ('children' in component && component.children?.length)
await Promise.all(component.children.map(child => __pwResolveComponent(child))); await Promise.all(component.children.map(child => __pwResolveComponent(child)));
} }
@ -124,7 +124,7 @@ function __pwCreateComponent(component) {
if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') { if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') {
const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:')); const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:'));
const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default'; const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default';
nodeData.scopedSlots[slot] = () => child.children.map(c => __pwCreateChild(c)); nodeData.scopedSlots[slot] = () => child.children?.map(c => __pwCreateChild(c));
} else { } else {
children.push(__pwCreateChild(child)); children.push(__pwCreateChild(child));
} }

View File

@ -1,3 +1,4 @@
export default function EmptyFragment() { export default function EmptyFragment(props: unknown) {
Object.assign(window, { props });
return <>{[]}</>; return <>{[]}</>;
} }

View File

@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => {
await expect(component).toHaveClass('primary'); await expect(component).toHaveClass('primary');
}); });
test('get textContent of the empty fragment', async ({ mount }) => { test('render an empty component', async ({ mount, page }) => {
const component = await mount(<EmptyFragment />); const component = await mount(<EmptyFragment />);
expect(await page.evaluate(() => 'props' in window && window.props)).toEqual({});
expect(await component.allTextContents()).toEqual(['']); expect(await component.allTextContents()).toEqual(['']);
expect(await component.textContent()).toBe(''); expect(await component.textContent()).toBe('');
await expect(component).toHaveText(''); await expect(component).toHaveText('');

View File

@ -1,3 +1,4 @@
export default function EmptyFragment() { export default function EmptyFragment(props: unknown) {
Object.assign(window, { props });
return <>{[]}</>; return <>{[]}</>;
} }

View File

@ -20,8 +20,9 @@ test('render delayed data', async ({ mount }) => {
await expect(component).toHaveText('complete'); await expect(component).toHaveText('complete');
}); });
test('get textContent of the empty fragment', async ({ mount }) => { test('render an empty component', async ({ mount, page }) => {
const component = await mount(<EmptyFragment />); const component = await mount(<EmptyFragment />);
expect(await page.evaluate(() => 'props' in window && window.props)).toEqual({});
expect(await component.allTextContents()).toEqual(['']); expect(await component.allTextContents()).toEqual(['']);
expect(await component.textContent()).toBe(''); expect(await component.textContent()).toBe('');
await expect(component).toHaveText(''); await expect(component).toHaveText('');

View File

@ -1,3 +1,4 @@
export default function EmptyFragment() { export default function EmptyFragment(props: unknown) {
Object.assign(window, { props });
return <>{[]}</>; return <>{[]}</>;
} }

View File

@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => {
await expect(component).toHaveClass('primary'); await expect(component).toHaveClass('primary');
}); });
test('get textContent of the empty fragment', async ({ mount }) => { test('render an empty component', async ({ mount, page }) => {
const component = await mount(<EmptyFragment />); const component = await mount(<EmptyFragment />);
expect(await page.evaluate(() => 'props' in window && window.props)).toEqual({});
expect(await component.allTextContents()).toEqual(['']); expect(await component.allTextContents()).toEqual(['']);
expect(await component.textContent()).toBe(''); expect(await component.textContent()).toBe('');
await expect(component).toHaveText(''); await expect(component).toHaveText('');

View File

@ -1,2 +1,8 @@
<script lang="ts" setup>
import { useSlots } from 'vue';
const slots = useSlots();
Object.assign(window, { slots });
</script>
<template> <template>
</template> </template>

View File

@ -7,8 +7,9 @@ test('render props', async ({ mount }) => {
await expect(component).toContainText('Submit'); await expect(component).toContainText('Submit');
}); });
test('get textContent of the empty template', async ({ mount }) => { test('render an empty component', async ({ page, mount }) => {
const component = await mount(<EmptyTemplate />); const component = await mount(<EmptyTemplate />);
expect(await page.evaluate(() => 'slots' in window && window.slots)).toEqual({});
expect(await component.allTextContents()).toEqual(['']); expect(await component.allTextContents()).toEqual(['']);
expect(await component.textContent()).toBe(''); expect(await component.textContent()).toBe('');
await expect(component).toHaveText(''); await expect(component).toHaveText('');

View File

@ -1,2 +1,7 @@
<script lang="ts" setup>
import { useSlots } from 'vue';
const slots = useSlots();
Object.assign(window, { slots });
</script>
<template> <template>
</template> </template>

View File

@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => {
await expect(component).toHaveClass('primary'); await expect(component).toHaveClass('primary');
}); });
test('get textContent of the empty template', async ({ mount }) => { test('render an empty component', async ({ page, mount }) => {
const component = await mount(<EmptyTemplate />); const component = await mount(<EmptyTemplate />);
expect(await page.evaluate(() => 'slots' in window && window.slots)).toEqual({});
expect(await component.allTextContents()).toEqual(['']); expect(await component.allTextContents()).toEqual(['']);
expect(await component.textContent()).toBe(''); expect(await component.textContent()).toBe('');
await expect(component).toHaveText(''); await expect(component).toHaveText('');

View File

@ -1,2 +1,8 @@
<script lang="ts" setup>
import { useSlots } from 'vue';
const slots = useSlots();
Object.assign(window, { slots });
</script>
<template> <template>
</template> </template>

View File

@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => {
await expect(component).toHaveClass('primary'); await expect(component).toHaveClass('primary');
}); });
test('get textContent of the empty template', async ({ mount }) => { test('render an empty component', async ({ page, mount }) => {
const component = await mount(<EmptyTemplate />); const component = await mount(<EmptyTemplate />);
expect(await page.evaluate(() => 'slots' in window && window.slots)).toEqual({});
expect(await component.allTextContents()).toEqual(['']); expect(await component.allTextContents()).toEqual(['']);
expect(await component.textContent()).toBe(''); expect(await component.textContent()).toBe('');
await expect(component).toHaveText(''); await expect(component).toHaveText('');