Skip to content

Commit 1eca9a2

Browse files
authored
[playground] Add compiler playground tests (#34528)
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn test --debug --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> Added more tests for the compiler playground with the addition of the new config editor and "Show Internals" button. Added testing to check for incomplete store params in the URL, toggle functionality, and correct errors showing for syntax/validation errors in the config overrides.
1 parent cd85bb5 commit 1eca9a2

File tree

7 files changed

+184
-15
lines changed

7 files changed

+184
-15
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { PluginOptions } from 
2+
'babel-plugin-react-compiler/dist';
3+
({
4+
  //compilationMode: "all"
5+
} satisfies Partial<PluginOptions>);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { c as _c } from "react/compiler-runtime";
2+
export default function TestComponent(t0) {
3+
const $ = _c(2);
4+
const { x } = t0;
5+
let t1;
6+
if ($[0] !== x || true) {
7+
t1 = <Button>{x}</Button>;
8+
$[0] = x;
9+
$[1] = t1;
10+
} else {
11+
t1 = $[1];
12+
}
13+
return t1;
14+
}

compiler/apps/playground/__tests__/e2e/page.spec.ts

Lines changed: 159 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {expect, test} from '@playwright/test';
8+
import {expect, test, type Page} from '@playwright/test';
99
import {encodeStore, type Store} from '../../lib/stores';
10+
import {defaultConfig} from '../../lib/defaultStore';
1011
import {format} from 'prettier';
1112

1213
function isMonacoLoaded(): boolean {
@@ -20,6 +21,15 @@ function formatPrint(data: Array<string>): Promise<string> {
2021
return format(data.join(''), {parser: 'babel'});
2122
}
2223

24+
async function expandConfigs(page: Page): Promise<void> {
25+
const expandButton = page.locator('[title="Expand config editor"]');
26+
expandButton.click();
27+
}
28+
29+
const TEST_SOURCE = `export default function TestComponent({ x }) {
30+
return <Button>{x}</Button>;
31+
}`;
32+
2333
const TEST_CASE_INPUTS = [
2434
{
2535
name: 'module-scope-use-memo',
@@ -121,10 +131,9 @@ test('editor should open successfully', async ({page}) => {
121131

122132
test('editor should compile from hash successfully', async ({page}) => {
123133
const store: Store = {
124-
source: `export default function TestComponent({ x }) {
125-
return <Button>{x}</Button>;
126-
}
127-
`,
134+
source: TEST_SOURCE,
135+
config: defaultConfig,
136+
showInternals: false,
128137
};
129138
const hash = encodeStore(store);
130139
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
@@ -136,7 +145,7 @@ test('editor should compile from hash successfully', async ({page}) => {
136145
path: 'test-results/01-compiles-from-hash.png',
137146
});
138147
const text =
139-
(await page.locator('.monaco-editor').nth(3).allInnerTexts()) ?? [];
148+
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
140149
const output = await formatPrint(text);
141150

142151
expect(output).not.toEqual('');
@@ -145,10 +154,9 @@ test('editor should compile from hash successfully', async ({page}) => {
145154

146155
test('reset button works', async ({page}) => {
147156
const store: Store = {
148-
source: `export default function TestComponent({ x }) {
149-
return <Button>{x}</Button>;
150-
}
151-
`,
157+
source: TEST_SOURCE,
158+
config: defaultConfig,
159+
showInternals: false,
152160
};
153161
const hash = encodeStore(store);
154162
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
@@ -157,33 +165,171 @@ test('reset button works', async ({page}) => {
157165
// Reset button works
158166
page.on('dialog', dialog => dialog.accept());
159167
await page.getByRole('button', {name: 'Reset'}).click();
168+
await expandConfigs(page);
169+
160170
await page.screenshot({
161171
fullPage: true,
162172
path: 'test-results/02-reset-button-works.png',
163173
});
164174
const text =
165-
(await page.locator('.monaco-editor').nth(3).allInnerTexts()) ?? [];
175+
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
166176
const output = await formatPrint(text);
167177

178+
const configText =
179+
(await page.locator('.monaco-editor-config').allInnerTexts()) ?? [];
180+
const configOutput = configText.join('');
181+
168182
expect(output).not.toEqual('');
169183
expect(output).toMatchSnapshot('02-default-output.txt');
184+
expect(configOutput).not.toEqual('');
185+
expect(configOutput).toMatchSnapshot('default-config.txt');
186+
});
187+
188+
test('defaults load when only source is in Store', async ({page}) => {
189+
// Test for backwards compatibility
190+
const partial = {
191+
source: TEST_SOURCE,
192+
};
193+
const hash = encodeStore(partial as Store);
194+
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
195+
await page.waitForFunction(isMonacoLoaded);
196+
await expandConfigs(page);
197+
198+
await page.screenshot({
199+
fullPage: true,
200+
path: 'test-results/03-missing-defaults.png',
201+
});
202+
203+
// Config editor has default config
204+
const configText =
205+
(await page.locator('.monaco-editor-config').allInnerTexts()) ?? [];
206+
const configOutput = configText.join('');
207+
208+
expect(configOutput).not.toEqual('');
209+
expect(configOutput).toMatchSnapshot('default-config.txt');
210+
211+
const checkbox = page.locator('label.show-internals');
212+
await expect(checkbox).not.toBeChecked();
213+
const ssaTab = page.locator('text=SSA');
214+
await expect(ssaTab).not.toBeVisible();
215+
});
216+
217+
test('show internals button toggles correctly', async ({page}) => {
218+
await page.goto(`/`, {waitUntil: 'networkidle'});
219+
await page.waitForFunction(isMonacoLoaded);
220+
221+
// show internals should be off
222+
const checkbox = page.locator('label.show-internals');
223+
await checkbox.click();
224+
225+
await page.screenshot({
226+
fullPage: true,
227+
path: 'test-results/04-show-internals-on.png',
228+
});
229+
230+
await expect(checkbox).toBeChecked();
231+
232+
const ssaTab = page.locator('text=SSA');
233+
await expect(ssaTab).toBeVisible();
234+
});
235+
236+
test('error is displayed when config has syntax error', async ({page}) => {
237+
const store: Store = {
238+
source: TEST_SOURCE,
239+
config: `compilationMode: `,
240+
showInternals: false,
241+
};
242+
const hash = encodeStore(store);
243+
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
244+
await page.waitForFunction(isMonacoLoaded);
245+
await expandConfigs(page);
246+
await page.screenshot({
247+
fullPage: true,
248+
path: 'test-results/05-config-syntax-error.png',
249+
});
250+
251+
const text =
252+
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
253+
const output = text.join('');
254+
255+
// Remove hidden chars
256+
expect(output.replace(/\s+/g, ' ')).toContain('Invalid override format');
257+
});
258+
259+
test('error is displayed when config has validation error', async ({page}) => {
260+
const store: Store = {
261+
source: TEST_SOURCE,
262+
config: `import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
263+
264+
({
265+
compilationMode: "123"
266+
} satisfies Partial<PluginOptions>);`,
267+
showInternals: false,
268+
};
269+
const hash = encodeStore(store);
270+
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
271+
await page.waitForFunction(isMonacoLoaded);
272+
await expandConfigs(page);
273+
await page.screenshot({
274+
fullPage: true,
275+
path: 'test-results/06-config-validation-error.png',
276+
});
277+
278+
const text =
279+
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
280+
const output = text.join('');
281+
282+
expect(output.replace(/\s+/g, ' ')).toContain('Unexpected compilationMode');
283+
});
284+
285+
test('disableMemoizationForDebugging flag works as expected', async ({
286+
page,
287+
}) => {
288+
const store: Store = {
289+
source: TEST_SOURCE,
290+
config: `import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
291+
292+
({
293+
environment: {
294+
disableMemoizationForDebugging: true
295+
}
296+
} satisfies Partial<PluginOptions>);`,
297+
showInternals: false,
298+
};
299+
const hash = encodeStore(store);
300+
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
301+
await page.waitForFunction(isMonacoLoaded);
302+
await expandConfigs(page);
303+
await page.screenshot({
304+
fullPage: true,
305+
path: 'test-results/07-config-disableMemoizationForDebugging-flag.png',
306+
});
307+
308+
const text =
309+
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
310+
const output = await formatPrint(text);
311+
312+
expect(output).not.toEqual('');
313+
expect(output).toMatchSnapshot('disableMemoizationForDebugging-output.txt');
170314
});
171315

172316
TEST_CASE_INPUTS.forEach((t, idx) =>
173317
test(`playground compiles: ${t.name}`, async ({page}) => {
174318
const store: Store = {
175319
source: t.input,
320+
config: defaultConfig,
321+
showInternals: false,
176322
};
177323
const hash = encodeStore(store);
178324
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
179325
await page.waitForFunction(isMonacoLoaded);
180326
await page.screenshot({
181327
fullPage: true,
182-
path: `test-results/03-0${idx}-${t.name}.png`,
328+
path: `test-results/08-0${idx}-${t.name}.png`,
183329
});
184330

185331
const text =
186-
(await page.locator('.monaco-editor').nth(3).allInnerTexts()) ?? [];
332+
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
187333
let output: string;
188334
if (t.noFormat) {
189335
output = text.join('');

compiler/apps/playground/components/Editor/ConfigEditor.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
99
import {PluginOptions} from 'babel-plugin-react-compiler';
1010
import type {editor} from 'monaco-editor';
1111
import * as monaco from 'monaco-editor';
12-
import React, {useState, useRef, useEffect} from 'react';
12+
import React, {useState, useRef} from 'react';
1313
import {Resizable} from 're-resizable';
1414
import {useStore, useStoreDispatch} from '../StoreContext';
1515
import {monacoOptions} from './monacoOptions';
@@ -145,6 +145,7 @@ function ExpandedEditor({
145145
onMount={handleMount}
146146
onChange={handleChange}
147147
loading={''}
148+
className="monaco-editor-config"
148149
options={{
149150
...monacoOptions,
150151
lineNumbers: 'off',
@@ -170,6 +171,7 @@ function ExpandedEditor({
170171
language={'javascript'}
171172
value={formattedAppliedOptions}
172173
loading={''}
174+
className="monaco-editor-applied-config"
173175
options={{
174176
...monacoOptions,
175177
lineNumbers: 'off',

compiler/apps/playground/components/Editor/Input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export default function Input({errors, language}: Props): JSX.Element {
145145
value={store.source}
146146
onMount={handleMount}
147147
onChange={handleChange}
148+
className="monaco-editor-input"
148149
options={monacoOptions}
149150
loading={''}
150151
/>

compiler/apps/playground/components/Editor/Output.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ function TextTabContent({
340340
language={language ?? 'javascript'}
341341
value={output}
342342
loading={''}
343+
className="monaco-editor-output"
343344
options={{
344345
...monacoOptions,
345346
readOnly: true,

compiler/apps/playground/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export default function Header(): JSX.Element {
5858
</div>
5959
<div className="flex items-center text-[15px] gap-4">
6060
<div className="flex items-center gap-2">
61-
<label className="relative inline-block w-[34px] h-5">
61+
<label className="show-internals relative inline-block w-[34px] h-5">
6262
<input
6363
type="checkbox"
6464
checked={store.showInternals}

0 commit comments

Comments
 (0)