Skip to content

Commit

Permalink
Make types work
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandrHoroshih committed Dec 11, 2023
1 parent f37593c commit 6b2a83e
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 56 deletions.
35 changes: 23 additions & 12 deletions packages/redux-interop/src/lib/redux-interop.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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({
Expand Down Expand Up @@ -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',
Expand All @@ -150,17 +163,15 @@ 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,
params: testSlice.actions.test(),
});

expect(scope.getState($test)).toEqual('lol');
expect($test.getState()).toEqual(undefined);
expect($test.getState()).toEqual('');
});
});
});
90 changes: 46 additions & 44 deletions packages/redux-interop/src/lib/redux-interop.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<string, unknown>,
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<State, Act, Ext>;
// We don't care about the type of the setup unit here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setup: Unit<any>;
}) {
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<ReduxStore>;
/**
* Effector's event, which will trigger Redux store dispatch
*
* @example
* ```
* const updateName = reduxInterop.dispatch.prepend((name: string) => updateNameAction(name));
* ```
*/
dispatch: EventCallable<Act>;
/**
* Function to get Effector store containing selected part of the Redux store
*
* @example
* ```
* const $user = reduxInterop.fromState(state => state.user);
* ```
*/
fromState: <R>(selector: (state: State & Ext) => R) => Store<R>;
} {
const { reduxStore, setup } = config;
if (!is.unit(setup)) {
throw new Error('setup must be an effector unit');
Expand All @@ -48,22 +79,22 @@ export function createReduxInterop<
name: 'redux-interop/$store',
});

const stateUpdated = createEvent<State>();
const stateUpdated = createEvent<State & Ext>();

const $state = createStore<State>(reduxStore.getState(), {
const $state = createStore<State & Ext>(reduxStore.getState(), {
serialize: 'ignore',
name: 'redux-interop/$state',
skipVoid: false,
}).on(stateUpdated, (_, state) => state);

function fromState<R>(selector: (state: State) => R) {
function fromState<R>(selector: (state: State & Ext) => R) {
return $state.map(selector);
}

const dispatch = createEvent<Action>();
const dispatch = createEvent<Act>();
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);
},
Expand Down Expand Up @@ -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,
};
}

0 comments on commit 6b2a83e

Please sign in to comment.