From 6e54dd64937ee9b9f4f78e79f074e3011220e03b Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Thu, 24 Sep 2020 16:40:14 -0700 Subject: [PATCH] refactor: clean up gist action states (#491) * refactor: clean up gist action states * fix: tweak small state edge case --- src/interfaces.ts | 7 ++ .../components/commands-action-button.tsx | 35 ++++---- .../components/commands-address-bar.tsx | 6 +- src/renderer/state.ts | 3 +- .../commands-address-bar-spec.tsx.snap | 4 +- .../commands-publish-button-spec.tsx.snap | 4 +- .../components/commands-address-bar-spec.tsx | 55 +++++++++++-- .../commands-publish-button-spec.tsx | 80 ++++++++++++++++--- 8 files changed, 159 insertions(+), 35 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 7b4ba24510..331459ddf9 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -22,6 +22,13 @@ export enum GistActionType { delete = 'Delete', } +export enum GistActionState { + publishing = 'publishing', + updating = 'updating', + deleting = 'deleting', + none = 'none', +} + export interface Version { version: string; name?: string; diff --git a/src/renderer/components/commands-action-button.tsx b/src/renderer/components/commands-action-button.tsx index 624b10b69a..d56131c029 100644 --- a/src/renderer/components/commands-action-button.tsx +++ b/src/renderer/components/commands-action-button.tsx @@ -12,7 +12,11 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { when } from 'mobx'; -import { EditorValues, GistActionType } from '../../interfaces'; +import { + EditorValues, + GistActionState, + GistActionType, +} from '../../interfaces'; import { IpcEvents } from '../../ipc-events'; import { INDEX_HTML_NAME, @@ -112,7 +116,7 @@ export class GistActionButton extends React.Component< const options = { includeDependencies: true, includeElectron: true }; const values = await window.ElectronFiddle.app.getEditorValues(options); - appState.isPublishing = true; + appState.activeGistAction = GistActionState.publishing; try { const gist = await octo.gists.create({ @@ -126,6 +130,9 @@ export class GistActionButton extends React.Component< console.log(`Publish Button: Publishing complete`, { gist }); this.renderToast({ message: 'Publishing completed successfully!' }); + + // Only set action type to update if publish completed successfully. + this.setActionType(GistActionType.update); } catch (error) { console.warn(`Could not publish gist`, { error }); @@ -138,8 +145,7 @@ export class GistActionButton extends React.Component< ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions); } - appState.isPublishing = false; - this.setActionType(GistActionType.update); + appState.activeGistAction = GistActionState.none; } /** @@ -151,7 +157,7 @@ export class GistActionButton extends React.Component< const options = { includeDependencies: true, includeElectron: true }; const values = await window.ElectronFiddle.app.getEditorValues(options); - this.setState({ isUpdating: true }); + appState.activeGistAction = GistActionState.updating; try { const gist = await octo.gists.update({ @@ -173,7 +179,7 @@ export class GistActionButton extends React.Component< ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions); } - this.setState({ isUpdating: false }); + appState.activeGistAction = GistActionState.none; this.setActionType(GistActionType.update); } @@ -184,7 +190,8 @@ export class GistActionButton extends React.Component< const { appState } = this.props; const octo = await getOctokit(this.props.appState); - this.setState({ isDeleting: true }); + appState.activeGistAction = GistActionState.deleting; + try { const gist = await octo.gists.delete({ gist_id: appState.gistId!, @@ -205,7 +212,7 @@ export class GistActionButton extends React.Component< } appState.gistId = undefined; - this.setState({ isDeleting: false }); + appState.activeGistAction = GistActionState.none; this.setActionType(GistActionType.publish); } @@ -253,18 +260,18 @@ export class GistActionButton extends React.Component< } public render() { - const { isPublishing, gistId } = this.props.appState; - const { isDeleting, isUpdating, actionType } = this.state; + const { gistId, activeGistAction } = this.props.appState; + const { actionType } = this.state; const getTextForButton = () => { let text; if (gistId) { text = actionType; - } else if (isUpdating) { + } else if (activeGistAction === GistActionState.updating) { text = 'Updating...'; - } else if (isPublishing) { + } else if (activeGistAction === GistActionState.publishing) { text = 'Publishing...'; - } else if (isDeleting) { + } else if (activeGistAction === GistActionState.deleting) { text = 'Deleting...'; } else { text = 'Publish'; @@ -283,7 +290,7 @@ export class GistActionButton extends React.Component< } }; - const isPerformingAction = isPublishing || isUpdating || isDeleting; + const isPerformingAction = activeGistAction !== GistActionState.none; return ( <>
diff --git a/src/renderer/components/commands-address-bar.tsx b/src/renderer/components/commands-address-bar.tsx index 6a698f2e03..b90344ec9c 100644 --- a/src/renderer/components/commands-address-bar.tsx +++ b/src/renderer/components/commands-address-bar.tsx @@ -5,6 +5,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { IpcEvents } from '../../ipc-events'; +import { GistActionState } from '../../interfaces'; import { idFromUrl, urlFromId } from '../../utils/gist'; import { ipcRendererManager } from '../ipc'; import { AppState } from '../state'; @@ -121,14 +122,15 @@ export class AddressBar extends React.Component< } public render() { - const { isUnsaved, isPublishing } = this.props.appState; + const { isUnsaved, activeGistAction } = this.props.appState; const { value } = this.state; const isCorrect = /https:\/\/gist\.github\.com\/(.+)$/.test(value); const className = classnames('address-bar', isUnsaved, { empty: !value }); + const isPerformingAction = activeGistAction !== GistActionState.none; return (
-
+
-
+
diff --git a/tests/renderer/components/commands-address-bar-spec.tsx b/tests/renderer/components/commands-address-bar-spec.tsx index 5b57d52290..992459a94e 100644 --- a/tests/renderer/components/commands-address-bar-spec.tsx +++ b/tests/renderer/components/commands-address-bar-spec.tsx @@ -5,6 +5,7 @@ import { observable } from 'mobx'; import { AddressBar } from '../../../src/renderer/components/commands-address-bar'; import { ElectronFiddleMock } from '../../mocks/electron-fiddle'; import { MockState } from '../../mocks/state'; +import { GistActionState } from '../../../src/interfaces'; jest.mock('../../../src/utils/octokit'); @@ -68,12 +69,54 @@ describe('AddressBar component', () => { it('disables during gist publishing', async () => { const wrapper = shallow(); - wrapper.setProps({ appState: { ...store, isPublishing: true } }, () => { - expect(wrapper.find('fieldset').prop('disabled')).toBe(true); - }); + wrapper.setProps( + { appState: { ...store, activeGistAction: GistActionState.publishing } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(true); + }, + ); + + wrapper.setProps( + { appState: { ...store, activeGistAction: GistActionState.none } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + }, + ); + }); + + it('disables during gist updating', async () => { + const wrapper = shallow(); + + wrapper.setProps( + { appState: { ...store, activeGistAction: GistActionState.updating } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(true); + }, + ); + + wrapper.setProps( + { appState: { ...store, activeGistAction: GistActionState.none } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + }, + ); + }); + + it('disables during gist deleting', async () => { + const wrapper = shallow(); - wrapper.setProps({ appState: { ...store, isPublishing: false } }, () => { - expect(wrapper.find('fieldset').prop('disabled')).toBe(false); - }); + wrapper.setProps( + { appState: { ...store, activeGistAction: GistActionState.deleting } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(true); + }, + ); + + wrapper.setProps( + { appState: { ...store, activeGistAction: GistActionState.none } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + }, + ); }); }); diff --git a/tests/renderer/components/commands-publish-button-spec.tsx b/tests/renderer/components/commands-publish-button-spec.tsx index 1abcb65af9..890100cc59 100644 --- a/tests/renderer/components/commands-publish-button-spec.tsx +++ b/tests/renderer/components/commands-publish-button-spec.tsx @@ -1,6 +1,6 @@ import { shallow } from 'enzyme'; import * as React from 'react'; -import { GistActionType } from '../../../src/interfaces'; +import { GistActionState, GistActionType } from '../../../src/interfaces'; import { GistActionButton } from '../../../src/renderer/components/commands-action-button'; import { getOctokit } from '../../../src/utils/octokit'; @@ -182,7 +182,7 @@ describe('Publish button component', () => { await instance.performGistAction(); - expect(store.isPublishing).toBe(false); + expect(store.activeGistAction).toBe(GistActionState.none); }); it('uses the privacy setting correctly', async () => { @@ -218,7 +218,7 @@ describe('Publish button component', () => { }); it('disables during gist publishing', async () => { - store.isPublishing = false; + store.activeGistAction = GistActionState.none; const wrapper = shallow(); const instance: GistActionButton = wrapper.instance() as any; @@ -226,12 +226,74 @@ describe('Publish button component', () => { instance.performGistAction = jest.fn().mockImplementationOnce(() => { return new Promise((resolve) => { - wrapper.setProps({ appState: { store, isPublishing: true } }, () => { - expect(wrapper.find('fieldset').prop('disabled')).toBe(true); - }); - wrapper.setProps({ appState: { store, isPublishing: false } }, () => { - expect(wrapper.find('fieldset').prop('disabled')).toBe(false); - }); + wrapper.setProps( + { appState: { store, activeGistAction: GistActionState.publishing } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(true); + }, + ); + wrapper.setProps( + { appState: { store, activeGistAction: GistActionState.none } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + }, + ); + resolve(); + }); + }); + + await instance.performGistAction(); + }); + + it('disables during gist updating', async () => { + store.activeGistAction = GistActionState.none; + const wrapper = shallow(); + const instance: GistActionButton = wrapper.instance() as any; + + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + + instance.performGistAction = jest.fn().mockImplementationOnce(() => { + return new Promise((resolve) => { + wrapper.setProps( + { appState: { store, activeGistAction: GistActionState.updating } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(true); + }, + ); + wrapper.setProps( + { appState: { store, activeGistAction: GistActionState.none } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + }, + ); + resolve(); + }); + }); + + await instance.performGistAction(); + }); + + it('disables during gist deleting', async () => { + store.activeGistAction = GistActionState.none; + const wrapper = shallow(); + const instance: GistActionButton = wrapper.instance() as any; + + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + + instance.performGistAction = jest.fn().mockImplementationOnce(() => { + return new Promise((resolve) => { + wrapper.setProps( + { appState: { store, activeGistAction: GistActionState.deleting } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(true); + }, + ); + wrapper.setProps( + { appState: { store, activeGistAction: GistActionState.none } }, + () => { + expect(wrapper.find('fieldset').prop('disabled')).toBe(false); + }, + ); resolve(); }); });