diff --git a/packages/core/src/__tests__/smoking.spec.ts b/packages/core/src/__tests__/smoking.spec.ts index 13450664..3cc73f48 100644 --- a/packages/core/src/__tests__/smoking.spec.ts +++ b/packages/core/src/__tests__/smoking.spec.ts @@ -1,7 +1,9 @@ +/* eslint-disable sonarjs/no-identical-functions */ + import 'reflect-metadata' import { rootInjector } from '@sigi/di' import { Observable } from 'rxjs' -import { delay, map } from 'rxjs/operators' +import { delay, map, tap } from 'rxjs/operators' import * as Sinon from 'sinon' import { Reducer, Effect } from '../decorators' @@ -83,4 +85,56 @@ describe('Smoking tests', () => { timer.restore() rootInjector.reset() }) + + it('should skip all effects restored from persisted state', () => { + type State = { foo: string; bar: number } + const staticState = { foo: 'foo', bar: 42 } + global['SIGI_STATE'] = { + SSRPersistModule: staticState, + } + const spy = Sinon.spy() + @Module('SSRPersistModule') + class SSRPersistModule extends EffectModule { + defaultState = { + foo: '1', + bar: 2, + } + + @Reducer() + set(state: State, payload: string | number) { + if (typeof payload === 'string') { + return { ...state, foo: payload } + } else { + return { ...state, bar: payload } + } + } + + @Effect({ + payloadGetter: () => '2', + }) + setFoo(payload$: Observable) { + return payload$.pipe( + tap(spy), + map((payload) => this.getActions().set(payload)), + ) + } + + @Effect({ + payloadGetter: () => 3, + }) + setBar(payload$: Observable) { + return payload$.pipe( + tap(spy), + map((payload) => this.getActions().set(payload)), + ) + } + } + + const module = rootInjector.getInstance(SSRPersistModule) + module.dispatchers.setFoo(staticState.foo + 1) + module.dispatchers.setBar(staticState.bar + 1) + expect(spy.callCount).toBe(0) + expect(module.state.foo).toBe(staticState.foo) + expect(module.state.bar).toBe(staticState.bar) + }) }) diff --git a/packages/core/src/module.ts b/packages/core/src/module.ts index 561bd8f6..b3c76594 100644 --- a/packages/core/src/module.ts +++ b/packages/core/src/module.ts @@ -286,9 +286,14 @@ export abstract class EffectModule { ...effectKeys.map((name) => { const effect: Effect = (this as any)[name] const payload$ = action$.pipe( - filter(({ type }, index) => { + filter(({ type }) => type === name), + // If this Module restored from `SIGI_STATE` successfully. + // We should skip the effects decorated by `payloadGetter`, which was dispatched in the server side. + // So we need `filter` the decorated effects by `index: 0`, which is the first time we dispatch the action in the client side. + // Which means we should filter by the `action.type` first, and `filter` by the `index` again then. + filter((_, index) => { const skipCount = !this.actionsToRetry.has(name) && this.actionsToSkip?.has(name) ? 1 : 0 - return type === name && skipCount <= index + return skipCount <= index }), map(({ payload }) => payload), )