@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
162 lines (138 loc) • 4.54 kB
text/typescript
import type {
ActionCreatorWithPayload,
ActionCreatorWithoutPayload,
EntityAdapter,
EntityId,
EntityStateAdapter,
Update,
} from '@reduxjs/toolkit'
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'
function extractReducers<T, Id extends EntityId>(
adapter: EntityAdapter<T, Id>,
): EntityStateAdapter<T, Id> {
const { selectId, sortComparer, getInitialState, getSelectors, ...rest } =
adapter
return rest
}
describe('type tests', () => {
test('should be usable in a slice, with all the "reducer-like" functions', () => {
type Id = string & { readonly __tag: unique symbol }
type Entity = {
id: Id
}
const adapter = createEntityAdapter<Entity>()
const slice = createSlice({
name: 'test',
initialState: adapter.getInitialState(),
reducers: {
...extractReducers(adapter),
},
})
expectTypeOf(slice.actions.addOne).toMatchTypeOf<
ActionCreatorWithPayload<Entity>
>()
expectTypeOf(slice.actions.addMany).toMatchTypeOf<
ActionCreatorWithPayload<ReadonlyArray<Entity> | Record<string, Entity>>
>()
expectTypeOf(slice.actions.setAll).toMatchTypeOf<
ActionCreatorWithPayload<ReadonlyArray<Entity> | Record<string, Entity>>
>()
expectTypeOf(slice.actions.removeOne).toMatchTypeOf<
ActionCreatorWithPayload<Id>
>()
expectTypeOf(slice.actions.addMany).not.toMatchTypeOf<
ActionCreatorWithPayload<Entity[] | Record<string, Entity>>
>()
expectTypeOf(slice.actions.setAll).not.toMatchTypeOf<
ActionCreatorWithPayload<ReadonlyArray<Id>>
>()
expectTypeOf(slice.actions.removeOne).toMatchTypeOf<
ActionCreatorWithPayload<Id>
>()
expectTypeOf(slice.actions.removeMany).toMatchTypeOf<
ActionCreatorWithPayload<ReadonlyArray<Id>>
>()
expectTypeOf(slice.actions.removeMany).not.toMatchTypeOf<
ActionCreatorWithPayload<EntityId[]>
>()
expectTypeOf(
slice.actions.removeAll,
).toMatchTypeOf<ActionCreatorWithoutPayload>()
expectTypeOf(slice.actions.updateOne).toMatchTypeOf<
ActionCreatorWithPayload<Update<Entity, Id>>
>()
expectTypeOf(slice.actions.updateMany).not.toMatchTypeOf<
ActionCreatorWithPayload<Update<Entity, Id>[]>
>()
expectTypeOf(slice.actions.upsertOne).toMatchTypeOf<
ActionCreatorWithPayload<Entity>
>()
expectTypeOf(slice.actions.updateMany).toMatchTypeOf<
ActionCreatorWithPayload<ReadonlyArray<Update<Entity, Id>>>
>()
expectTypeOf(slice.actions.upsertOne).toMatchTypeOf<
ActionCreatorWithPayload<Entity>
>()
expectTypeOf(slice.actions.upsertMany).toMatchTypeOf<
ActionCreatorWithPayload<ReadonlyArray<Entity> | Record<string, Entity>>
>()
expectTypeOf(slice.actions.upsertMany).not.toMatchTypeOf<
ActionCreatorWithPayload<Entity[] | Record<string, Entity>>
>()
})
test('should not be able to mix with a different EntityAdapter', () => {
type Entity = {
id: EntityId
value: string
}
type Entity2 = {
id: EntityId
value2: string
}
const adapter = createEntityAdapter<Entity>()
const adapter2 = createEntityAdapter<Entity2>()
createSlice({
name: 'test',
initialState: adapter.getInitialState(),
reducers: {
addOne: adapter.addOne,
// @ts-expect-error
addOne2: adapter2.addOne,
},
})
})
test('should be usable in a slice with extra properties', () => {
type Entity = { id: EntityId; value: string }
const adapter = createEntityAdapter<Entity>()
createSlice({
name: 'test',
initialState: adapter.getInitialState({ extraData: 'test' }),
reducers: {
addOne: adapter.addOne,
},
})
})
test('should not be usable in a slice with an unfitting state', () => {
type Entity = { id: EntityId; value: string }
const adapter = createEntityAdapter<Entity>()
createSlice({
name: 'test',
initialState: { somethingElse: '' },
reducers: {
// @ts-expect-error
addOne: adapter.addOne,
},
})
})
test('should not be able to create an adapter unless the type has an Id or an idSelector is provided', () => {
type Entity = {
value: string
}
// @ts-expect-error
const adapter = createEntityAdapter<Entity>()
const adapter2: EntityAdapter<Entity, Entity['value']> =
createEntityAdapter({
selectId: (e: Entity) => e.value,
})
})
})