fix(ct): throw error if inline component is getting mounted (#32531)
What was happening? - When we use CT, we go over the test files, look at the imports using `tsxTransform.ts` and store them inside a map, these we feed into the import registry which we build using Vite and have access inside the browser - In case of an inline component in the same file as where the test file is, this is not happening. - jsx-runtime via babel kicks in, transforms every JSX component in something like that: ``` { __pw_type: 'jsx', type: [Function: MyInlineComponent], props: { value: 'Max' }, key: undefined } ``` this then gets passed into `wrapObject` which maps any function from the Node.js side into expose function calls so they work inside the browser. The assumption for `wrapObject` was to do it mostly for callbacks. So it does for `type` - which is actually our component. We then pass this to the React render function, which calls back the exposed function but we never return anything, so it mounts `undefined`. --- While there have been experiments from certain vendors to get the 'client only' code inside a server side file, we should throw for now to not confuse users. We might revisit this in the future since Babel / TSX doesn't support it outside of the box. Fixes https://github.com/microsoft/playwright/issues/32167
This commit is contained in:
Родитель
8995ace825
Коммит
9fa06be49e
|
@ -66,6 +66,13 @@ export function transformObject(value: any, mapping: (v: any) => { result: any }
|
|||
result.push(transformObject(item, mapping));
|
||||
return result;
|
||||
}
|
||||
if (value?.__pw_type === 'jsx' && typeof value.type === 'function') {
|
||||
throw new Error([
|
||||
`Component "${value.type.name}" cannot be mounted.`,
|
||||
`Most likely, this component is defined in the test file. Create a test story instead.`,
|
||||
`For more information, see https://playwright.dev/docs/test-components#test-stories.`,
|
||||
].join('\n'));
|
||||
}
|
||||
const result2: any = {};
|
||||
for (const [key, prop] of Object.entries(value))
|
||||
result2[key] = transformObject(prop, mapping);
|
||||
|
|
|
@ -2,6 +2,7 @@ import { test, expect } from '@playwright/experimental-ct-react';
|
|||
import Button from '@/components/Button';
|
||||
import EmptyFragment from '@/components/EmptyFragment';
|
||||
import { ComponentAsProp } from '@/components/ComponentAsProp';
|
||||
import DefaultChildren from '@/components/DefaultChildren';
|
||||
|
||||
test('render props', async ({ mount }) => {
|
||||
const component = await mount(<Button title="Submit" />);
|
||||
|
@ -31,3 +32,17 @@ test('render an empty component', async ({ mount, page }) => {
|
|||
expect(await component.textContent()).toBe('');
|
||||
await expect(component).toHaveText('');
|
||||
});
|
||||
|
||||
function MyInlineComponent({ value }: { value: string }) {
|
||||
return <>Hello {value}</>;
|
||||
}
|
||||
|
||||
test('render inline component with an error', async ({ mount }) => {
|
||||
await expect(mount(<MyInlineComponent value="Max" />)).rejects.toThrow('Component "MyInlineComponent" cannot be mounted.');
|
||||
});
|
||||
|
||||
test('render inline component with an error if its nested', async ({ mount }) => {
|
||||
await expect(mount(<DefaultChildren>
|
||||
<MyInlineComponent value="Max" />
|
||||
</DefaultChildren>)).rejects.toThrow('Component "MyInlineComponent" cannot be mounted.');
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче