From 67f74caa629dc25cdbe90b033be8acf13ad2d57f Mon Sep 17 00:00:00 2001 From: Jonas Snellinckx Date: Tue, 8 Dec 2020 21:11:59 +0100 Subject: [PATCH 1/2] fix: align enhancer style with standards to wrap the middleware --- src/mainStateSyncEnhancer.ts | 29 ++++++++++++++++++---- src/rendererStateSyncEnhancer.ts | 41 ++++++++++++++++++++++---------- tests/counter.ts | 10 +++++++- tests/e2e.spec.ts | 33 +++++++++++++++++++++++++ tests/e2e/main/index.ts | 17 ++++++++++--- tests/e2e/renderer/index.ts | 23 ++++++++++++++++-- tests/middleware.ts | 8 +++++++ 7 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 tests/middleware.ts diff --git a/src/mainStateSyncEnhancer.ts b/src/mainStateSyncEnhancer.ts index 08aeb6e8..8827e87d 100644 --- a/src/mainStateSyncEnhancer.ts +++ b/src/mainStateSyncEnhancer.ts @@ -1,11 +1,18 @@ import { ipcMain, webContents } from 'electron' -import { Action, applyMiddleware, Middleware, StoreCreator, StoreEnhancer } from 'redux' +import { + Action, + compose, + Dispatch, + Middleware, + MiddlewareAPI, + StoreCreator, + StoreEnhancer, +} from 'redux' import { IPCEvents } from './constants' import { defaultMainOptions, MainStateSyncEnhancerOptions, } from './options/MainStateSyncEnhancerOptions' - import { preventDoubleInitialization, stopForwarding, validateAction } from './utils' function createMiddleware(options: MainStateSyncEnhancerOptions) { @@ -60,7 +67,21 @@ export const mainStateSyncEnhancer = (options = defaultMainOptions): StoreEnhanc ) => { preventDoubleInitialization() const middleware = createMiddleware(options) - return (reducer, state) => { - return createStore(reducer, state, applyMiddleware(middleware)) + return (reducer, preloadedState) => { + const store = createStore(reducer, preloadedState) + + let dispatch = store.dispatch + + const middlewareAPI: MiddlewareAPI> = { + getState: store.getState, + dispatch, + } + + dispatch = compose(middleware(middlewareAPI))(store.dispatch) + + return { + ...store, + dispatch, + } } } diff --git a/src/rendererStateSyncEnhancer.ts b/src/rendererStateSyncEnhancer.ts index ca49331e..ccd35194 100644 --- a/src/rendererStateSyncEnhancer.ts +++ b/src/rendererStateSyncEnhancer.ts @@ -1,10 +1,20 @@ import { ipcRenderer } from 'electron' -import { Action, applyMiddleware, Middleware, StoreCreator, StoreEnhancer } from 'redux' +import { + Action, + compose, + Dispatch, + Middleware, + MiddlewareAPI, + StoreCreator, + StoreEnhancer, +} from 'redux' import { IPCEvents } from './constants' import { fetchInitialState, fetchInitialStateAsync } from './fetchState' import { replaceState, withStoreReplacer } from './fetchState/replaceState' -import { defaultRendererOptions, RendererStateSyncEnhancerOptions } from './options/RendererStateSyncEnhancerOptions' - +import { + defaultRendererOptions, + RendererStateSyncEnhancerOptions, +} from './options/RendererStateSyncEnhancerOptions' import { preventDoubleInitialization, stopForwarding, validateAction } from './utils' const createMiddleware = (options: RendererStateSyncEnhancerOptions): Middleware => (store) => { @@ -35,11 +45,12 @@ export const rendererStateSyncEnhancer = (options = defaultRendererOptions): Sto preventDoubleInitialization() return (reducer, state) => { + const middleware = createMiddleware(options) + const initialState = options.lazyInit ? state : fetchInitialState(options) const store = createStore( options.lazyInit ? withStoreReplacer(reducer) : reducer, - initialState, - applyMiddleware(createMiddleware(options)) + initialState ) if (options.lazyInit) { @@ -48,14 +59,18 @@ export const rendererStateSyncEnhancer = (options = defaultRendererOptions): Sto }) } - // TODO: this needs some ❤️ - // XXX: TypeScript is dumb. If you return the call to createStore - // immediately it's fine, but even assigning it to a constant and returning - // will make it freak out. We fix this with the line below the return. - return store + let dispatch = store.dispatch + + const middlewareAPI: MiddlewareAPI> = { + getState: store.getState, + dispatch, + } + + dispatch = compose(middleware(middlewareAPI))(store.dispatch) - // TODO: this needs some ❤️ - // XXX: Even though this is unreachable, it fixes the type signature???? - return (store as unknown) as any + return { + ...store, + dispatch, + } } } diff --git a/tests/counter.ts b/tests/counter.ts index 92458747..8862494c 100644 --- a/tests/counter.ts +++ b/tests/counter.ts @@ -8,6 +8,7 @@ const init: CounterState = { const INCREMENT = 'INCREMENT' const DECREMENT = 'DECREMENT' +const SET_COUNT_MIDDLEWARE = 'SET_COUNT_MIDDLEWARE' type IncrementAction = { type: typeof INCREMENT @@ -17,7 +18,12 @@ type DecrementAction = { type: typeof DECREMENT } -export type Actions = IncrementAction | DecrementAction +type SetCountMiddlewareAction = { + type: typeof SET_COUNT_MIDDLEWARE + payload: number +} + +export type Actions = IncrementAction | DecrementAction | SetCountMiddlewareAction export const reducer = (state = init, action: Actions): CounterState => { switch (action.type) { @@ -25,6 +31,8 @@ export const reducer = (state = init, action: Actions): CounterState => { return { ...state, count: state.count + 1 } case DECREMENT: return { ...state, count: state.count - 1 } + case SET_COUNT_MIDDLEWARE: + return { ...state, count: action.payload } default: return state } diff --git a/tests/e2e.spec.ts b/tests/e2e.spec.ts index 40a0a11f..48f218fb 100644 --- a/tests/e2e.spec.ts +++ b/tests/e2e.spec.ts @@ -48,4 +48,37 @@ describe('End to End Tests', () => { // eslint-disable-next-line @typescript-eslint/await-thenable expect(await app.browserWindow.getTitle()).toEqual('9') }) + + it('should be able to increment value from main process', async () => { + expect(await getText('#value')).toEqual('10') + expect(await app.browserWindow.getTitle()).toEqual('10') + + await click('#mainIncrement') + + expect(await getText('#value')).toEqual('11') + // eslint-disable-next-line @typescript-eslint/await-thenable + expect(await app.browserWindow.getTitle()).toEqual('11') + }) + + it('should be able to use middleware when dispatching from renderer process', async () => { + expect(await getText('#value')).toEqual('10') + expect(await app.browserWindow.getTitle()).toEqual('10') + + await click('#setCountMiddleware') + + expect(await getText('#value')).toEqual('99') + // eslint-disable-next-line @typescript-eslint/await-thenable + expect(await app.browserWindow.getTitle()).toEqual('99') + }) + + it('should be able to use middleware when dispatching from main process', async () => { + expect(await getText('#value')).toEqual('10') + expect(await app.browserWindow.getTitle()).toEqual('10') + + await click('#mainsetCountMiddleware') + + expect(await getText('#value')).toEqual('99') + // eslint-disable-next-line @typescript-eslint/await-thenable + expect(await app.browserWindow.getTitle()).toEqual('99') + }) }) diff --git a/tests/e2e/main/index.ts b/tests/e2e/main/index.ts index 2591aa2c..15911886 100644 --- a/tests/e2e/main/index.ts +++ b/tests/e2e/main/index.ts @@ -1,9 +1,10 @@ import path from 'path' import url from 'url' -import { app, BrowserWindow } from 'electron' -import { createStore } from 'redux' +import { app, BrowserWindow, ipcMain } from 'electron' +import { applyMiddleware, compose, createStore } from 'redux' import { reducer } from '../../counter' import { mainStateSyncEnhancer } from '../../..' +import { countMiddleware } from '../../middleware' const isDevelopment = process.env.NODE_ENV !== 'production' @@ -11,7 +12,9 @@ const defaultState = { count: 10, } -const store = createStore(reducer, defaultState, mainStateSyncEnhancer()) +const middleware = applyMiddleware(countMiddleware) +const enhancer = compose(middleware, mainStateSyncEnhancer()) +const store = createStore(reducer, defaultState, enhancer) // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. @@ -44,6 +47,14 @@ function createWindow() { } renderValue() + ipcMain.once('mainsetCountMiddleware', () => { + store.dispatch({ type: 'SET_COUNT_MIDDLEWARE', payload: 9 }) + }) + + ipcMain.once('mainIncrement', () => { + store.dispatch({ type: 'INCREMENT' }) + }) + // Emitted when the window is closed. mainWindow.on('closed', () => { // Dereference the window object, usually you would store windows diff --git a/tests/e2e/renderer/index.ts b/tests/e2e/renderer/index.ts index dfe447a1..3c9a2f83 100644 --- a/tests/e2e/renderer/index.ts +++ b/tests/e2e/renderer/index.ts @@ -1,8 +1,13 @@ -import { createStore } from 'redux' +import { applyMiddleware, compose, createStore } from 'redux' import { reducer } from '../../counter' import { rendererStateSyncEnhancer } from '../../../' +import { countMiddleware } from '../../middleware' +import { ipcRenderer } from 'electron' -const store = createStore(reducer, rendererStateSyncEnhancer()) +const middleware = applyMiddleware(countMiddleware) +const enhancer = compose(middleware, rendererStateSyncEnhancer()) + +const store = createStore(reducer, enhancer) function mount() { document.getElementById('app')!.innerHTML = ` @@ -10,6 +15,10 @@ function mount() { Clicked: 0 times
+ + + +

` @@ -20,6 +29,16 @@ function mount() { document.getElementById('decrement')!.addEventListener('click', () => { store.dispatch({ type: 'DECREMENT' }) }) + + document.getElementById('setCountMiddleware')!.addEventListener('click', () => { + store.dispatch({ type: 'SET_COUNT_MIDDLEWARE', payload: 9 }) + }) + document.getElementById('mainsetCountMiddleware')!.addEventListener('click', () => { + ipcRenderer.send('mainsetCountMiddleware') + }) + document.getElementById('mainIncrement')!.addEventListener('click', () => { + ipcRenderer.send('mainIncrement') + }) } function renderValue() { diff --git a/tests/middleware.ts b/tests/middleware.ts new file mode 100644 index 00000000..934698f1 --- /dev/null +++ b/tests/middleware.ts @@ -0,0 +1,8 @@ +import { Middleware } from 'redux' + +export const countMiddleware: Middleware = () => (next) => (action) => { + return next({ + ...action, + payload: 99, + }) +} From 9dce953d23af7703caeac672bb8ba4b0ff45abf6 Mon Sep 17 00:00:00 2001 From: Jonas Snellinckx Date: Tue, 8 Dec 2020 21:27:56 +0100 Subject: [PATCH 2/2] fix: align enhancer style with standards to wrap the middleware --- src/mainStateSyncEnhancer.ts | 2 +- src/rendererStateSyncEnhancer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mainStateSyncEnhancer.ts b/src/mainStateSyncEnhancer.ts index 8827e87d..f175f3c5 100644 --- a/src/mainStateSyncEnhancer.ts +++ b/src/mainStateSyncEnhancer.ts @@ -77,7 +77,7 @@ export const mainStateSyncEnhancer = (options = defaultMainOptions): StoreEnhanc dispatch, } - dispatch = compose(middleware(middlewareAPI))(store.dispatch) + dispatch = compose(middleware(middlewareAPI))(dispatch) return { ...store, diff --git a/src/rendererStateSyncEnhancer.ts b/src/rendererStateSyncEnhancer.ts index ccd35194..0c6df751 100644 --- a/src/rendererStateSyncEnhancer.ts +++ b/src/rendererStateSyncEnhancer.ts @@ -66,7 +66,7 @@ export const rendererStateSyncEnhancer = (options = defaultRendererOptions): Sto dispatch, } - dispatch = compose(middleware(middlewareAPI))(store.dispatch) + dispatch = compose(middleware(middlewareAPI))(dispatch) return { ...store,