Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ct): allow passing date, url, bigint as properties #29031

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/playwright-ct-core/src/injected/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
72 changes: 46 additions & 26 deletions packages/playwright-ct-core/src/injected/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> {
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<any> {
export async function transformObjectAsync(value: any, mapping: (v: any) => Promise<{ result: any } | undefined>): Promise<any> {
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;
}
1 change: 1 addition & 0 deletions packages/playwright-ct-core/types/component.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>;
__pwTransformObject: (value: any, mapping: (v: any) => { result: any } | undefined) => any;
}
}
24 changes: 7 additions & 17 deletions packages/playwright-ct-react/registerSource.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
24 changes: 7 additions & 17 deletions packages/playwright-ct-react17/registerSource.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
62 changes: 62 additions & 0 deletions tests/playwright-test/playwright.ct-build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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': `<script type="module" src="./index.ts"></script>`,
'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 <div>{types.join(' ')}</div>;
};
`,
'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(<Button props={{
date: new Date(),
url: new URL('https://example.com'),
bigint: BigInt(42),
regex: /foo/,
}} />);
await expect(component).toHaveText('true true true true');
});
`,
}, { workers: 1 });

expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});

test('should pass undefined value as param', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': playwrightConfig,
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
'playwright/index.ts': ``,
'src/component.tsx': `
export const Component = ({ value }: { value?: number }) => {
return <div>{typeof value}</div>;
};
`,
'src/component.spec.tsx': `
import { test, expect } from '@playwright/experimental-ct-react';
import { Component } from './component';
test('renders props with undefined type', async ({ mount, page }) => {
const component = await mount(<Component value={undefined} />);
await expect(component).toHaveText('undefined');
});
`,
}, { workers: 1 });

expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
1 change: 1 addition & 0 deletions utils/build/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ steps.push({
onChanges.push({
inputs: [
'packages/playwright-core/src/server/injected/**',
'packages/playwright-ct-core/src/injected/**',
'packages/playwright-core/src/utils/isomorphic/**',
'utils/generate_injected.js',
],
Expand Down
Loading