Skip to content

Commit 5a02bbf

Browse files
committed
test: migrate Editors tests to RTL
1 parent 8c7d666 commit 5a02bbf

11 files changed

+140
-1071
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { mount } from 'enzyme';
1+
import { render } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
23

34
import { renderNonIdealState } from '../../src/renderer/components/editors-non-ideal-state';
45
import { EditorMosaic } from '../../src/renderer/editor-mosaic';
@@ -11,13 +12,17 @@ describe('renderNonIdealState()', () => {
1112
});
1213

1314
it('renders a non-ideal state', () => {
14-
expect(renderNonIdealState({} as EditorMosaic)).toMatchSnapshot();
15+
const renderResult = render(renderNonIdealState({} as EditorMosaic));
16+
17+
expect(
18+
renderResult.getByTestId('editors-non-ideal-state'),
19+
).toBeInTheDocument();
1520
});
1621

17-
it('handles a click', () => {
22+
it('handles a click', async () => {
1823
const resetLayoutSpy = jest.spyOn(editorMosaic, 'resetLayout');
19-
const wrapper = mount(renderNonIdealState(editorMosaic));
20-
wrapper.find('button').simulate('click');
24+
const renderResult = render(renderNonIdealState(editorMosaic));
25+
await userEvent.click(renderResult.getByRole('button'));
2126
expect(resetLayoutSpy).toHaveBeenCalledTimes(1);
2227
});
2328
});

rtl-spec/components/editors-toolbar-button.spec.tsx

+29-22
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import * as React from 'react';
2-
3-
import { shallow } from 'enzyme';
1+
import userEvent from '@testing-library/user-event';
2+
import { MosaicContext, MosaicWindowContext } from 'react-mosaic-component';
43

54
import { EditorId, MAIN_JS } from '../../src/interfaces';
65
import {
76
MaximizeButton,
87
RemoveButton,
98
} from '../../src/renderer/components/editors-toolbar-button';
109
import { AppState } from '../../src/renderer/state';
10+
import { renderClassComponentWithInstanceRef } from '../test-utils/renderClassComponentWithInstanceRef';
1111

12-
let mockContext: any = {};
12+
let mockContext = {} as MosaicWindowContext & MosaicContext<string>;
1313

