diff --git a/rtl-spec/components/editors-non-ideal-state.spec.tsx b/rtl-spec/components/editors-non-ideal-state.spec.tsx index 19fe2d9d0d..3ca595f64c 100644 --- a/rtl-spec/components/editors-non-ideal-state.spec.tsx +++ b/rtl-spec/components/editors-non-ideal-state.spec.tsx @@ -1,4 +1,5 @@ -import { mount } from 'enzyme'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { renderNonIdealState } from '../../src/renderer/components/editors-non-ideal-state'; import { EditorMosaic } from '../../src/renderer/editor-mosaic'; @@ -11,13 +12,17 @@ describe('renderNonIdealState()', () => { }); it('renders a non-ideal state', () => { - expect(renderNonIdealState({} as EditorMosaic)).toMatchSnapshot(); + const renderResult = render(renderNonIdealState({} as EditorMosaic)); + + expect( + renderResult.getByTestId('editors-non-ideal-state'), + ).toBeInTheDocument(); }); - it('handles a click', () => { + it('handles a click', async () => { const resetLayoutSpy = jest.spyOn(editorMosaic, 'resetLayout'); - const wrapper = mount(renderNonIdealState(editorMosaic)); - wrapper.find('button').simulate('click'); + const renderResult = render(renderNonIdealState(editorMosaic)); + await userEvent.click(renderResult.getByRole('button')); expect(resetLayoutSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/rtl-spec/components/editors-toolbar-button.spec.tsx b/rtl-spec/components/editors-toolbar-button.spec.tsx index fa27b12d0c..d3d092c00e 100644 --- a/rtl-spec/components/editors-toolbar-button.spec.tsx +++ b/rtl-spec/components/editors-toolbar-button.spec.tsx @@ -1,6 +1,5 @@ -import * as React from 'react'; - -import { shallow } from 'enzyme'; +import userEvent from '@testing-library/user-event'; +import { MosaicContext, MosaicWindowContext } from 'react-mosaic-component'; import { EditorId, MAIN_JS } from '../../src/interfaces'; import { @@ -8,8 +7,9 @@ import { RemoveButton, } from '../../src/renderer/components/editors-toolbar-button'; import { AppState } from '../../src/renderer/state'; +import { renderClassComponentWithInstanceRef } from '../test-utils/renderClassComponentWithInstanceRef'; -let mockContext: any = {}; +let mockContext = {} as MosaicWindowContext & MosaicContext; jest.mock('react-mosaic-component', () => { const { MosaicContext, MosaicRootActions, MosaicWindowContext } = @@ -52,44 +52,51 @@ describe('Editor toolbar button component', () => { describe('MaximizeButton', () => { function createMaximizeButton(id: EditorId) { - const wrapper = shallow(, { - context: mockContext, + return renderClassComponentWithInstanceRef(MaximizeButton, { + id, + appState: store, }); - const instance = wrapper.instance(); - return { instance, wrapper }; + + // const wrapper = shallow(, { + // context: mockContext, + // }); } it('renders', () => { - const { wrapper } = createMaximizeButton(MAIN_JS); - expect(wrapper).toMatchSnapshot(); + const { renderResult } = createMaximizeButton(MAIN_JS); + expect( + renderResult.getByTestId('editors-toolbar-maximize-button'), + ).toBeInTheDocument(); }); - it('handles a click', () => { - const { instance, wrapper } = createMaximizeButton(MAIN_JS); - instance.context = mockContext as unknown; - wrapper.dive().dive().find('button').simulate('click'); + it('handles a click', async () => { + const { instance, renderResult } = createMaximizeButton(MAIN_JS); + instance.context = mockContext; + await userEvent.click(renderResult.getByRole('button')); expect(mockContext.mosaicActions.expand).toHaveBeenCalledTimes(1); }); }); describe('RemoveButton', () => { function createRemoveButton(id: EditorId) { - const wrapper = shallow(, { - context: mockContext, + return renderClassComponentWithInstanceRef(RemoveButton, { + id, + appState: store, }); - return { wrapper }; } it('renders', () => { - const { wrapper } = createRemoveButton(MAIN_JS); - expect(wrapper).toMatchSnapshot(); + const { renderResult } = createRemoveButton(MAIN_JS); + expect( + renderResult.getByTestId('editors-toolbar-remove-button'), + ).toBeInTheDocument(); }); - it('handles a click', () => { + it('handles a click', async () => { const { editorMosaic } = store; const hideSpy = jest.spyOn(editorMosaic, 'hide'); - const { wrapper } = createRemoveButton(MAIN_JS); - wrapper.dive().dive().find('button').simulate('click'); + const { renderResult } = createRemoveButton(MAIN_JS); + await userEvent.click(renderResult.getByRole('button')); expect(hideSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/rtl-spec/components/editors.spec.tsx b/rtl-spec/components/editors.spec.tsx index 56c482f0ae..887c343319 100644 --- a/rtl-spec/components/editors.spec.tsx +++ b/rtl-spec/components/editors.spec.tsx @@ -1,7 +1,4 @@ -import * as React from 'react'; - -import { mount, shallow } from 'enzyme'; -import { MosaicWindowProps } from 'react-mosaic-component'; +import { MosaicNode } from 'react-mosaic-component'; import { EditorId, EditorValues, MAIN_JS } from '../../src/interfaces'; import { App } from '../../src/renderer/app'; @@ -10,11 +7,11 @@ import { Editor, EditorMosaic } from '../../src/renderer/editor-mosaic'; import { AppState } from '../../src/renderer/state'; import { MonacoEditorMock, - MonacoMock, StateMock, createEditorValues, } from '../../tests/mocks/mocks'; import { emitEvent } from '../../tests/utils'; +import { renderClassComponentWithInstanceRef } from '../test-utils/renderClassComponentWithInstanceRef'; jest.mock('../../src/renderer/components/editor', () => ({ Editor: () => 'Editor', @@ -22,14 +19,12 @@ jest.mock('../../src/renderer/components/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(); @@ -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(); - 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(); - const instance: any = wrapper.instance(); + const { instance } = renderEditors(); const editor = new MonacoEditorMock(); const action = editor.getAction(); @@ -70,15 +70,13 @@ describe('Editors component', () => { throw new Error('Bwap bwap'); }); - const wrapper = shallow(); - const instance: any = wrapper.instance(); + const { instance } = renderEditors(); expect(instance.toggleEditorOption('wordWrap')).toBe(false); }); it('updates a setting', () => { - const wrapper = shallow(); - const instance: any = wrapper.instance(); + const { instance } = renderEditors(); const editor = new MonacoEditorMock(); editorMosaic.addEditor(filename, editor as unknown as Editor); @@ -90,29 +88,35 @@ describe('Editors component', () => { }); }); - it('renders a toolbar', () => { - const wrapper = shallow(); - const instance: any = wrapper.instance(); - const toolbar = instance.renderToolbar( - { title: MAIN_JS } as MosaicWindowProps, - 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(); - const instance: any = wrapper.instance(); + const { instance } = renderEditors(); - const arrangement = { testArrangement: true }; - instance.onChange(arrangement as any); + const arrangement: MosaicNode = 'testArrangement.js'; + instance.onChange(arrangement); expect(editorMosaic.mosaic).toStrictEqual(arrangement); }); describe('events', () => { it('handles a "execute-monaco-command" event', () => { - shallow(); + renderEditors(); const editor = new MonacoEditorMock(); const action = editor.getAction(); @@ -129,7 +133,7 @@ describe('Editors component', () => { const fakeValues = { [MAIN_JS]: 'hi' } as const; it('handles a "new-fiddle" event', async () => { - shallow(); + renderEditors(); let resolve: (value?: unknown) => void; const replacePromise = new Promise((r) => { @@ -162,7 +166,7 @@ describe('Editors component', () => { describe('"select-all-in-editor" handler', () => { it('selects all in the focused editor', async () => { - shallow(); + renderEditors(); const range = 'range'; const editor = new MonacoEditorMock(); @@ -177,7 +181,7 @@ describe('Editors component', () => { }); it('does not change selection if the selected editor has no model', async () => { - shallow(); + renderEditors(); const editor = new MonacoEditorMock(); delete (editor as any).model; @@ -191,14 +195,14 @@ describe('Editors component', () => { }); it('does not crash if there is no selected editor', () => { - shallow(); + renderEditors(); editorMosaic.focusedEditor = jest.fn().mockReturnValue(null); emitEvent('select-all-in-editor'); }); }); it('handles a "new-test" event', async () => { - shallow(); + renderEditors(); // setup const getTestTemplateSpy = jest @@ -229,7 +233,7 @@ describe('Editors component', () => { }); it('handles a "select-all-in-editor" event', async () => { - shallow(); + renderEditors(); const range = 'range'; const editor = new MonacoEditorMock(); @@ -247,7 +251,7 @@ describe('Editors component', () => { const editor = new MonacoEditorMock(); editorMosaic.addEditor(id, editor as unknown as Editor); - shallow(); + renderEditors(); emitEvent('toggle-monaco-option', 'wordWrap'); expect(editor.updateOptions).toHaveBeenCalled(); }); @@ -255,8 +259,8 @@ describe('Editors component', () => { describe('setFocused()', () => { it('sets the "focused" property', () => { - const wrapper = shallow(); - const instance: any = wrapper.instance(); + const { instance } = renderEditors(); + const spy = jest.spyOn(instance, 'setState'); const id = MAIN_JS; @@ -265,8 +269,8 @@ describe('Editors component', () => { }); it('focus sidebar file', () => { - const wrapper = shallow(); - const instance: any = wrapper.instance(); + const { instance } = renderEditors(); + const spy = jest.spyOn(instance, 'setState'); const id = MAIN_JS; diff --git a/src/renderer/components/TestIdContainer.tsx b/src/renderer/components/TestIdContainer.tsx new file mode 100644 index 0000000000..03c7fe5568 --- /dev/null +++ b/src/renderer/components/TestIdContainer.tsx @@ -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 ( +
+ {children} +
+ ); +} diff --git a/src/renderer/components/editors-non-ideal-state.tsx b/src/renderer/components/editors-non-ideal-state.tsx index 386b6a8f6a..bc1449d03c 100644 --- a/src/renderer/components/editors-non-ideal-state.tsx +++ b/src/renderer/components/editors-non-ideal-state.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { Button, NonIdealState } from '@blueprintjs/core'; +import { TestIdContainer } from './TestIdContainer'; import { EditorMosaic } from '../editor-mosaic'; export function renderNonIdealState(editorMosaic: EditorMosaic) { @@ -10,10 +11,12 @@ export function renderNonIdealState(editorMosaic: EditorMosaic) { ); return ( - + + + ); } diff --git a/src/renderer/components/editors-toolbar-button.tsx b/src/renderer/components/editors-toolbar-button.tsx index c0bd816737..4ab2c25948 100644 --- a/src/renderer/components/editors-toolbar-button.tsx +++ b/src/renderer/components/editors-toolbar-button.tsx @@ -48,7 +48,14 @@ export class MaximizeButton extends ToolbarButton { mosaicActions.expand(this.context.mosaicWindowActions.getPath()); }; - return - - - - -
- Editor -
-
-
-
-
-
- Main Process (main.js) -
-
-
-

- Main Process (main.js) -

- - - - application - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Renderer Process (renderer.js) -
-
-
-
- - -
-
-
-
- Editor -
-
-
-
-
-
- Renderer Process (renderer.js) -
-
-
-

- Renderer Process (renderer.js) -

- - - - application - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- HTML (index.html) -
-
-
-
- - -
-
-
-
- Editor -
-
-
-
-
-
- HTML (index.html) -
-
-
-

- HTML (index.html) -

- - - - application - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Preload (preload.js) -
-
-
-
- - -
-
-
-
- Editor -
-
-
-
-
-
- Preload (preload.js) -
-
-
-

- Preload (preload.js) -

- - - - application - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stylesheet (styles.css) -
-
-
-
- - -
-
-
-
- Editor -
-
-
-
-
-
- Stylesheet (styles.css) -
-
-
-

- Stylesheet (styles.css) -

- - - - application - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`; - -exports[`Editors component renders a toolbar 1`] = ` -
-
-
- main.js -
-
-
-
- - -
-
-`; diff --git a/tests/renderer/components/__snapshots__/editors-toolbar-button-spec.tsx.snap b/tests/renderer/components/__snapshots__/editors-toolbar-button-spec.tsx.snap deleted file mode 100644 index 176ae8d790..0000000000 --- a/tests/renderer/components/__snapshots__/editors-toolbar-button-spec.tsx.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Editor toolbar button component MaximizeButton renders 1`] = ` - - - -`; - -exports[`Editor toolbar button component RemoveButton renders 1`] = ` - - - -`;