Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: migrate Editors tests to RTL #1625

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,35 +1,30 @@
import * as React from 'react';
import { MosaicNode } from 'react-mosaic-component';

import { mount, shallow } from 'enzyme';
import { MosaicWindowProps } from 'react-mosaic-component';

import { EditorId, EditorValues, MAIN_JS } from '../../../src/interfaces';
import { App } from '../../../src/renderer/app';
import { Editors } from '../../../src/renderer/components/editors';
import { Editor, EditorMosaic } from '../../../src/renderer/editor-mosaic';
import { AppState } from '../../../src/renderer/state';
import { EditorId, EditorValues, MAIN_JS } from '../../src/interfaces';
import { App } from '../../src/renderer/app';
import { Editors } from '../../src/renderer/components/editors';
import { Editor, EditorMosaic } from '../../src/renderer/editor-mosaic';
import { AppState } from '../../src/renderer/state';
import {
MonacoEditorMock,
MonacoMock,
StateMock,
createEditorValues,
} from '../../mocks/mocks';
import { emitEvent } from '../../utils';
} from '../../tests/mocks/mocks';
import { emitEvent } from '../../tests/utils';
import { renderClassComponentWithInstanceRef } from '../test-utils/renderClassComponentWithInstanceRef';

jest.mock('../../../src/renderer/components/editor', () => ({
jest.mock('../../src/renderer/components/editor', () => ({
Editor: () => 'Editor',
}));

describe('Editors component', () => {
let app: App;
let monaco: MonacoMock;
let store: AppState;
let editorMosaic: EditorMosaic;
let editorValues: EditorValues;

beforeEach(() => {
({ app } = window);
monaco = window.monaco as unknown as MonacoMock;
({ state: store } = window.app);
editorValues = createEditorValues();
editorMosaic = new EditorMosaic();
Expand All @@ -38,15 +33,20 @@ describe('Editors component', () => {
(store as unknown as StateMock).editorMosaic = editorMosaic;
});

function renderEditors() {
return renderClassComponentWithInstanceRef(Editors, {
appState: store,
});
}

it('renders', () => {
const wrapper = mount(<Editors appState={store} />);
wrapper.setState({ monaco });
expect(wrapper).toMatchSnapshot();
const { renderResult } = renderEditors();

expect(renderResult.getByTestId('editors')).toBeInTheDocument();
});

it('does not execute command if not supported', () => {
const wrapper = shallow(<Editors appState={store} />);
const instance: any = wrapper.instance();
const { instance } = renderEditors();

const editor = new MonacoEditorMock();
const action = editor.getAction();
Expand All @@ -70,15 +70,13 @@ describe('Editors component', () => {
throw new Error('Bwap bwap');
});

const wrapper = shallow(<Editors appState={store} />);
const instance: any = wrapper.instance();
const { instance } = renderEditors();

expect(instance.toggleEditorOption('wordWrap')).toBe(false);
});

it('updates a setting', () => {
const wrapper = shallow(<Editors appState={store} />);
const instance: any = wrapper.instance();
const { instance } = renderEditors();

const editor = new MonacoEditorMock();
editorMosaic.addEditor(filename, editor as unknown as Editor);
Expand All @@ -90,29 +88,35 @@ describe('Editors component', () => {
});
});

it('renders a toolbar', () => {
const wrapper = shallow(<Editors appState={store} />);
const instance: any = wrapper.instance();
const toolbar = instance.renderToolbar(
{ title: MAIN_JS } as MosaicWindowProps<EditorId>,
MAIN_JS,
);

expect(toolbar).toMatchSnapshot();
it('renders toolbars', () => {
const { renderResult } = renderEditors();

const [
mainToolbar,
rendererToolbar,
htmlToolbar,
preloadToolbar,
stylesheetToolbar,
] = renderResult.getAllByTestId('editors-toolbar');

expect(mainToolbar).toHaveTextContent('Main Process (main.js)');
expect(rendererToolbar).toHaveTextContent('Renderer Process (renderer.js)');
expect(htmlToolbar).toHaveTextContent('HTML (index.html)');
expect(preloadToolbar).toHaveTextContent('Preload (preload.js)');
expect(stylesheetToolbar).toHaveTextContent('Stylesheet (styles.css)');
});

it('onChange() updates the mosaic arrangement in the appState', () => {
const wrapper = shallow(<Editors appState={store} />);
const instance: any = wrapper.instance();
const { instance } = renderEditors();

const arrangement = { testArrangement: true };
instance.onChange(arrangement as any);
const arrangement: MosaicNode<EditorId> = 'testArrangement.js';
instance.onChange(arrangement);
expect(editorMosaic.mosaic).toStrictEqual(arrangement);
});

describe('events', () => {
it('handles a "execute-monaco-command" event', () => {
shallow(<Editors appState={store} />);
renderEditors();

const editor = new MonacoEditorMock();
const action = editor.getAction();
Expand All @@ -129,7 +133,7 @@ describe('Editors component', () => {
const fakeValues = { [MAIN_JS]: 'hi' } as const;

it('handles a "new-fiddle" event', async () => {
shallow(<Editors appState={store} />);
renderEditors();

let resolve: (value?: unknown) => void;
const replacePromise = new Promise((r) => {
Expand Down Expand Up @@ -162,7 +166,7 @@ describe('Editors component', () => {

describe('"select-all-in-editor" handler', () => {
it('selects all in the focused editor', async () => {
shallow(<Editors appState={store} />);
renderEditors();

const range = 'range';
const editor = new MonacoEditorMock();
Expand All @@ -177,7 +181,7 @@ describe('Editors component', () => {
});

it('does not change selection if the selected editor has no model', async () => {
shallow(<Editors appState={store} />);
renderEditors();

const editor = new MonacoEditorMock();
delete (editor as any).model;
Expand All @@ -191,14 +195,14 @@ describe('Editors component', () => {
});

it('does not crash if there is no selected editor', () => {
shallow(<Editors appState={store} />);
renderEditors();
editorMosaic.focusedEditor = jest.fn().mockReturnValue(null);
emitEvent('select-all-in-editor');
});
});

it('handles a "new-test" event', async () => {
shallow(<Editors appState={store} />);
renderEditors();

// setup
const getTestTemplateSpy = jest
Expand Down Expand Up @@ -229,7 +233,7 @@ describe('Editors component', () => {
});

it('handles a "select-all-in-editor" event', async () => {
shallow(<Editors appState={store} />);
renderEditors();

const range = 'range';
const editor = new MonacoEditorMock();
Expand All @@ -247,16 +251,16 @@ describe('Editors component', () => {
const editor = new MonacoEditorMock();
editorMosaic.addEditor(id, editor as unknown as Editor);

shallow(<Editors appState={store} />);
renderEditors();
emitEvent('toggle-monaco-option', 'wordWrap');
expect(editor.updateOptions).toHaveBeenCalled();
});
});

describe('setFocused()', () => {
it('sets the "focused" property', () => {
const wrapper = shallow(<Editors appState={store} />);
const instance: any = wrapper.instance();
const { instance } = renderEditors();

const spy = jest.spyOn(instance, 'setState');

const id = MAIN_JS;
Expand All @@ -265,8 +269,8 @@ describe('Editors component', () => {
});

it('focus sidebar file', () => {
const wrapper = shallow(<Editors appState={store} />);
const instance: any = wrapper.instance();
const { instance } = renderEditors();

const spy = jest.spyOn(instance, 'setState');

const id = MAIN_JS;
Expand Down
18 changes: 18 additions & 0 deletions src/renderer/components/TestIdContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { PropsWithChildren } from 'react';

type TestIdContainerProps = PropsWithChildren<{
testId: string;
}>;

/**
* A wrapper for third-party components that don't allow us to pass arbitrary
* DOM attributes like `data-testid`. It uses `display: contents` in the
* wrapping `div` so it has no CSS side effects.
*/
export function TestIdContainer({ testId, children }: TestIdContainerProps) {
return (
<div data-testid={testId} style={{ display: 'contents' }}>
{children}
</div>
);
}
19 changes: 11 additions & 8 deletions src/renderer/components/editors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { Editor } from './editor';
import { renderNonIdealState } from './editors-non-ideal-state';
import { MaximizeButton, RemoveButton } from './editors-toolbar-button';
import { TestIdContainer } from './TestIdContainer';
import { EditorId, SetFiddleOptions } from '../../interfaces';
import { AppState } from '../state';
import { getEditorTitle } from '../utils/editor-utils';
Expand Down Expand Up @@ -191,7 +192,7 @@ export const Editors = observer(
const { appState } = this.props;

return (
<div>
<div data-testid="editors-toolbar">
{/* Left */}
<div>
<h5>{title}</h5>
Expand Down Expand Up @@ -258,13 +259,15 @@ export const Editors = observer(
const { editorMosaic } = this.props.appState;

return (
<Mosaic<EditorId>
className={`focused__${this.state.focused}`}
onChange={this.onChange}
value={editorMosaic.mosaic}
zeroStateView={renderNonIdealState(editorMosaic)}
renderTile={this.renderTile}
/>
<TestIdContainer testId="editors">
<Mosaic<EditorId>
className={`focused__${this.state.focused}`}
onChange={this.onChange}
value={editorMosaic.mosaic}
zeroStateView={renderNonIdealState(editorMosaic)}
renderTile={this.renderTile}
/>
</TestIdContainer>
);
}

Expand Down
8 changes: 3 additions & 5 deletions src/renderer/components/tour-welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import { Button, Classes, Dialog } from '@blueprintjs/core';
import { observer } from 'mobx-react';

import { TestIdContainer } from './TestIdContainer';
import { Tour, TourScriptStep, TourStepGetButtonParams } from './tour';
import { AppState } from '../state';

Expand Down Expand Up @@ -238,10 +239,7 @@ export const WelcomeTour = observer(
if (!isTourStarted) {
return (
<Dialog key="welcome-tour-dialog" isOpen={true}>
<div
data-testid="welcome-tour-dialog"
style={{ display: 'contents' }}
>
<TestIdContainer testId="welcome-tour-dialog">
<div className={Classes.DIALOG_HEADER}>
<h4 className={Classes.HEADING}>🙋‍ Hey There!</h4>
</div>
Expand All @@ -260,7 +258,7 @@ export const WelcomeTour = observer(
{this.buttons}
</div>
</div>
</div>
</TestIdContainer>
</Dialog>
);
} else {
Expand Down
Loading