From 6b2a83eb961f124e7e748f875ecf313af231fe1b Mon Sep 17 00:00:00 2001 From: AlexandrHoroshih Date: Mon, 11 Dec 2023 22:56:50 +0700 Subject: [PATCH] Make types work --- .../src/lib/redux-interop.spec.ts | 35 +++++--- .../redux-interop/src/lib/redux-interop.ts | 90 ++++++++++--------- 2 files changed, 69 insertions(+), 56 deletions(-) diff --git a/packages/redux-interop/src/lib/redux-interop.spec.ts b/packages/redux-interop/src/lib/redux-interop.spec.ts index df9927b5..3a35fa1a 100644 --- a/packages/redux-interop/src/lib/redux-interop.spec.ts +++ b/packages/redux-interop/src/lib/redux-interop.spec.ts @@ -51,14 +51,17 @@ describe('@withease/redux', () => { }); test('Should allow reading state', () => { - const reduxStore = legacy_createStore((_, x) => ({ - value: (x as any).value || 'kek', + const reduxStore = legacy_createStore< + { value: string }, + { type: string; value: string } + >((_, x) => ({ + value: x.value || 'kek', })); const setup = createEvent(); const interop = createReduxInterop({ reduxStore, setup }); setup(); - const $state = interop.fromState((x) => (x as any).value); + const $state = interop.fromState((x) => x.value); expect($state.getState()).toEqual('kek'); @@ -68,14 +71,20 @@ describe('@withease/redux', () => { }); test('Should work with Fork API', async () => { - const reduxStore = legacy_createStore(() => ({}), {}); + const reduxStore = legacy_createStore< + { value: string }, + { type: string; value: string } + >(() => ({ value: '' }), { value: '' }); const setup = createEvent(); const interop = createReduxInterop({ reduxStore, setup }); - const $state = interop.fromState((x) => (x as any).value); + const $state = interop.fromState((x) => x.value); - const mockStore = legacy_createStore((_, x) => ({ - value: (x as any).value || 'kek', + const mockStore = legacy_createStore< + { value: string }, + { type: string; value: string } + >((_, x) => ({ + value: x.value || 'kek', })); const scope = fork({ @@ -124,7 +133,11 @@ describe('@withease/redux', () => { }); test('Should work with Fork API', async () => { - const reduxStore = legacy_createStore(() => ({}), {}); + const reduxStore = legacy_createStore<{ test: string }, { type: string }>( + () => ({ test: '' }), + { test: '' } + ); + const testSlice = createSlice({ name: 'test', initialState: 'kek', @@ -150,9 +163,7 @@ describe('@withease/redux', () => { expect(scope.getState(interop.$store)).toBe(mockStore); expect(scope.getState($test)).toEqual('kek'); - expect($test.getState()).toEqual(undefined); - - interop.dispatch(testSlice.actions.test()); + expect($test.getState()).toEqual(''); await allSettled(interop.dispatch, { scope, @@ -160,7 +171,7 @@ describe('@withease/redux', () => { }); expect(scope.getState($test)).toEqual('lol'); - expect($test.getState()).toEqual(undefined); + expect($test.getState()).toEqual(''); }); }); }); diff --git a/packages/redux-interop/src/lib/redux-interop.ts b/packages/redux-interop/src/lib/redux-interop.ts index 97fd7a63..7182bcc8 100644 --- a/packages/redux-interop/src/lib/redux-interop.ts +++ b/packages/redux-interop/src/lib/redux-interop.ts @@ -1,5 +1,5 @@ -import type { Unit } from 'effector'; -import type { Store as ReduxStore } from 'redux'; +import type { Unit, Store, EventCallable } from 'effector'; +import type { Store as ReduxStore, Action } from 'redux'; import { createStore, createEvent, @@ -18,18 +18,49 @@ import { * @returns Interop API object */ export function createReduxInterop< - BaseState = unknown, - // Record type is used here instead of UnknownAction, because compatibility with redux ^4.0.0 is also needed - Action = Record, - StateExt = unknown + State = unknown, + Act extends Action = { type: string; [k: string]: unknown }, + // eslint-disable-next-line @typescript-eslint/ban-types + Ext extends {} = {} >(config: { - reduxStore: ReduxStore; + reduxStore: ReduxStore; // We don't care about the type of the setup unit here // eslint-disable-next-line @typescript-eslint/no-explicit-any setup: Unit; -}) { - type State = BaseState & StateExt; - +}): { + /** + * Effector store containing the Redux store + * + * You can use it to substitute Redux store instance, while writing tests via Effector's Fork API + * @example + * ``` + * const scope = fork({ + * values: [ + * [reduxInterop.$store, reduxStoreMock] + * ] + * }) + * ``` + */ + $store: Store; + /** + * Effector's event, which will trigger Redux store dispatch + * + * @example + * ``` + * const updateName = reduxInterop.dispatch.prepend((name: string) => updateNameAction(name)); + * ``` + */ + dispatch: EventCallable; + /** + * Function to get Effector store containing selected part of the Redux store + * + * @example + * ``` + * const $user = reduxInterop.fromState(state => state.user); + * ``` + */ + fromState: (selector: (state: State & Ext) => R) => Store; +} { const { reduxStore, setup } = config; if (!is.unit(setup)) { throw new Error('setup must be an effector unit'); @@ -48,22 +79,22 @@ export function createReduxInterop< name: 'redux-interop/$store', }); - const stateUpdated = createEvent(); + const stateUpdated = createEvent(); - const $state = createStore(reduxStore.getState(), { + const $state = createStore(reduxStore.getState(), { serialize: 'ignore', name: 'redux-interop/$state', skipVoid: false, }).on(stateUpdated, (_, state) => state); - function fromState(selector: (state: State) => R) { + function fromState(selector: (state: State & Ext) => R) { return $state.map(selector); } - const dispatch = createEvent(); + const dispatch = createEvent(); const dispatchFx = attach({ source: $store, - effect(store, action: Action) { + effect(store, action: Act) { // eslint-disable-next-line @typescript-eslint/no-explicit-any store.dispatch(action as any); }, @@ -102,37 +133,8 @@ export function createReduxInterop< }).watch(console.error); return { - /** - * Effector store containing the Redux store - * - * You can use it to substitute Redux store instance, while writing tests via Effector's Fork API - * @example - * ``` - * const scope = fork({ - * values: [ - * [reduxInterop.$store, reduxStoreMock] - * ] - * }) - * ``` - */ $store, - /** - * Effector's event, which will trigger Redux store dispatch - * - * @example - * ``` - * const updateName = reduxInterop.dispatch.prepend((name: string) => updateNameAction(name)); - * ``` - */ dispatch, - /** - * Function to get Effector store containing selected part of the Redux store - * - * @example - * ``` - * const $user = reduxInterop.fromState(state => state.user); - * ``` - */ fromState, }; }