From e5073399a01b3c2832712c350dba88269e84efc6 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 30 Jan 2021 11:20:43 -0600 Subject: [PATCH] drop explicit state interfaces This could cause subtle bugs like the one fixed here where license was renamed to licenses. --- src/app/reducers.ts | 12 ---------- src/ble/reducers.ts | 4 ---- src/editor/reducers.ts | 3 --- src/firmware/reducers.ts | 7 ------ src/hub/reducers.ts | 4 ---- src/index.tsx | 2 +- src/licenses/LicenseDialog.tsx | 4 ++-- src/licenses/reducers.ts | 5 ----- src/licenses/sagas.test.ts | 6 ++--- src/licenses/sagas.ts | 2 +- src/lwp3-bootloader/reducers.ts | 4 ---- src/reducers.ts | 40 +++++++++++++++------------------ src/settings/reducers.ts | 6 ----- 13 files changed, 25 insertions(+), 74 deletions(-) diff --git a/src/app/reducers.ts b/src/app/reducers.ts index 9853b550..ac0c09eb 100644 --- a/src/app/reducers.ts +++ b/src/app/reducers.ts @@ -9,18 +9,6 @@ import { ServiceWorkerActionType } from '../service-worker/actions'; import { BeforeInstallPromptEvent } from '../utils/dom'; import { AppActionType } from './actions'; -export interface AppState { - readonly showSettings: boolean; - readonly showAboutDialog: boolean; - readonly showLicenseDialog: boolean; - readonly serviceWorker: ServiceWorkerRegistration | null; - readonly checkingForUpdate: boolean; - readonly updateAvailable: boolean; - readonly beforeInstallPrompt: BeforeInstallPromptEvent; - readonly promptingInstall: boolean; - readonly readyForOfflineUse: boolean; -} - const showSettings: Reducer = (state = false, action) => { switch (action.type) { case AppActionType.OpenSettings: diff --git a/src/ble/reducers.ts b/src/ble/reducers.ts index 6bc91d07..f22d0dcb 100644 --- a/src/ble/reducers.ts +++ b/src/ble/reducers.ts @@ -49,8 +49,4 @@ const connection: Reducer = ( } }; -export interface BleState { - readonly connection: BleConnectionState; -} - export default combineReducers({ connection }); diff --git a/src/editor/reducers.ts b/src/editor/reducers.ts index 4f822032..76f89da0 100644 --- a/src/editor/reducers.ts +++ b/src/editor/reducers.ts @@ -15,7 +15,4 @@ const current: Reducer = (state = null, action) } }; -export interface EditorState { - current: Ace.EditSession | null; -} export default combineReducers({ current }); diff --git a/src/firmware/reducers.ts b/src/firmware/reducers.ts index b96ca514..831b1e19 100644 --- a/src/firmware/reducers.ts +++ b/src/firmware/reducers.ts @@ -5,13 +5,6 @@ import { Reducer, combineReducers } from 'redux'; import { Action } from '../actions'; import { FlashFirmwareActionType } from './actions'; -export interface FirmwareState { - /** The firmware is being erased/flashed right now. */ - flashing: boolean; - /** The current progress (0 to 1) or null for unknown (e.g erasing) */ - progress: number | null; -} - const flashing: Reducer = (state = false, action) => { switch (action.type) { case FlashFirmwareActionType.DidStart: diff --git a/src/hub/reducers.ts b/src/hub/reducers.ts index 838902d5..210ac87e 100644 --- a/src/hub/reducers.ts +++ b/src/hub/reducers.ts @@ -70,8 +70,4 @@ const runtime: Reducer = ( } }; -export interface HubState { - readonly runtime: HubRuntimeState; -} - export default combineReducers({ runtime }); diff --git a/src/index.tsx b/src/index.tsx index c00908f7..b1158128 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,7 +13,7 @@ import './index.scss'; import App from './app/App'; import { i18nManager } from './i18n'; import * as I18nToaster from './notifications/I18nToaster'; -import rootReducer from './reducers'; +import { rootReducer } from './reducers'; import reportWebVitals from './reportWebVitals'; import rootSaga from './sagas'; import { didSucceed, didUpdate } from './service-worker/actions'; diff --git a/src/licenses/LicenseDialog.tsx b/src/licenses/LicenseDialog.tsx index 7f879d1a..cf442a79 100644 --- a/src/licenses/LicenseDialog.tsx +++ b/src/licenses/LicenseDialog.tsx @@ -147,8 +147,8 @@ class LicenseDialog extends React.Component { const mapStateToProps = (state: RootState): StateProps => ({ showLicenseDialog: state.app.showLicenseDialog, - licenseList: state.license.list, - licenseInfo: state.license.selected, + licenseList: state.licenses.list, + licenseInfo: state.licenses.selected, }); const mapDispatchToProps: DispatchProps = { diff --git a/src/licenses/reducers.ts b/src/licenses/reducers.ts index 8b508c69..46501a8d 100644 --- a/src/licenses/reducers.ts +++ b/src/licenses/reducers.ts @@ -15,11 +15,6 @@ export interface LicenseInfo { export type LicenseList = LicenseInfo[]; -export interface LicenseState { - readonly list: LicenseList | null; - readonly selected: LicenseInfo | null; -} - const list: Reducer = (state = null, action) => { switch (action.type) { case LicenseActionType.DidFetchList: diff --git a/src/licenses/sagas.test.ts b/src/licenses/sagas.test.ts index c418a452..891f9efe 100644 --- a/src/licenses/sagas.test.ts +++ b/src/licenses/sagas.test.ts @@ -16,7 +16,7 @@ afterAll(() => { describe('fetchLicenses', () => { test('first call', async () => { const testLicenseList: LicenseList = []; - const saga = new AsyncSaga(license, { license: { list: null } }); + const saga = new AsyncSaga(license, { licenses: { list: null } }); jest.spyOn(globalThis, 'fetch').mockResolvedValue( new Response(JSON.stringify(testLicenseList)), @@ -33,7 +33,7 @@ describe('fetchLicenses', () => { }); test('second call', async () => { const testLicenseList: LicenseList = []; - const saga = new AsyncSaga(license, { license: { list: testLicenseList } }); + const saga = new AsyncSaga(license, { licenses: { list: testLicenseList } }); jest.spyOn(globalThis, 'fetch').mockRejectedValue( 'fetch () should not have been called', @@ -50,7 +50,7 @@ describe('fetchLicenses', () => { }); test('failed fetch', async () => { const failResponse = new Response(undefined, { status: 404 }); - const saga = new AsyncSaga(license, { license: { list: null } }); + const saga = new AsyncSaga(license, { licenses: { list: null } }); jest.spyOn(globalThis, 'fetch').mockResolvedValue(failResponse); diff --git a/src/licenses/sagas.ts b/src/licenses/sagas.ts index f36bab02..35854885 100644 --- a/src/licenses/sagas.ts +++ b/src/licenses/sagas.ts @@ -7,7 +7,7 @@ import { RootState } from '../reducers'; import { didFailToFetchList, didFetchList } from './actions'; function* fetchLicenses(): Generator { - const licenses = yield* select((s: RootState) => s.license.list); + const licenses = yield* select((s: RootState) => s.licenses.list); // if we already have license list, nothing to do if (licenses !== null) { diff --git a/src/lwp3-bootloader/reducers.ts b/src/lwp3-bootloader/reducers.ts index 2b20ead7..39edec53 100644 --- a/src/lwp3-bootloader/reducers.ts +++ b/src/lwp3-bootloader/reducers.ts @@ -47,8 +47,4 @@ const connection: Reducer = ( } }; -export interface BootloaderState { - readonly connection: BootloaderConnectionState; -} - export default combineReducers({ connection }); diff --git a/src/reducers.ts b/src/reducers.ts index 88da0fa5..04c5f38d 100644 --- a/src/reducers.ts +++ b/src/reducers.ts @@ -1,31 +1,20 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2021 The Pybricks Authors -import { combineReducers } from 'redux'; -import app, { AppState } from './app/reducers'; -import ble, { BleState } from './ble/reducers'; -import editor, { EditorState } from './editor/reducers'; -import firmware, { FirmwareState } from './firmware/reducers'; -import hub, { HubState } from './hub/reducers'; -import licenses, { LicenseState } from './licenses/reducers'; -import bootloader, { BootloaderState } from './lwp3-bootloader/reducers'; -import settings, { SettingsState } from './settings/reducers'; +import { Reducer, combineReducers } from 'redux'; +import app from './app/reducers'; +import ble from './ble/reducers'; +import editor from './editor/reducers'; +import firmware from './firmware/reducers'; +import hub from './hub/reducers'; +import licenses from './licenses/reducers'; +import bootloader from './lwp3-bootloader/reducers'; +import settings from './settings/reducers'; /** - * Root state for redux store. + * Root reducer for redux store. */ -export interface RootState { - readonly app: AppState; - readonly bootloader: BootloaderState; - readonly ble: BleState; - readonly editor: EditorState; - readonly firmware: FirmwareState; - readonly hub: HubState; - readonly license: LicenseState; - readonly settings: SettingsState; -} - -export default combineReducers({ +export const rootReducer = combineReducers({ app, bootloader, ble, @@ -35,3 +24,10 @@ export default combineReducers({ licenses, settings, }); + +/** + * Root state for redux store. + */ +type StateFromReducer = R extends Reducer ? S : never; + +export type RootState = StateFromReducer; diff --git a/src/settings/reducers.ts b/src/settings/reducers.ts index 1a575719..2fb1ba05 100644 --- a/src/settings/reducers.ts +++ b/src/settings/reducers.ts @@ -6,12 +6,6 @@ import { Action } from '../actions'; import { SettingsActionType } from './actions'; import { SettingId, getDefaultBooleanValue } from './defaults'; -export interface SettingsState { - readonly darkMode: boolean; - readonly showDocs: boolean; - readonly flashCurrentProgram: boolean; -} - const darkMode: Reducer = ( state = getDefaultBooleanValue(SettingId.DarkMode), action,