From 1f38b720a5ff85dc2ce6a094d450e8480e215c74 Mon Sep 17 00:00:00 2001 From: Erik Moura Date: Sat, 3 Aug 2024 17:19:57 -0300 Subject: [PATCH 1/2] test: move `Editors` test to `rtl-spec` --- .../components/editors.spec.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename tests/renderer/components/editors-spec.tsx => rtl-spec/components/editors.spec.tsx (94%) diff --git a/tests/renderer/components/editors-spec.tsx b/rtl-spec/components/editors.spec.tsx similarity index 94% rename from tests/renderer/components/editors-spec.tsx rename to rtl-spec/components/editors.spec.tsx index 81f3b8809c..56c482f0ae 100644 --- a/tests/renderer/components/editors-spec.tsx +++ b/rtl-spec/components/editors.spec.tsx @@ -3,20 +3,20 @@ import * as React from 'react'; 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'; -jest.mock('../../../src/renderer/components/editor', () => ({ +jest.mock('../../src/renderer/components/editor', () => ({ Editor: () => 'Editor', })); From 9449eece2f23ecd4f408012fc239fdf472ccc10e Mon Sep 17 00:00:00 2001 From: Erik Moura Date: Sat, 3 Aug 2024 23:25:34 -0300 Subject: [PATCH 2/2] test: migrate `Editors` tests to RTL --- rtl-spec/components/editors.spec.tsx | 86 +- src/renderer/components/TestIdContainer.tsx | 18 + src/renderer/components/editors.tsx | 19 +- src/renderer/components/tour-welcome.tsx | 8 +- .../__snapshots__/editors-spec.tsx.snap | 956 ------------------ 5 files changed, 77 insertions(+), 1010 deletions(-) create mode 100644 src/renderer/components/TestIdContainer.tsx delete mode 100644 tests/renderer/components/__snapshots__/editors-spec.tsx.snap 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.tsx b/src/renderer/components/editors.tsx index 07be780a27..c1cb0a7a6f 100644 --- a/src/renderer/components/editors.tsx +++ b/src/renderer/components/editors.tsx @@ -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'; @@ -191,7 +192,7 @@ export const Editors = observer( const { appState } = this.props; return ( -
+
{/* Left */}
{title}
@@ -258,13 +259,15 @@ export const Editors = observer( const { editorMosaic } = this.props.appState; return ( - - className={`focused__${this.state.focused}`} - onChange={this.onChange} - value={editorMosaic.mosaic} - zeroStateView={renderNonIdealState(editorMosaic)} - renderTile={this.renderTile} - /> + + + className={`focused__${this.state.focused}`} + onChange={this.onChange} + value={editorMosaic.mosaic} + zeroStateView={renderNonIdealState(editorMosaic)} + renderTile={this.renderTile} + /> + ); } diff --git a/src/renderer/components/tour-welcome.tsx b/src/renderer/components/tour-welcome.tsx index 22b79bdf85..f36a7e7cf5 100644 --- a/src/renderer/components/tour-welcome.tsx +++ b/src/renderer/components/tour-welcome.tsx @@ -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'; @@ -238,10 +239,7 @@ export const WelcomeTour = observer( if (!isTourStarted) { return ( -
+

🙋‍ Hey There!

@@ -260,7 +258,7 @@ export const WelcomeTour = observer( {this.buttons}
-
+ ); } else { diff --git a/tests/renderer/components/__snapshots__/editors-spec.tsx.snap b/tests/renderer/components/__snapshots__/editors-spec.tsx.snap deleted file mode 100644 index b4cb240441..0000000000 --- a/tests/renderer/components/__snapshots__/editors-spec.tsx.snap +++ /dev/null @@ -1,956 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Editors component renders 1`] = ` -
-
-
-
-
-
-
-
- Main Process (main.js) -
-
-
-
- - -
-
-
-
- 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 -
-
-
-
- - -
-
-`;