2023-01-14 03:15:43 +01:00
import { test , expect } from '@playwright/experimental-ct-react' ;
import Button from '@/components/Button' ;
import EmptyFragment from '@/components/EmptyFragment' ;
2023-12-23 05:51:59 +01:00
import { ComponentAsProp } from '@/components/ComponentAsProp' ;
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
2024-09-10 11:15:20 +02:00
import DefaultChildren from '@/components/DefaultChildren' ;
2023-01-14 03:15:43 +01:00
test ( 'render props' , async ( { mount } ) = > {
const component = await mount ( < Button title = "Submit" / > ) ;
await expect ( component ) . toContainText ( 'Submit' ) ;
} ) ;
2023-12-23 05:51:59 +01:00
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]' ) ;
} ) ;
2023-01-14 03:15:43 +01:00
test ( 'render attributes' , async ( { mount } ) = > {
const component = await mount ( < Button className = "primary" title = "Submit" / > ) ;
await expect ( component ) . toHaveClass ( 'primary' ) ;
} ) ;
2023-11-28 00:53:50 +01:00
test ( 'render an empty component' , async ( { mount , page } ) = > {
2023-01-14 03:15:43 +01:00
const component = await mount ( < EmptyFragment / > ) ;
2023-11-28 00:53:50 +01:00
expect ( await page . evaluate ( ( ) = > 'props' in window && window . props ) ) . toEqual ( { } ) ;
2023-01-14 03:15:43 +01:00
expect ( await component . allTextContents ( ) ) . toEqual ( [ '' ] ) ;
expect ( await component . textContent ( ) ) . toBe ( '' ) ;
await expect ( component ) . toHaveText ( '' ) ;
} ) ;
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
2024-09-10 11:15:20 +02:00
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.' ) ;
} ) ;
2024-10-02 11:19:09 +02:00
test ( 'render Fragment shorthand notation' , { annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/32853' } } , async ( { mount } ) = > {
const component = await mount ( < > Learn React < / > ) ;
await expect ( component ) . toContainText ( 'Learn React' ) ;
} ) ;