1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import {expect, test, type Page} from '@playwright/test';9import {encodeStore, type Store} from '../../lib/stores';10import {defaultConfig} from '../../lib/defaultStore';11import {format} from 'prettier';1213function isMonacoLoaded(): boolean {14 return (15 typeof window['MonacoEnvironment'] !== 'undefined' &&16 window['__MONACO_LOADED__'] === true17 );18}1920function formatPrint(data: Array<string>): Promise<string> {21 return format(data.join(''), {parser: 'babel'});22}2324async function expandConfigs(page: Page): Promise<void> {25 const expandButton = page.locator('[title="Expand config editor"]');26 await expandButton.click();27 await page.waitForSelector('.monaco-editor-config', {state: 'visible'});28}2930const TEST_SOURCE = `export default function TestComponent({ x }) {31 return <Button>{x}</Button>;32}`;3334const TEST_CASE_INPUTS = [35 {36 name: 'module-scope-use-memo',37 input: `38'use memo';39export default function TestComponent({ x }) {40 return <Button>{x}</Button>;41}`,42 },43 {44 name: 'module-scope-use-no-memo',45 input: `46'use no memo';47export default function TestComponent({ x }) {48 return <Button>{x}</Button>;49}`,50 },51 {52 name: 'use-memo',53 input: `54function TestComponent({ x }) {55 'use memo';56 return <Button>{x}</Button>;57}58const TestComponent2 = ({ x }) => {59 'use memo';60 return <Button>{x}</Button>;61};`,62 },63 {64 name: 'use-no-memo',65 input: `66const TestComponent = function() {67 'use no memo';68 return <Button>{x}</Button>;69};70const TestComponent2 = ({ x }) => {71 'use no memo';72 return <Button>{x}</Button>;73};`,74 },75 {76 name: 'todo-function-scope-does-not-beat-module-scope',77 input: `78'use no memo';79function TestComponent({ x }) {80 'use memo';81 return <Button>{x}</Button>;82}`,83 },84 {85 name: 'parse-typescript',86 input: `87function Foo() {88 const x = foo() as number;89 return <div>{x}</div>;90}91`,92 noFormat: true,93 },94 {95 name: 'parse-flow',96 input: `97// @flow98function useFoo(propVal: {+baz: number}) {99 return <div>{(propVal.baz as number)}</div>;100}101 `,102 noFormat: true,103 },104 {105 name: 'compilationMode-infer',106 input: `// @compilationMode:"infer"107function nonReactFn() {108 return {};109}110 `,111 noFormat: true,112 },113 {114 name: 'compilationMode-all',115 input: `// @compilationMode:"all"116function nonReactFn() {117 return {};118}119 `,120 noFormat: true,121 },122];123124test('editor should open successfully', async ({page}) => {125 await page.goto(`/`, {waitUntil: 'networkidle'});126 await page.waitForFunction(isMonacoLoaded);127 await page.screenshot({128 fullPage: true,129 path: 'test-results/00-fresh-page.png',130 });131});132133test('editor should compile from hash successfully', async ({page}) => {134 const store: Store = {135 source: TEST_SOURCE,136 config: defaultConfig,137 showInternals: false,138 };139 const hash = encodeStore(store);140 await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});141 await page.waitForFunction(isMonacoLoaded);142143 // User input from hash compiles144 await page.screenshot({145 fullPage: true,146 path: 'test-results/01-compiles-from-hash.png',147 });148 const text =149 (await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];150 const output = await formatPrint(text);151152 expect(output).not.toEqual('');153 expect(output).toMatchSnapshot('01-user-output.txt');154});155156test('reset button works', async ({page}) => {157 const store: Store = {158 source: TEST_SOURCE,159 config: defaultConfig,160 showInternals: false,161 };162 const hash = encodeStore(store);163 await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});164 await page.waitForFunction(isMonacoLoaded);165166 // Reset button works167 page.on('dialog', dialog => dialog.accept());168 await page.getByRole('button', {name: 'Reset'}).click();169 await expandConfigs(page);170171 await page.screenshot({172 fullPage: true,173 path: 'test-results/02-reset-button-works.png',174 });175 const text =176 (await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];177 const output = await formatPrint(text);178179 const configText =180 (await page.locator('.monaco-editor-config').allInnerTexts()) ?? [];181 const configOutput = configText.join('');182183 expect(output).not.toEqual('');184 expect(output).toMatchSnapshot('02-default-output.txt');185 expect(configOutput).not.toEqual('');186 expect(configOutput).toMatchSnapshot('default-config.txt');187});188189test('defaults load when only source is in Store', async ({page}) => {190 // Test for backwards compatibility191 const partial = {192 source: TEST_SOURCE,193 };194 const hash = encodeStore(partial as Store);195 await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});196 await page.waitForFunction(isMonacoLoaded);197 await expandConfigs(page);198199 await page.screenshot({200 fullPage: true,201 path: 'test-results/03-missing-defaults.png',202 });203204 // Config editor has default config205 const configText =206 (await page.locator('.monaco-editor-config').allInnerTexts()) ?? [];207 const configOutput = configText.join('');208209 expect(configOutput).not.toEqual('');210 expect(configOutput).toMatchSnapshot('default-config.txt');211212 const checkbox = page.locator('label.show-internals');213 await expect(checkbox).not.toBeChecked();214 const ssaTab = page.locator('text=SSA');215 await expect(ssaTab).not.toBeVisible();216});217218test('show internals button toggles correctly', async ({page}) => {219 await page.goto(`/`, {waitUntil: 'networkidle'});220 await page.waitForFunction(isMonacoLoaded);221222 // show internals should be off223 const checkbox = page.locator('label.show-internals');224 await checkbox.click();225226 await page.screenshot({227 fullPage: true,228 path: 'test-results/04-show-internals-on.png',229 });230231 await expect(checkbox).toBeChecked();232233 const ssaTab = page.locator('text=SSA');234 await expect(ssaTab).toBeVisible();235});236237test('error is displayed when config has syntax error', async ({page}) => {238 const store: Store = {239 source: TEST_SOURCE,240 config: `{ compilationMode: }`,241 showInternals: false,242 };243 const hash = encodeStore(store);244 await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});245 await page.waitForFunction(isMonacoLoaded);246 await expandConfigs(page);247 await page.screenshot({248 fullPage: true,249 path: 'test-results/05-config-syntax-error.png',250 });251252 const text =253 (await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];254 const output = text.join('');255256 // Remove hidden chars257 expect(output.replace(/\s+/g, ' ')).toContain(258 'Unexpected failure when transforming configs',259 );260});261262test('error is displayed when config has validation error', async ({page}) => {263 const store: Store = {264 source: TEST_SOURCE,265 config: `{266 compilationMode: "123"267}`,268 showInternals: false,269 };270 const hash = encodeStore(store);271 await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});272 await page.waitForFunction(isMonacoLoaded);273 await expandConfigs(page);274 await page.screenshot({275 fullPage: true,276 path: 'test-results/06-config-validation-error.png',277 });278279 const text =280 (await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];281 const output = text.join('');282283 expect(output.replace(/\s+/g, ' ')).toContain('Unexpected compilationMode');284});285286test('error is displayed when source has syntax error', async ({page}) => {287 const syntaxErrorSource = `function TestComponent(props) {288 const oops = props.289 return (290 <>{oops}</>291 );292}`;293 const store: Store = {294 source: syntaxErrorSource,295 config: defaultConfig,296 showInternals: false,297 };298 const hash = encodeStore(store);299 await page.goto(`/#${hash}`);300 await page.waitForFunction(isMonacoLoaded);301 await expandConfigs(page);302 await page.screenshot({303 fullPage: true,304 path: 'test-results/08-source-syntax-error.png',305 });306307 const text =308 (await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];309 const output = text.join('');310311 expect(output.replace(/\s+/g, ' ')).toContain(312 'Expected identifier to be defined before being used',313 );314});315316TEST_CASE_INPUTS.forEach((t, idx) =>317 test(`playground compiles: ${t.name}`, async ({page}) => {318 const store: Store = {319 source: t.input,320 config: defaultConfig,321 showInternals: false,322 };323 const hash = encodeStore(store);324 await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});325 await page.waitForFunction(isMonacoLoaded);326 await page.screenshot({327 fullPage: true,328 path: `test-results/08-0${idx}-${t.name}.png`,329 });330331 const text =332 (await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];333 let output: string;334 if (t.noFormat) {335 output = text.join('');336 } else {337 output = await formatPrint(text);338 }339340 expect(output).not.toEqual('');341 expect(output).toMatchSnapshot(`${t.name}-output.txt`);342 }),343);
Findings
✓ No findings reported for this file.