compiler/apps/playground/__tests__/e2e/page.spec.ts TYPESCRIPT 344 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.