From 208a54529d8fe096c652eae66a51096bc0078187 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 2 Oct 2024 11:19:09 +0200 Subject: [PATCH] fix(ct-react): support shorthand fragment notation (#32900) Closes https://github.com/microsoft/playwright/issues/32853 Vite turns the shorthand fragment notation `<>` into `import { Fragment } from "react"; `. On the Node.js side of things, this `react` import resolves to our mock version of React, which currently mocks `Fragment` as `{}`. Currently, we pass that straight to `React.createElement`, which throws an error. The fix is to make our `Fragment` mock detectable with a tag, and when we render it replace it with the real `__pwReact.Fragment`. --- packages/playwright-ct-react/registerSource.mjs | 14 +++++++++++++- packages/playwright/jsx-runtime.js | 3 ++- .../components/ct-react-vite/tests/render.spec.tsx | 5 +++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/playwright-ct-react/registerSource.mjs b/packages/playwright-ct-react/registerSource.mjs index e5a14c3db6..4b233a8dec 100644 --- a/packages/playwright-ct-react/registerSource.mjs +++ b/packages/playwright-ct-react/registerSource.mjs @@ -33,12 +33,24 @@ function isJsxComponent(component) { } /** + * @param {any} type + * @returns {boolean} type is Playwright's mock JSX.Fragment + */ +function isJsxFragment(type) { + return typeof type === 'object' && type?.__pw_jsx_fragment; +} + +/** + * Turns the Playwright representation of JSX (see jsx-runtime.js) into React.createElement calls. * @param {any} value */ function __pwRender(value) { return window.__pwTransformObject(value, v => { if (isJsxComponent(v)) { const component = v; + let type = component.type; + if (isJsxFragment(type)) + type = __pwReact.Fragment; const props = component.props ? __pwRender(component.props) : {}; const key = component.key ? __pwRender(component.key) : undefined; const { children, ...propsWithoutChildren } = props; @@ -47,7 +59,7 @@ function __pwRender(value) { const createElementArguments = [propsWithoutChildren]; if (children) createElementArguments.push(children); - return { result: __pwReact.createElement(component.type, ...createElementArguments) }; + return { result: __pwReact.createElement(type, ...createElementArguments) }; } }); } diff --git a/packages/playwright/jsx-runtime.js b/packages/playwright/jsx-runtime.js index d35e5e6e8d..ff3d79e34e 100644 --- a/packages/playwright/jsx-runtime.js +++ b/packages/playwright/jsx-runtime.js @@ -32,7 +32,8 @@ function jsxs(type, props, key) { }; } -const Fragment = {}; +// this is used in <> notation +const Fragment = { __pw_jsx_fragment: true }; module.exports = { Fragment, diff --git a/tests/components/ct-react-vite/tests/render.spec.tsx b/tests/components/ct-react-vite/tests/render.spec.tsx index d00a313e9e..b7d0e61b29 100644 --- a/tests/components/ct-react-vite/tests/render.spec.tsx +++ b/tests/components/ct-react-vite/tests/render.spec.tsx @@ -46,3 +46,8 @@ test('render inline component with an error if its nested', async ({ mount }) => )).rejects.toThrow('Component "MyInlineComponent" cannot be mounted.'); }); + +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'); +});