Skip to content

Commit

Permalink
feat(CodeEditor): use new monaco package (#9842)
Browse files Browse the repository at this point in the history
* feat(CodeEditor): use new monaco package

* remove commented code

* fix integration

* update tests and mock out editor

* move to direct dependency
  • Loading branch information
kmcfaul committed Jan 5, 2024
1 parent 01b0155 commit 54dd134
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 495 deletions.
6 changes: 3 additions & 3 deletions packages/react-code-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
"@patternfly/react-icons": "^5.2.0-prerelease.9",
"@patternfly/react-styles": "^5.2.0-prerelease.6",
"react-dropzone": "14.2.3",
"tslib": "^2.5.0"
"tslib": "^2.5.0",
"@monaco-editor/react": "^4.6.0"
},
"peerDependencies": {
"react": "^17 || ^18",
"react-dom": "^17 || ^18",
"react-monaco-editor": "^0.51.0"
"react-dom": "^17 || ^18"
},
"devDependencies": {
"rimraf": "^2.6.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
TooltipPosition,
EmptyStateHeader
} from '@patternfly/react-core';
import MonacoEditor, { ChangeHandler, EditorDidMount } from 'react-monaco-editor';
import Editor, { Monaco } from '@monaco-editor/react';
import { editor } from 'monaco-editor/esm/vs/editor/editor.api';
import CopyIcon from '@patternfly/react-icons/dist/esm/icons/copy-icon';
import UploadIcon from '@patternfly/react-icons/dist/esm/icons/upload-icon';
Expand All @@ -28,6 +28,9 @@ import Dropzone, { FileRejection } from 'react-dropzone';
import { CodeEditorContext } from './CodeEditorUtils';
import { CodeEditorControl } from './CodeEditorControl';

export type ChangeHandler = (value: string, event: editor.IModelContentChangedEvent) => void;
export type EditorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void;

export interface Shortcut {
description: string;
keys: string[];
Expand Down Expand Up @@ -631,15 +634,15 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {

const editor = (
<div className={css(styles.codeEditorCode)} ref={this.wrapperRef} tabIndex={0} dir="ltr">
<MonacoEditor
<Editor
height={height}
width={width}
language={language}
value={value}
options={options}
overrideServices={overrideServices}
onChange={this.onChange}
editorDidMount={this.editorDidMount}
onMount={this.editorDidMount}
theme={isDarkTheme ? 'vs-dark' : 'vs-light'}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,79 @@
import React from 'react';
import { render } from '@testing-library/react';
import { render, screen, act } from '@testing-library/react';
import { CodeEditor, Language } from '../CodeEditor';
import styles from '@patternfly/react-styles/css/components/CodeEditor/code-editor';
import fileUploadStyles from '@patternfly/react-styles/css/components/FileUpload/file-upload';

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
}))
});

describe('CodeEditor', () => {
beforeAll(() => {
window.HTMLCanvasElement.prototype.getContext = () => ({}) as any;
});
jest.mock('@monaco-editor/react', () => jest.fn(() => <div data-testid="mock-editor"></div>));

test('matches snapshot without props', () => {
const { asFragment } = render(<CodeEditor />);
expect(asFragment()).toMatchSnapshot();
});
test('Matches snapshot without props', () => {
const { asFragment } = render(<CodeEditor code="test" />);
expect(asFragment()).toMatchSnapshot();
});

test('Matches snapshot with control buttons enabled', () => {
const { asFragment } = render(<CodeEditor isUploadEnabled isDownloadEnabled isCopyEnabled code="test" />);
expect(asFragment()).toMatchSnapshot();
});

test(`Renders with default classes ${styles.codeEditor}, ${styles.codeEditorMain}, ${styles.codeEditorCode}`, () => {
render(<CodeEditor />);
expect(screen.getByTestId('mock-editor').parentElement).toHaveClass(styles.codeEditorCode);
expect(screen.getByTestId('mock-editor').parentElement?.parentElement).toHaveClass(styles.codeEditorMain);
expect(screen.getByTestId('mock-editor').parentElement?.parentElement?.parentElement).toHaveClass(styles.codeEditor);
});

test('Renders custom class when className is passed', () => {
render(<CodeEditor className="custom" />);
expect(screen.getByTestId('mock-editor').parentElement?.parentElement?.parentElement).toHaveClass('custom');
});

test(`Renders with ${styles.modifiers.readOnly} when isReadOnly = true`, () => {
render(<CodeEditor isReadOnly />);
expect(screen.getByTestId('mock-editor').parentElement?.parentElement?.parentElement).toHaveClass(
styles.modifiers.readOnly
);
});

test(`Renders with ${fileUploadStyles.fileUpload} when isUploadEnabled = true`, () => {
render(<CodeEditor isUploadEnabled code="test" />);
expect(screen.getByTestId('mock-editor').parentElement?.parentElement?.parentElement).toHaveClass(
fileUploadStyles.fileUpload
);
});

test(`Renders with empty state when code = undefined`, () => {
render(<CodeEditor emptyState={<div>empty</div>} />);
expect(screen.getByText('empty')).toBeInTheDocument();
});

test(`Renders with empty state when isUploadEnabled = true and code = undefined`, () => {
render(<CodeEditor emptyState={<div>empty</div>} isUploadEnabled />);
expect(screen.getByText('empty')).toBeInTheDocument();
});

test(`Renders with language label when isLanguageLabelVisible`, () => {
render(<CodeEditor isLanguageLabelVisible language={Language.java} />);
expect(screen.getByText('JAVA')).toBeInTheDocument();
});

test(`Renders with custom controls when customControls is passed`, () => {
render(<CodeEditor customControls={<div>control</div>} />);
expect(screen.getByText('control')).toBeInTheDocument();
});

test(`Renders with custom header content when headerMainContent is passed`, () => {
render(<CodeEditor headerMainContent="header content" />);
expect(screen.getByText('header content')).toBeInTheDocument();
});

test('matches snapshot with all props', () => {
const { asFragment } = render(
<CodeEditor
isReadOnly
isDarkTheme
isLineNumbersVisible={false}
isUploadEnabled
isLanguageLabelVisible
isDownloadEnabled
isCopyEnabled
height="400px"
code={'test'}
language={Language.javascript}
/>
);
expect(asFragment()).toMatchSnapshot();
test(`Renders with shortcuts when shortcutsPopoverButtonText is passed`, () => {
render(
<CodeEditor shortcutsPopoverButtonText="shortcuts-button" shortcutsPopoverProps={{ bodyContent: 'shortcuts' }} />
);
expect(screen.getByText('shortcuts-button')).toBeInTheDocument();
act(() => {
screen.getByText('shortcuts-button').click();
});
expect(screen.getByText('shortcuts')).toBeInTheDocument();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { CodeEditorControl } from '../CodeEditorControl';

test('Matches snapshot', () => {
const { asFragment } = render(<CodeEditorControl icon={<div>icon</div>} onClick={jest.fn()} />);
expect(asFragment()).toMatchSnapshot();
});

test('Renders with custom class when className is passed', () => {
render(<CodeEditorControl className="custom" icon={<div>icon</div>} onClick={jest.fn()} />);
expect(screen.getByText('icon').parentElement).toHaveClass('custom');
});

test('Renders with accessible name when aria-label is passed', () => {
render(<CodeEditorControl aria-label="aria-test" icon={<div>icon</div>} onClick={jest.fn()} />);
expect(screen.getByLabelText('aria-test'));
});
Loading

0 comments on commit 54dd134

Please sign in to comment.