diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index 47293204ac390..ce11f76530243 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -194,7 +194,7 @@ jobs: if: steps.node_modules.outputs.cache-hit != 'true' - run: | yarn generate-inline-fizz-runtime - git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false) + git diff --exit-code || (echo "There was a change to the Fizz runtime. Run \`yarn generate-inline-fizz-runtime\` and check in the result." && false) # ----- FEATURE FLAGS ----- flags: @@ -567,7 +567,7 @@ jobs: - name: Search build artifacts for unminified errors run: | yarn extract-errors - git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false) + git diff --exit-code || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false) check_release_dependencies: name: Check release dependencies diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-config.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-config.txt new file mode 100644 index 0000000000000..383eb7e71e67f --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-config.txt @@ -0,0 +1,5 @@ +import type { PluginOptions } from  +'babel-plugin-react-compiler/dist'; +({ +  //compilationMode: "all" +} satisfies Partial); \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/disableMemoizationForDebugging-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/disableMemoizationForDebugging-output.txt new file mode 100644 index 0000000000000..c0a2769bb3fad --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/disableMemoizationForDebugging-output.txt @@ -0,0 +1,14 @@ +import { c as _c } from "react/compiler-runtime"; +export default function TestComponent(t0) { + const $ = _c(2); + const { x } = t0; + let t1; + if ($[0] !== x || true) { + t1 = ; + $[0] = x; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 17505024ffeac..a904923b4228b 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {expect, test} from '@playwright/test'; +import {expect, test, type Page} from '@playwright/test'; import {encodeStore, type Store} from '../../lib/stores'; +import {defaultConfig} from '../../lib/defaultStore'; import {format} from 'prettier'; function isMonacoLoaded(): boolean { @@ -20,6 +21,15 @@ function formatPrint(data: Array): Promise { return format(data.join(''), {parser: 'babel'}); } +async function expandConfigs(page: Page): Promise { + const expandButton = page.locator('[title="Expand config editor"]'); + expandButton.click(); +} + +const TEST_SOURCE = `export default function TestComponent({ x }) { + return ; +}`; + const TEST_CASE_INPUTS = [ { name: 'module-scope-use-memo', @@ -121,10 +131,9 @@ test('editor should open successfully', async ({page}) => { test('editor should compile from hash successfully', async ({page}) => { const store: Store = { - source: `export default function TestComponent({ x }) { - return ; - } - `, + source: TEST_SOURCE, + config: defaultConfig, + showInternals: false, }; const hash = encodeStore(store); await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); @@ -136,7 +145,7 @@ test('editor should compile from hash successfully', async ({page}) => { path: 'test-results/01-compiles-from-hash.png', }); const text = - (await page.locator('.monaco-editor').nth(3).allInnerTexts()) ?? []; + (await page.locator('.monaco-editor-output').allInnerTexts()) ?? []; const output = await formatPrint(text); expect(output).not.toEqual(''); @@ -145,10 +154,9 @@ test('editor should compile from hash successfully', async ({page}) => { test('reset button works', async ({page}) => { const store: Store = { - source: `export default function TestComponent({ x }) { - return ; - } - `, + source: TEST_SOURCE, + config: defaultConfig, + showInternals: false, }; const hash = encodeStore(store); await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); @@ -157,33 +165,171 @@ test('reset button works', async ({page}) => { // Reset button works page.on('dialog', dialog => dialog.accept()); await page.getByRole('button', {name: 'Reset'}).click(); + await expandConfigs(page); + await page.screenshot({ fullPage: true, path: 'test-results/02-reset-button-works.png', }); const text = - (await page.locator('.monaco-editor').nth(3).allInnerTexts()) ?? []; + (await page.locator('.monaco-editor-output').allInnerTexts()) ?? []; const output = await formatPrint(text); + const configText = + (await page.locator('.monaco-editor-config').allInnerTexts()) ?? []; + const configOutput = configText.join(''); + expect(output).not.toEqual(''); expect(output).toMatchSnapshot('02-default-output.txt'); + expect(configOutput).not.toEqual(''); + expect(configOutput).toMatchSnapshot('default-config.txt'); +}); + +test('defaults load when only source is in Store', async ({page}) => { + // Test for backwards compatibility + const partial = { + source: TEST_SOURCE, + }; + const hash = encodeStore(partial as Store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); + await page.waitForFunction(isMonacoLoaded); + await expandConfigs(page); + + await page.screenshot({ + fullPage: true, + path: 'test-results/03-missing-defaults.png', + }); + + // Config editor has default config + const configText = + (await page.locator('.monaco-editor-config').allInnerTexts()) ?? []; + const configOutput = configText.join(''); + + expect(configOutput).not.toEqual(''); + expect(configOutput).toMatchSnapshot('default-config.txt'); + + const checkbox = page.locator('label.show-internals'); + await expect(checkbox).not.toBeChecked(); + const ssaTab = page.locator('text=SSA'); + await expect(ssaTab).not.toBeVisible(); +}); + +test('show internals button toggles correctly', async ({page}) => { + await page.goto(`/`, {waitUntil: 'networkidle'}); + await page.waitForFunction(isMonacoLoaded); + + // show internals should be off + const checkbox = page.locator('label.show-internals'); + await checkbox.click(); + + await page.screenshot({ + fullPage: true, + path: 'test-results/04-show-internals-on.png', + }); + + await expect(checkbox).toBeChecked(); + + const ssaTab = page.locator('text=SSA'); + await expect(ssaTab).toBeVisible(); +}); + +test('error is displayed when config has syntax error', async ({page}) => { + const store: Store = { + source: TEST_SOURCE, + config: `compilationMode: `, + showInternals: false, + }; + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); + await page.waitForFunction(isMonacoLoaded); + await expandConfigs(page); + await page.screenshot({ + fullPage: true, + path: 'test-results/05-config-syntax-error.png', + }); + + const text = + (await page.locator('.monaco-editor-output').allInnerTexts()) ?? []; + const output = text.join(''); + + // Remove hidden chars + expect(output.replace(/\s+/g, ' ')).toContain('Invalid override format'); +}); + +test('error is displayed when config has validation error', async ({page}) => { + const store: Store = { + source: TEST_SOURCE, + config: `import type { PluginOptions } from 'babel-plugin-react-compiler/dist'; + +({ + compilationMode: "123" +} satisfies Partial);`, + showInternals: false, + }; + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); + await page.waitForFunction(isMonacoLoaded); + await expandConfigs(page); + await page.screenshot({ + fullPage: true, + path: 'test-results/06-config-validation-error.png', + }); + + const text = + (await page.locator('.monaco-editor-output').allInnerTexts()) ?? []; + const output = text.join(''); + + expect(output.replace(/\s+/g, ' ')).toContain('Unexpected compilationMode'); +}); + +test('disableMemoizationForDebugging flag works as expected', async ({ + page, +}) => { + const store: Store = { + source: TEST_SOURCE, + config: `import type { PluginOptions } from 'babel-plugin-react-compiler/dist'; + +({ + environment: { + disableMemoizationForDebugging: true + } +} satisfies Partial);`, + showInternals: false, + }; + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); + await page.waitForFunction(isMonacoLoaded); + await expandConfigs(page); + await page.screenshot({ + fullPage: true, + path: 'test-results/07-config-disableMemoizationForDebugging-flag.png', + }); + + const text = + (await page.locator('.monaco-editor-output').allInnerTexts()) ?? []; + const output = await formatPrint(text); + + expect(output).not.toEqual(''); + expect(output).toMatchSnapshot('disableMemoizationForDebugging-output.txt'); }); TEST_CASE_INPUTS.forEach((t, idx) => test(`playground compiles: ${t.name}`, async ({page}) => { const store: Store = { source: t.input, + config: defaultConfig, + showInternals: false, }; const hash = encodeStore(store); await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); await page.waitForFunction(isMonacoLoaded); await page.screenshot({ fullPage: true, - path: `test-results/03-0${idx}-${t.name}.png`, + path: `test-results/08-0${idx}-${t.name}.png`, }); const text = - (await page.locator('.monaco-editor').nth(3).allInnerTexts()) ?? []; + (await page.locator('.monaco-editor-output').allInnerTexts()) ?? []; let output: string; if (t.noFormat) { output = text.join(''); diff --git a/compiler/apps/playground/components/Editor/ConfigEditor.tsx b/compiler/apps/playground/components/Editor/ConfigEditor.tsx index add42018a3879..c70cd10ba53e8 100644 --- a/compiler/apps/playground/components/Editor/ConfigEditor.tsx +++ b/compiler/apps/playground/components/Editor/ConfigEditor.tsx @@ -9,7 +9,7 @@ import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react'; import {PluginOptions} from 'babel-plugin-react-compiler'; import type {editor} from 'monaco-editor'; import * as monaco from 'monaco-editor'; -import React, {useState, useRef, useEffect} from 'react'; +import React, {useState, useRef} from 'react'; import {Resizable} from 're-resizable'; import {useStore, useStoreDispatch} from '../StoreContext'; import {monacoOptions} from './monacoOptions'; @@ -145,6 +145,7 @@ function ExpandedEditor({ onMount={handleMount} onChange={handleChange} loading={''} + className="monaco-editor-config" options={{ ...monacoOptions, lineNumbers: 'off', @@ -170,6 +171,7 @@ function ExpandedEditor({ language={'javascript'} value={formattedAppliedOptions} loading={''} + className="monaco-editor-applied-config" options={{ ...monacoOptions, lineNumbers: 'off', diff --git a/compiler/apps/playground/components/Editor/Input.tsx b/compiler/apps/playground/components/Editor/Input.tsx index d8744c3ca9770..6cded7656b06f 100644 --- a/compiler/apps/playground/components/Editor/Input.tsx +++ b/compiler/apps/playground/components/Editor/Input.tsx @@ -145,6 +145,7 @@ export default function Input({errors, language}: Props): JSX.Element { value={store.source} onMount={handleMount} onChange={handleChange} + className="monaco-editor-input" options={monacoOptions} loading={''} /> diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index bf73c192c1152..331aa66bcbeb4 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -340,6 +340,7 @@ function TextTabContent({ language={language ?? 'javascript'} value={output} loading={''} + className="monaco-editor-output" options={{ ...monacoOptions, readOnly: true, diff --git a/compiler/apps/playground/components/Header.tsx b/compiler/apps/playground/components/Header.tsx index 582caebffb9c3..a5d8d17e2426c 100644 --- a/compiler/apps/playground/components/Header.tsx +++ b/compiler/apps/playground/components/Header.tsx @@ -58,7 +58,7 @@ export default function Header(): JSX.Element {
-