From 32669d31d1539e73d2549b1bb1d13d0e9fe7e7d7 Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Wed, 11 Jan 2023 13:49:11 +0100 Subject: [PATCH 1/4] fix --- packages/toolpad-core/src/jsRuntime.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/toolpad-core/src/jsRuntime.tsx b/packages/toolpad-core/src/jsRuntime.tsx index facee35f7d9..8d9c261a09a 100644 --- a/packages/toolpad-core/src/jsRuntime.tsx +++ b/packages/toolpad-core/src/jsRuntime.tsx @@ -52,7 +52,13 @@ function evalCode(code: string, globalScope: Record) { // eslint-disable-next-line no-underscore-dangle (iframe.contentWindow as any).__SCOPE = globalScope; - return (iframe.contentWindow as any).eval(`with (window.__SCOPE) { ${code} }`); + return (iframe.contentWindow as any).eval(` + (() => { + with (window.__SCOPE) { + return (${code}) + } + })() + `); } export function evaluateBindable( From 3a19e9e9b2d2e428e09631b7c5f2e66697bfaf98 Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Wed, 11 Jan 2023 14:45:02 +0100 Subject: [PATCH 2/4] Add test --- .../toolpad-app/src/runtime/evalJsBindings.ts | 2 +- .../src/toolpadDataSources/rest/runtime.tsx | 2 +- .../toolpad-app/src/utils/evalExpression.ts | 19 --- packages/toolpad-core/src/jsRuntime.tsx | 7 +- test/integration/bindings/dom.json | 111 ++++++++++++++++++ test/integration/bindings/index.spec.ts | 28 +++++ 6 files changed, 146 insertions(+), 23 deletions(-) delete mode 100644 packages/toolpad-app/src/utils/evalExpression.ts create mode 100644 test/integration/bindings/dom.json create mode 100644 test/integration/bindings/index.spec.ts diff --git a/packages/toolpad-app/src/runtime/evalJsBindings.ts b/packages/toolpad-app/src/runtime/evalJsBindings.ts index bcb5c5508b4..7b4d3686fe5 100644 --- a/packages/toolpad-app/src/runtime/evalJsBindings.ts +++ b/packages/toolpad-app/src/runtime/evalJsBindings.ts @@ -1,5 +1,5 @@ +import { evalExpression } from '@mui/toolpad-core/jsRuntime'; import { set } from 'lodash-es'; -import evalExpression from '../utils/evalExpression'; import { mapValues } from '../utils/collections'; import { errorFrom } from '../utils/errors'; diff --git a/packages/toolpad-app/src/toolpadDataSources/rest/runtime.tsx b/packages/toolpad-app/src/toolpadDataSources/rest/runtime.tsx index 880d1d5b456..c0690e93fd1 100644 --- a/packages/toolpad-app/src/toolpadDataSources/rest/runtime.tsx +++ b/packages/toolpad-app/src/toolpadDataSources/rest/runtime.tsx @@ -1,8 +1,8 @@ import type { Entry } from 'har-format'; +import { evalExpression } from '@mui/toolpad-core/jsRuntime'; import { ExecFetchFn, RuntimeDataSource } from '../../types'; import { FetchQuery, FetchResult } from './types'; import { execfetch } from './shared'; -import evalExpression from '../../utils/evalExpression'; import { createHarLog } from '../../utils/har'; export async function clientExec( diff --git a/packages/toolpad-app/src/utils/evalExpression.ts b/packages/toolpad-app/src/utils/evalExpression.ts deleted file mode 100644 index c34102a86c2..00000000000 --- a/packages/toolpad-app/src/utils/evalExpression.ts +++ /dev/null @@ -1,19 +0,0 @@ -let iframe: HTMLIFrameElement; - -/** - * Evaluates a javascript expression with global scope in an iframe. - */ -export default function evalExpression(code: string, globalScope: Record) { - // TODO: investigate https://www.npmjs.com/package/ses - if (!iframe) { - iframe = document.createElement('iframe'); - iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts'); - iframe.style.display = 'none'; - document.documentElement.appendChild(iframe); - } - - // eslint-disable-next-line no-underscore-dangle - (iframe.contentWindow as any).__SCOPE = globalScope; - (iframe.contentWindow as any).console = window.console; - return (iframe.contentWindow as any).eval(`with (window.__SCOPE) { ${code} }`); -} diff --git a/packages/toolpad-core/src/jsRuntime.tsx b/packages/toolpad-core/src/jsRuntime.tsx index 8d9c261a09a..14b236cb504 100644 --- a/packages/toolpad-core/src/jsRuntime.tsx +++ b/packages/toolpad-core/src/jsRuntime.tsx @@ -42,7 +42,7 @@ export function useJsRuntime(): QuickJSRuntime { } let iframe: HTMLIFrameElement; -function evalCode(code: string, globalScope: Record) { +export function evalExpression(code: string, globalScope: Record) { if (!iframe) { iframe = document.createElement('iframe'); iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts'); @@ -52,6 +52,7 @@ function evalCode(code: string, globalScope: Record) { // eslint-disable-next-line no-underscore-dangle (iframe.contentWindow as any).__SCOPE = globalScope; + (iframe.contentWindow as any).console = window.console; return (iframe.contentWindow as any).eval(` (() => { with (window.__SCOPE) { @@ -59,6 +60,8 @@ function evalCode(code: string, globalScope: Record) { } })() `); + + return (iframe.contentWindow as any).eval(`with (window.__SCOPE) { ${code} }`); } export function evaluateBindable( @@ -69,7 +72,7 @@ export function evaluateBindable( ): LiveBinding { const execExpression = () => { if (bindable?.type === 'jsExpression') { - return evalCode(bindable?.value, globalScope); + return evalExpression(bindable?.value, globalScope); } if (bindable?.type === 'const') { diff --git a/test/integration/bindings/dom.json b/test/integration/bindings/dom.json new file mode 100644 index 00000000000..92cc9901d96 --- /dev/null +++ b/test/integration/bindings/dom.json @@ -0,0 +1,111 @@ +{ + "root": "740rd3w", + "nodes": { + "740rd3w": { + "id": "740rd3w", + "name": "Application", + "type": "app", + "parentId": null, + "attributes": {}, + "parentProp": null, + "parentIndex": null + }, + "741rda6": { + "id": "741rda6", + "name": "bindings", + "type": "page", + "parentId": "740rd3w", + "attributes": { + "title": { + "type": "const", + "value": "Page 1" + } + }, + "parentProp": "pages", + "parentIndex": "a0" + }, + "7t730ct": { + "id": "7t730ct", + "name": "pageRow1", + "type": "element", + "props": {}, + "layout": {}, + "parentId": "741rda6", + "attributes": { + "component": { + "type": "const", + "value": "PageRow" + } + }, + "parentProp": "children", + "parentIndex": "a1" + }, + "vx530kf": { + "id": "vx530kf", + "name": "text1", + "type": "element", + "props": { + "sx": { + "type": "jsExpression", + "value": "{" + }, + "value": { + "type": "const", + "value": "-test2-" + } + }, + "layout": {}, + "parentId": "7t730ct", + "attributes": { + "component": { + "type": "const", + "value": "Text" + } + }, + "parentProp": "children", + "parentIndex": "a0" + }, + "b7a3074": { + "name": "pageRow", + "props": {}, + "attributes": { + "component": { + "type": "const", + "value": "PageRow" + } + }, + "layout": {}, + "id": "b7a3074", + "type": "element", + "parentId": "741rda6", + "parentProp": "children", + "parentIndex": "a0" + }, + "n9830o2": { + "name": "text", + "props": { + "value": { + "type": "const", + "value": "-test1-" + }, + "sx": { + "type": "jsExpression", + "value": "{ color: \"primary.main\", p: 2 }\n" + } + }, + "attributes": { + "component": { + "type": "const", + "value": "Text" + } + }, + "layout": {}, + "id": "n9830o2", + "type": "element", + "parentId": "b7a3074", + "parentProp": "children", + "parentIndex": "a0" + } + }, + "version": 5 +} diff --git a/test/integration/bindings/index.spec.ts b/test/integration/bindings/index.spec.ts new file mode 100644 index 00000000000..180268527be --- /dev/null +++ b/test/integration/bindings/index.spec.ts @@ -0,0 +1,28 @@ +import * as path from 'path'; +import { ToolpadRuntime } from '../../models/ToolpadRuntime'; +import { expect, test } from '../../playwright/test'; +import { readJsonFile } from '../../utils/fs'; +import generateId from '../../utils/generateId'; + +test.use({ + ignoreConsoleErrors: [/Unexpected token '\)'/], +}); + +test('bindings', async ({ page, api }) => { + const dom = await readJsonFile(path.resolve(__dirname, './dom.json')); + + const app = await api.mutation.createApp(`App ${generateId()}`, { + from: { kind: 'dom', dom }, + }); + + const runtimeModel = new ToolpadRuntime(page); + await runtimeModel.gotoPage(app.id, 'bindings'); + + const test1 = page.getByText('-test1-'); + await expect(test1).toBeVisible(); + const color = await test1.evaluate((elm) => + window.getComputedStyle(elm).getPropertyValue('color'), + ); + expect(color).toBe('rgb(25, 118, 210)'); + await expect(page.getByText('-test2-')).toBeVisible(); +}); From 24e016cb49f65ebf28c0b56bd1a7c50e769d27e8 Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Wed, 11 Jan 2023 14:46:59 +0100 Subject: [PATCH 3/4] Update jsRuntime.tsx address unreachable code --- packages/toolpad-core/src/jsRuntime.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/toolpad-core/src/jsRuntime.tsx b/packages/toolpad-core/src/jsRuntime.tsx index 14b236cb504..6f47def7cb9 100644 --- a/packages/toolpad-core/src/jsRuntime.tsx +++ b/packages/toolpad-core/src/jsRuntime.tsx @@ -53,6 +53,7 @@ export function evalExpression(code: string, globalScope: Record { with (window.__SCOPE) { @@ -60,8 +61,6 @@ export function evalExpression(code: string, globalScope: Record( From 80220f6ddc4a9cd0a349885f59fe186b2733c7b1 Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Wed, 11 Jan 2023 16:18:55 +0100 Subject: [PATCH 4/4] test ff --- test/integration/bindings/index.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/integration/bindings/index.spec.ts b/test/integration/bindings/index.spec.ts index 180268527be..134a7f2bc97 100644 --- a/test/integration/bindings/index.spec.ts +++ b/test/integration/bindings/index.spec.ts @@ -5,7 +5,12 @@ import { readJsonFile } from '../../utils/fs'; import generateId from '../../utils/generateId'; test.use({ - ignoreConsoleErrors: [/Unexpected token '\)'/], + ignoreConsoleErrors: [ + // Chrome + /Unexpected token '\)'/, + // Firefox + /expected property name, got '\)'/, + ], }); test('bindings', async ({ page, api }) => {