diff --git a/rtl-spec/components/tour-welcome.spec.tsx b/rtl-spec/components/tour-welcome.spec.tsx new file mode 100644 index 0000000000..5e00d3da8f --- /dev/null +++ b/rtl-spec/components/tour-welcome.spec.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; + +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { + WelcomeTour, + getWelcomeTour, +} from '../../src/renderer/components/tour-welcome'; +import { AppState } from '../../src/renderer/state'; + +describe('Header component', () => { + let appState: AppState; + + function renderWelcomeTour(appState: AppState) { + return render(); + } + + beforeEach(() => { + ({ state: appState } = window.app); + appState.isTourShowing = true; + }); + + it('renders', () => { + const { getByTestId } = renderWelcomeTour(appState); + expect(getByTestId('welcome-tour-dialog')).toBeInTheDocument(); + }); + + it('does not render if the tour is not showing', () => { + appState.isTourShowing = false; + + const { queryByTestId } = renderWelcomeTour(appState); + expect(queryByTestId('welcome-tour-dialog')).not.toBeInTheDocument(); + }); + + it('renders the tour once started', async () => { + const { getByText, getByTestId } = renderWelcomeTour(appState); + + await userEvent.click(getByText(/show me around/i)); + + expect(getByTestId('tour')).toBeInTheDocument(); + }); + + it('stops the tour on stopTour()', async () => { + (appState.disableTour as jest.Mock).mockImplementation( + () => (appState.isTourShowing = false), + ); + + const { getByText, queryByTestId } = renderWelcomeTour(appState); + + await userEvent.click(getByText(/show me around/i)); + await userEvent.click(getByText(/stop tour/i)); + + expect(appState.isTourShowing).toBe(false); + + expect(queryByTestId('tour')).not.toBeInTheDocument(); + + expect(appState.disableTour).toHaveBeenCalled(); + }); + + describe('getWelcomeTour()', () => { + it('offers custom buttons for the Electron step', async () => { + const tourSteps = [...getWelcomeTour()]; + const electronStep = tourSteps.find( + ({ name }) => name === 'first-time-electron', + ); + const mockParam = { stop: jest.fn(), advance: jest.fn() }; + const buttons = electronStep!.getButtons!(mockParam); + + const renderResultFirstButton = render(buttons[0]); + await userEvent.click(renderResultFirstButton.getByText(/i'm good!/i)); + + expect(mockParam.stop).toHaveBeenCalled(); + + const renderResultSecondButton = render(buttons[1]); + await userEvent.click( + renderResultSecondButton.getByText(/electron basics/i), + ); + + expect(mockParam.advance).toHaveBeenCalled(); + }); + }); +}); diff --git a/rtl-spec/components/tour.spec.tsx b/rtl-spec/components/tour.spec.tsx new file mode 100644 index 0000000000..07975c61c8 --- /dev/null +++ b/rtl-spec/components/tour.spec.tsx @@ -0,0 +1,120 @@ +import * as React from 'react'; + +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { mocked } from 'jest-mock'; + +import { Tour, TourScriptStep } from '../../src/renderer/components/tour'; +import { overrideRendererPlatform } from '../../tests/utils'; + +describe('Tour component', () => { + const oldQuerySelector = document.querySelector; + + /** + * This is a function to ensure different object references are returned every + * time. The test for the `getButtons` method mutates the mock tour and that + * would interfere with other tests if the same object were reused. + */ + const getMockTour = () => + new Set([ + { + name: 'mock-step-1', + selector: 'div.mock-1', + title: 'Step 1', + content: mock-step-1, + }, + { + name: 'mock-step-2', + selector: 'div.mock-2', + title: 'Step 2', + content: mock-step-2, + }, + ]); + + const mockStop = jest.fn(); + + function renderTour(mockTour = getMockTour()) { + return render(); + } + + beforeEach(() => { + overrideRendererPlatform('darwin'); + + document.querySelector = jest.fn(() => ({ + getBoundingClientRect: jest.fn(() => ({ + top: 20, + left: 25, + height: 120, + width: 130, + })), + })); + }); + + afterEach(() => { + document.querySelector = oldQuerySelector; + }); + + it('renders', () => { + const { getByTestId } = renderTour(); + + expect(getByTestId('tour')).toBeInTheDocument(); + }); + + it('renders supplied buttons', () => { + const mockTour = getMockTour(); + + mockTour.forEach((item) => { + (item as any).getButtons = () => []; + }); + + const { getByText } = renderTour(mockTour); + + expect(getByText(/hello/i)).toBeInTheDocument(); + }); + + it(`renders the "Finish Tour" button at the end and closes the tour when it's clicked`, async () => { + const singleItemTour = new Set([ + { + name: 'mock-step-1', + selector: 'div.mock-1', + title: 'Step 1', + content: mock-step-1, + }, + ]); + + const { getByText } = renderTour(singleItemTour); + const finishTourButton = getByText(/finish tour/i); + + expect(finishTourButton).toBeInTheDocument(); + + await userEvent.click(finishTourButton); + + expect(mockStop).toHaveBeenCalled(); + }); + + it('handles a missing target', () => { + mocked(document.querySelector).mockReturnValueOnce(null); + + const { container } = renderTour(); + + expect(container.querySelectorAll('svg')).toHaveLength(1); + }); + + it('stops on stop()', async () => { + const { getByText } = renderTour(); + + await userEvent.click(getByText(/stop tour/i)); + + expect(mockStop).toHaveBeenCalled(); + }); + + it('advances on advance()', async () => { + const { getByText } = renderTour(); + + await userEvent.click(getByText(/continue/i)); + + const mockTour = [...getMockTour()]; + + expect(getByText(mockTour[1].title)).toBeInTheDocument(); + }); +}); diff --git a/src/renderer/components/tour-welcome.tsx b/src/renderer/components/tour-welcome.tsx index 26cc56c750..22b79bdf85 100644 --- a/src/renderer/components/tour-welcome.tsx +++ b/src/renderer/components/tour-welcome.tsx @@ -238,22 +238,27 @@ export const WelcomeTour = observer( if (!isTourStarted) { return ( -
-

πŸ™‹β€ Hey There!

-
-
-

- Welcome to Electron Fiddle! If you're new to the app, - we'd like to give you a brief tour of its features. -

-

- We won't show this dialog again, but you can always find - the tour in the Help menu. -

-
-
-
- {this.buttons} +
+
+

πŸ™‹β€ Hey There!

+
+
+

+ Welcome to Electron Fiddle! If you're new to the app, + we'd like to give you a brief tour of its features. +

+

+ We won't show this dialog again, but you can always find + the tour in the Help menu. +

+
+
+
+ {this.buttons} +
diff --git a/src/renderer/components/tour.tsx b/src/renderer/components/tour.tsx index 274f9346e6..978e4d40d9 100644 --- a/src/renderer/components/tour.tsx +++ b/src/renderer/components/tour.tsx @@ -93,7 +93,11 @@ export class Tour extends React.Component { if (!step) return null; - return
{this.getStep(step)}
; + return ( +
+ {this.getStep(step)} +
+ ); } /** diff --git a/tests/renderer/components/__snapshots__/tour-spec.tsx.snap b/tests/renderer/components/__snapshots__/tour-spec.tsx.snap deleted file mode 100644 index 0e8dbb7986..0000000000 --- a/tests/renderer/components/__snapshots__/tour-spec.tsx.snap +++ /dev/null @@ -1,108 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VersionChooser component renders 1`] = ` -
- -
-

- Step 1 -

-
-
- - mock-step-1 - -
-
-
- - -
-
-
- - - - - - - -
-`; diff --git a/tests/renderer/components/__snapshots__/tour-welcome-spec.tsx.snap b/tests/renderer/components/__snapshots__/tour-welcome-spec.tsx.snap deleted file mode 100644 index bf0189888e..0000000000 --- a/tests/renderer/components/__snapshots__/tour-welcome-spec.tsx.snap +++ /dev/null @@ -1,197 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Header component renders 1`] = ` - -
-

- πŸ™‹β€ Hey There! -

-
-
-

- Welcome to Electron Fiddle! If you're new to the app, we'd like to give you a brief tour of its features. -

-

- We won't show this dialog again, but you can always find the tour in the Help menu. -

-
-
-
- - -
-
-
-`; - -exports[`Header component renders the tour once started 1`] = ` - -

- Electron Fiddle allows you to build little experiments and mini-apps with Electron. Each Fiddle has at least three of these files: A main script, a renderer script, a preload script, and an HTML file. -

-

- If you - - require() - - a module, Fiddle will install it automatically. It will also automatically provide you with autocomplete information for the - - electron - - module. -

- , - "name": "fiddle-editors", - "selector": "div.mosaic-root", - "title": "πŸ“ Fiddle Editors", - }, - { - "content": -

- Electron Fiddle knows about all released Electron versions, downloading your versions automatically in the background. -

-

- Open the preferences to see all available versions and delete those previously downloaded. -

-
, - "name": "select-versions", - "selector": "#version-chooser", - "title": "πŸ“‡ Choose an Electron Version", - }, - { - "content":

- Hit this button to give your Fiddle a try and start it. -

, - "name": "button-run", - "selector": "#button-run", - "title": "πŸš€ Run Your Fiddle", - }, - { - "content": -

- Like what you've built? You can save your Fiddle as a public GitHub Gist, allowing other users to load it by pasting the URL into the address bar. If they don't have Electron Fiddle, they can see and download your code directly from GitHub. -

-

- You can also package your Fiddle as a standalone binary or as an installer from the "Tasks" menu. -

-
, - "name": "button-action", - "selector": "#button-action", - "title": "πŸ—Ί Share Your Fiddle", - }, - { - "content":

- We've finished our tour of Electron Fiddle, but if this is your first time using Electron, we could introduce you to its basics. Interested? -

, - "getButtons": [Function], - "name": "first-time-electron", - "selector": "div.mosaic-root", - "title": "πŸ‘‹ Getting Started With Electron?", - }, - { - "content": -

- Every Electron app starts with a main script, very similar to how a Node.js application is started. The main script runs in the "main process". To display a user interface, the main process creates renderer processes – usually in the form of windows, which Electron calls Β  - - BrowserWindow - - . -

-

- To get started, pretend that the main process is just like a Node.js process. All APIs and features found in Electron are accessible through the - - electron - - module, which can be required like any other Node.js module. -

-

- The default fiddle creates a new - - BrowserWindow - - and loads an HTML file. -

-
, - "name": "main-editor", - "selector": "div.mosaic-window.main\\.js", - "title": "πŸ“ Main Script", - }, - { - "content":

- In the default fiddle, this HTML file is loaded in the Β  - - BrowserWindow - - . Any HTML, CSS, or JavaScript that works in a browser will work here, too. In addition, Electron allows you to execute Node.js code. Take a close look at the Β  - - <script /> - - tag and notice how we can call - - - require() - - like we would in Node.js. -

, - "name": "html-editor", - "selector": "div.mosaic-window.index\\.html", - "title": "πŸ“ HTML", - }, - { - "content": -

- This is the script we just required from the HTML file. In here, you can do anything that works in Node.js - - and - - anything that works in a browser. -

-

- By the way: If you want to use an - - npm - - module here, just Β  - - require - - it. Electron Fiddle will automatically detect that you requested a module and install it as soon as you run your fiddle. -

-
, - "name": "renderer-editor", - "selector": "div.mosaic-window.renderer\\.js", - "title": "πŸ“ Renderer Script", - }, - } - } -/> -`; diff --git a/tests/renderer/components/tour-spec.tsx b/tests/renderer/components/tour-spec.tsx deleted file mode 100644 index d2c619ca78..0000000000 --- a/tests/renderer/components/tour-spec.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import * as React from 'react'; - -import { mount, shallow } from 'enzyme'; -import { mocked } from 'jest-mock'; - -import { Tour } from '../../../src/renderer/components/tour'; -import { overrideRendererPlatform } from '../../utils'; - -describe('VersionChooser component', () => { - const oldQuerySelector = document.querySelector; - const mockTour = new Set([ - { - name: 'mock-step-1', - selector: 'div.mock-1', - title: 'Step 1', - content: mock-step-1, - }, - { - name: 'mock-step-2', - selector: 'div.mock-2', - title: 'Step 2', - content: mock-step-2, - }, - ]); - - beforeEach(() => { - overrideRendererPlatform('darwin'); - - document.querySelector = jest.fn(() => ({ - getBoundingClientRect: jest.fn(() => ({ - top: 20, - left: 25, - height: 120, - width: 130, - })), - })); - }); - - afterEach(() => { - document.querySelector = oldQuerySelector; - }); - - it('renders', () => { - const mockStop = jest.fn(); - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); - }); - - it('renders supplied buttons', () => { - mockTour.forEach((item) => { - (item as any).getButtons = () => []; - }); - - const mockStop = jest.fn(); - const wrapper = shallow(); - - expect(wrapper.find('button').text()).toBe('Hello'); - }); - - it('renders "Finish Tour" at the end', () => { - const singleItemTour = new Set([ - { - name: 'mock-step-1', - selector: 'div.mock-1', - title: 'Step 1', - content: mock-step-1, - }, - ]); - - const mockStop = jest.fn(); - const wrapper = mount(); - - expect(wrapper.find('button').text()).toBe('tick-circleFinish Tour'); - }); - - it('handles a missing target', () => { - mocked(document.querySelector).mockReturnValueOnce(null); - - const mockStop = jest.fn(); - const wrapper = shallow(); - - expect(wrapper.find('svg').length).toBe(1); - }); - - it('stops on stop()', () => { - const mockStop = jest.fn(); - const wrapper = shallow(); - const instance: any = wrapper.instance(); - - instance.stop(); - - expect(mockStop).toHaveBeenCalled(); - }); - - it('advances on advance()', () => { - const mockStop = jest.fn(); - const wrapper = shallow(); - const instance: any = wrapper.instance(); - - instance.advance(); - expect((wrapper.state('step') as any).name).toBe('mock-step-2'); - - instance.advance(); - expect(wrapper.state('step')).toBe(null); - }); - - it('handles a resize', () => { - jest.useFakeTimers(); - - const mockStop = jest.fn(); - const wrapper = shallow(); - const instance: any = wrapper.instance(); - - instance.forceUpdate = jest.fn(); - instance.onResize(); - - expect(instance.resizeHandle).toBeTruthy(); - jest.runAllTimers(); - - expect(instance.forceUpdate).toHaveBeenCalled(); - }); - - it('removes the resize listener', () => { - const oldRemove = window.removeEventListener; - window.removeEventListener = jest.fn(); - - const mockStop = jest.fn(); - const wrapper = shallow(); - const instance: any = wrapper.instance() as any; - - instance.componentWillUnmount(); - expect(window.removeEventListener).toHaveBeenCalled(); - - window.removeEventListener = oldRemove; - }); -}); diff --git a/tests/renderer/components/tour-welcome-spec.tsx b/tests/renderer/components/tour-welcome-spec.tsx deleted file mode 100644 index e11b9cbf21..0000000000 --- a/tests/renderer/components/tour-welcome-spec.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as React from 'react'; - -import { shallow } from 'enzyme'; - -import { - WelcomeTour, - getWelcomeTour, -} from '../../../src/renderer/components/tour-welcome'; -import { AppState } from '../../../src/renderer/state'; - -describe('Header component', () => { - let store: AppState; - - beforeEach(() => { - ({ state: store } = window.app); - store.isTourShowing = true; - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders null if the tour is not showing', () => { - store.isTourShowing = false; - - const wrapper = shallow(); - expect(wrapper.html()).toBe(null); - }); - - it('renders the tour once started', () => { - const wrapper = shallow(); - const instance: any = wrapper.instance(); - - instance.startTour(); - - expect(wrapper.state('isTourStarted')).toBe(true); - expect(wrapper).toMatchSnapshot(); - }); - - it('stops the tour on stopTour()', () => { - const wrapper = shallow(); - const instance: any = wrapper.instance(); - - instance.stopTour(); - - expect(wrapper.state('isTourStarted')).toBe(false); - expect(store.disableTour).toHaveBeenCalled(); - }); - - describe('getWelcomeTour()', () => { - it('offers custom buttons for the Electron step', () => { - const tourSteps = [...getWelcomeTour()]; - const electronStep = tourSteps.find( - ({ name }) => name === 'first-time-electron', - ); - const mockParam = { stop: jest.fn(), advance: jest.fn() }; - const buttons = electronStep!.getButtons!(mockParam); - - shallow(buttons[0]).simulate('click'); - expect(mockParam.stop).toHaveBeenCalled(); - - shallow(buttons[1]).simulate('click'); - expect(mockParam.advance).toHaveBeenCalled(); - }); - }); -});