1414
jest.mock('react-mosaic-component', () => {
1515
const { MosaicContext, MosaicRootActions, MosaicWindowContext } =
@@ -52,44 +52,51 @@ describe('Editor toolbar button component', () => {
5252

5353
describe('MaximizeButton', () => {
5454
function createMaximizeButton(id: EditorId) {
55-
const wrapper = shallow(<MaximizeButton id={id} appState={store} />, {
56-
context: mockContext,
55+
return renderClassComponentWithInstanceRef(MaximizeButton, {
56+
id,
57+
appState: store,
5758
});
58-
const instance = wrapper.instance();
59-
return { instance, wrapper };
59+
60+
// const wrapper = shallow(<MaximizeButton id={id} appState={store} />, {
61+
// context: mockContext,
62+
// });
6063
}
6164

6265
it('renders', () => {
63-
const { wrapper } = createMaximizeButton(MAIN_JS);
64-
expect(wrapper).toMatchSnapshot();
66+
const { renderResult } = createMaximizeButton(MAIN_JS);
67+
expect(
68+
renderResult.getByTestId('editors-toolbar-maximize-button'),
69+
).toBeInTheDocument();
6570
});
6671

67-
it('handles a click', () => {
68-
const { instance, wrapper } = createMaximizeButton(MAIN_JS);
69-
instance.context = mockContext as unknown;
70-
wrapper.dive().dive().find('button').simulate('click');
72+
it('handles a click', async () => {
73+
const { instance, renderResult } = createMaximizeButton(MAIN_JS);
74+
instance.context = mockContext;
75+
await userEvent.click(renderResult.getByRole('button'));
7176
expect(mockContext.mosaicActions.expand).toHaveBeenCalledTimes(1);
7277
});
7378
});
7479

7580
describe('RemoveButton', () => {
7681
function createRemoveButton(id: EditorId) {
77-
const wrapper = shallow(<RemoveButton id={id} appState={store} />, {
78-
context: mockContext,
82+
return renderClassComponentWithInstanceRef(RemoveButton, {
83+
id,
84+
appState: store,
7985
});
80-
return { wrapper };
8186
}
8287

8388
it('renders', () => {
84-
const { wrapper } = createRemoveButton(MAIN_JS);
85-
expect(wrapper).toMatchSnapshot();
89+
const { renderResult } = createRemoveButton(MAIN_JS);
90+
expect(
91+
renderResult.getByTestId('editors-toolbar-remove-button'),
92+
).toBeInTheDocument();
8693
});
8794

88-
it('handles a click', () => {
95+
it('handles a click', async () => {
8996
const { editorMosaic } = store;
9097
const hideSpy = jest.spyOn(editorMosaic, 'hide');
91-
const { wrapper } = createRemoveButton(MAIN_JS);
92-
wrapper.dive().dive().find('button').simulate('click');
98+
const { renderResult } = createRemoveButton(MAIN_JS);
99+
await userEvent.click(renderResult.getByRole('button'));
93100
expect(hideSpy).toHaveBeenCalledTimes(1);
94101
});
95102
});

rtl-spec/components/editors.spec.tsx

+45-41
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import * as React from 'react';
2-
3-
import { mount, shallow } from 'enzyme';
4-
import { MosaicWindowProps } from 'react-mosaic-component';
1+
import { MosaicNode } from 'react-mosaic-component';
52

63
import { EditorId, EditorValues, MAIN_JS } from '../../src/interfaces';
74
import { App } from '../../src/renderer/app';
@@ -10,26 +7,24 @@ import { Editor, EditorMosaic } from '../../src/renderer/editor-mosaic';
107
import { AppState } from '../../src/renderer/state';
118
import {
129
MonacoEditorMock,
13-
MonacoMock,
1410
StateMock,
1511
createEditorValues,
1612
} from '../../tests/mocks/mocks';
1713
import { emitEvent } from '../../tests/utils';
14+
import { renderClassComponentWithInstanceRef } from '../test-utils/renderClassComponentWithInstanceRef';
1815

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

2320
describe('Editors component', () => {
2421
let app: App;
25-
let monaco: MonacoMock;
2622
let store: AppState;
2723
let editorMosaic: EditorMosaic;
2824
let editorValues: EditorValues;
2925

3026
beforeEach(() => {
3127
({ app } = window);
32-
monaco = window.monaco as unknown as MonacoMock;
3328
({ state: store } = window.app);
3429
editorValues = createEditorValues();
3530
editorMosaic = new EditorMosaic();
@@ -38,15 +33,20 @@ describe('Editors component', () => {
3833
(store as unknown as StateMock).editorMosaic = editorMosaic;
3934
});
4035

36+
function renderEditors() {
37+
return renderClassComponentWithInstanceRef(Editors, {
38+
appState: store,
39+
});
40+
}
41+
4142
it('renders', () => {
42-
const wrapper = mount(<Editors appState={store} />);
43-
wrapper.setState({ monaco });
44-
expect(wrapper).toMatchSnapshot();
43+
const { renderResult } = renderEditors();
44+
45+
expect(renderResult.getByTestId('editors')).toBeInTheDocument();
4546
});
4647

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

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

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

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

7978
it('updates a setting', () => {
80-
const wrapper = shallow(<Editors appState={store} />);
81-
const instance: any = wrapper.instance();
79+
const { instance } = renderEditors();
8280

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

93-
it('renders a toolbar', () => {
94-
const wrapper = shallow(<Editors appState={store} />);
95-
const instance: any = wrapper.instance();
96-
const toolbar = instance.renderToolbar(
97-
{ title: MAIN_JS } as MosaicWindowProps<EditorId>,
98-
MAIN_JS,
99-
);
100-
101-
expect(toolbar).toMatchSnapshot();
91+
it('renders toolbars', () => {
92+
const { renderResult } = renderEditors();
93+
94+
const [
95+
mainToolbar,
96+
rendererToolbar,
97+
htmlToolbar,
98+
preloadToolbar,
99+
stylesheetToolbar,
100+
] = renderResult.getAllByTestId('editors-toolbar');
101+
102+
expect(mainToolbar).toHaveTextContent('Main Process (main.js)');
103+
expect(rendererToolbar).toHaveTextContent('Renderer Process (renderer.js)');
104+
expect(htmlToolbar).toHaveTextContent('HTML (index.html)');
105+
expect(preloadToolbar).toHaveTextContent('Preload (preload.js)');
106+
expect(stylesheetToolbar).toHaveTextContent('Stylesheet (styles.css)');
102107
});
103108

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

108-
const arrangement = { testArrangement: true };
109-
instance.onChange(arrangement as any);
112+
const arrangement: MosaicNode<EditorId> = 'testArrangement.js';
113+
instance.onChange(arrangement);
110114
expect(editorMosaic.mosaic).toStrictEqual(arrangement);
111115
});
112116

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

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

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

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

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

167171
const range = 'range';
168172
const editor = new MonacoEditorMock();
@@ -177,7 +181,7 @@ describe('Editors component', () => {
177181
});
178182

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

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

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

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

203207
// setup
204208
const getTestTemplateSpy = jest
@@ -229,7 +233,7 @@ describe('Editors component', () => {
229233
});
230234

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

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

250-
shallow(<Editors appState={store} />);
254+
renderEditors();
251255
emitEvent('toggle-monaco-option', 'wordWrap');
252256
expect(editor.updateOptions).toHaveBeenCalled();
253257
});
254258
});
255259

256260
describe('setFocused()', () => {
257261
it('sets the "focused" property', () => {
258-
const wrapper = shallow(<Editors appState={store} />);
259-
const instance: any = wrapper.instance();
262+
const { instance } = renderEditors();
263+
260264
const spy = jest.spyOn(instance, 'setState');
261265

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

267271
it('focus sidebar file', () => {
268-
const wrapper = shallow(<Editors appState={store} />);
269-
const instance: any = wrapper.instance();
272+
const { instance } = renderEditors();
273+
270274
const spy = jest.spyOn(instance, 'setState');
271275

272276
const id = MAIN_JS;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React, { PropsWithChildren } from 'react';
2+
3+
type TestIdContainerProps = PropsWithChildren<{
4+
testId: string;
5+
}>;
6+
7+
/**
8+
* A wrapper for third-party components that don't allow us to pass arbitrary
9+
* DOM attributes like `data-testid`. It uses `display: contents` in the
10+
* wrapping `div` so it has no CSS side effects.
11+
*/
12+
export function TestIdContainer({ testId, children }: TestIdContainerProps) {
13+
return (
14+
<div data-testid={testId} style={{ display: 'contents' }}>
15+
{children}
16+
</div>
17+
);
18+
}

src/renderer/components/editors-non-ideal-state.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react';
22

33
import { Button, NonIdealState } from '@blueprintjs/core';
44

5+
import { TestIdContainer } from './TestIdContainer';
56
import { EditorMosaic } from '../editor-mosaic';
67

78
export function renderNonIdealState(editorMosaic: EditorMosaic) {
@@ -10,10 +11,12 @@ export function renderNonIdealState(editorMosaic: EditorMosaic) {
1011
);
1112

1213
return (
13-
<NonIdealState
14-
action={resolveButton}
15-
icon="applications"
16-
description="You have closed all editors. You can open them again with the button below or in the sidebar menu!"
17-
/>
14+
<TestIdContainer testId="editors-non-ideal-state">
15+
<NonIdealState
16+
action={resolveButton}
17+
icon="applications"
18+
description="You have closed all editors. You can open them again with the button below or in the sidebar menu!"
19+
/>
20+
</TestIdContainer>
1821
);
1922
}

0 commit comments

Comments
 (0)