From e5d201b459d2ac477394b58db1e1048ad5d95dd3 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 17 Jan 2024 20:43:28 -0800 Subject: [PATCH] cherry-pick(#29031): fix(ct): allow passing date, url, bigint as properties --- .../playwright-ct-core/src/injected/index.ts | 3 +- .../src/injected/serializers.ts | 72 ++++++++++++------- .../playwright-ct-core/types/component.d.ts | 1 + .../playwright-ct-react/registerSource.mjs | 24 ++----- .../playwright-ct-react17/registerSource.mjs | 24 ++----- .../playwright.ct-build.spec.ts | 62 ++++++++++++++++ utils/build/build.js | 1 + 7 files changed, 126 insertions(+), 61 deletions(-) diff --git a/packages/playwright-ct-core/src/injected/index.ts b/packages/playwright-ct-core/src/injected/index.ts index e81d4ff83c005..2238574353182 100644 --- a/packages/playwright-ct-core/src/injected/index.ts +++ b/packages/playwright-ct-core/src/injected/index.ts @@ -15,7 +15,8 @@ */ import { ImportRegistry } from './importRegistry'; -import { unwrapObject } from './serializers'; +import { transformObject, unwrapObject } from './serializers'; window.__pwRegistry = new ImportRegistry(); window.__pwUnwrapObject = unwrapObject; +window.__pwTransformObject = transformObject; diff --git a/packages/playwright-ct-core/src/injected/serializers.ts b/packages/playwright-ct-core/src/injected/serializers.ts index 41ae72c489b1c..d07be7c1f8cf7 100644 --- a/packages/playwright-ct-core/src/injected/serializers.ts +++ b/packages/playwright-ct-core/src/injected/serializers.ts @@ -26,48 +26,68 @@ function isFunctionRef(value: any): value is FunctionRef { } export function wrapObject(value: any, callbacks: Function[]): any { - if (typeof value === 'function') { - const ordinal = callbacks.length; - callbacks.push(value as Function); - const result: FunctionRef = { - __pw_type: 'function', - ordinal, - }; - return result; - } + return transformObject(value, (v: any) => { + if (typeof v === 'function') { + const ordinal = callbacks.length; + callbacks.push(v as Function); + const result: FunctionRef = { + __pw_type: 'function', + ordinal, + }; + return { result }; + } + }); +} + +export async function unwrapObject(value: any): Promise { + return transformObjectAsync(value, async (v: any) => { + if (isFunctionRef(v)) { + const result = (...args: any[]) => { + window.__ctDispatchFunction(v.ordinal, args); + }; + return { result }; + } + if (isImportRef(v)) + return { result: await window.__pwRegistry.resolveImportRef(v) }; + }); +} + +export function transformObject(value: any, mapping: (v: any) => { result: any } | undefined): any { + const result = mapping(value); + if (result) + return result.result; if (value === null || typeof value !== 'object') return value; + if (value instanceof Date || value instanceof RegExp || value instanceof URL) + return value; if (Array.isArray(value)) { const result = []; for (const item of value) - result.push(wrapObject(item, callbacks)); + result.push(transformObject(item, mapping)); return result; } - const result: any = {}; + const result2: any = {}; for (const [key, prop] of Object.entries(value)) - result[key] = wrapObject(prop, callbacks); - return result; + result2[key] = transformObject(prop, mapping); + return result2; } -export async function unwrapObject(value: any): Promise { +export async function transformObjectAsync(value: any, mapping: (v: any) => Promise<{ result: any } | undefined>): Promise { + const result = await mapping(value); + if (result) + return result.result; if (value === null || typeof value !== 'object') return value; - if (isFunctionRef(value)) { - return (...args: any[]) => { - window.__ctDispatchFunction(value.ordinal, args); - }; - } - if (isImportRef(value)) - return window.__pwRegistry.resolveImportRef(value); - + if (value instanceof Date || value instanceof RegExp || value instanceof URL) + return value; if (Array.isArray(value)) { const result = []; for (const item of value) - result.push(await unwrapObject(item)); + result.push(await transformObjectAsync(item, mapping)); return result; } - const result: any = {}; + const result2: any = {}; for (const [key, prop] of Object.entries(value)) - result[key] = await unwrapObject(prop); - return result; + result2[key] = await transformObjectAsync(prop, mapping); + return result2; } diff --git a/packages/playwright-ct-core/types/component.d.ts b/packages/playwright-ct-core/types/component.d.ts index 38ce1bfe2a136..1b43a944e44a9 100644 --- a/packages/playwright-ct-core/types/component.d.ts +++ b/packages/playwright-ct-core/types/component.d.ts @@ -59,5 +59,6 @@ declare global { // Can't start with __pw due to core reuse bindings logic for __pw*. __ctDispatchFunction: (ordinal: number, args: any[]) => void; __pwUnwrapObject: (value: any) => Promise; + __pwTransformObject: (value: any, mapping: (v: any) => { result: any } | undefined) => any; } } diff --git a/packages/playwright-ct-react/registerSource.mjs b/packages/playwright-ct-react/registerSource.mjs index efdb9d56ca513..0a30836824035 100644 --- a/packages/playwright-ct-react/registerSource.mjs +++ b/packages/playwright-ct-react/registerSource.mjs @@ -36,23 +36,13 @@ function isJsxComponent(component) { * @param {any} value */ function __pwRender(value) { - if (value === null || typeof value !== 'object') - return value; - if (isJsxComponent(value)) { - const component = value; - const props = component.props ? __pwRender(component.props) : {}; - return __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children); - } - if (Array.isArray(value)) { - const result = []; - for (const item of value) - result.push(__pwRender(item)); - return result; - } - const result = {}; - for (const [key, prop] of Object.entries(value)) - result[key] = __pwRender(prop); - return result; + return window.__pwTransformObject(value, v => { + if (isJsxComponent(v)) { + const component = v; + const props = component.props ? __pwRender(component.props) : {}; + return { result: __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children) }; + } + }); } window.playwrightMount = async (component, rootElement, hooksConfig) => { diff --git a/packages/playwright-ct-react17/registerSource.mjs b/packages/playwright-ct-react17/registerSource.mjs index f03d7b8d161c0..9363b4bb22d52 100644 --- a/packages/playwright-ct-react17/registerSource.mjs +++ b/packages/playwright-ct-react17/registerSource.mjs @@ -33,23 +33,13 @@ function isJsxComponent(component) { * @param {any} value */ function __pwRender(value) { - if (value === null || typeof value !== 'object') - return value; - if (isJsxComponent(value)) { - const component = value; - const props = component.props ? __pwRender(component.props) : {}; - return __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children); - } - if (Array.isArray(value)) { - const result = []; - for (const item of value) - result.push(__pwRender(item)); - return result; - } - const result = {}; - for (const [key, prop] of Object.entries(value)) - result[key] = __pwRender(prop); - return result; + return window.__pwTransformObject(value, v => { + if (isJsxComponent(v)) { + const component = v; + const props = component.props ? __pwRender(component.props) : {}; + return { result: __pwReact.createElement(/** @type { any } */ (component.type), { ...props, children: undefined }, props.children) }; + } + }); } window.playwrightMount = async (component, rootElement, hooksConfig) => { diff --git a/tests/playwright-test/playwright.ct-build.spec.ts b/tests/playwright-test/playwright.ct-build.spec.ts index 0ec82a51d897a..a9421c39aa56e 100644 --- a/tests/playwright-test/playwright.ct-build.spec.ts +++ b/tests/playwright-test/playwright.ct-build.spec.ts @@ -551,3 +551,65 @@ test('should pass imported images from test to component', async ({ runInlineTes expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); }); + +test('should pass dates, regex, urls and bigints', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': playwrightConfig, + 'playwright/index.html': ``, + 'playwright/index.ts': ``, + 'src/button.tsx': ` + export const Button = ({ props }: any) => { + const { date, url, bigint, regex } = props; + const types = [ + date instanceof Date, + url instanceof URL, + typeof bigint === 'bigint', + regex instanceof RegExp, + ]; + return
{types.join(' ')}
; + }; + `, + 'src/component.spec.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Button } from './button'; + + test('renders props with builtin types', async ({ mount, page }) => { + const component = await mount(