@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
211 lines (173 loc) • 5.45 kB
text/typescript
/* eslint-disable no-lone-blocks */
import type { Reducer, Slice, WithSlice } from '@reduxjs/toolkit'
import { combineSlices } from '@reduxjs/toolkit'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { expectExactType, expectType } from './helpers'
declare const stringSlice: Slice<string, {}, 'string'>
declare const numberSlice: Slice<number, {}, 'number'>
declare const booleanReducer: Reducer<boolean>
const exampleApi = createApi({
baseQuery: fetchBaseQuery(),
endpoints: (build) => ({
getThing: build.query({
query: () => '',
}),
}),
})
type ExampleApiState = ReturnType<typeof exampleApi.reducer>
/**
* Test: combineSlices correctly combines static state
*/
{
const rootReducer = combineSlices(stringSlice, numberSlice, exampleApi, {
boolean: booleanReducer,
})
expectType<{
string: string
number: number
boolean: boolean
api: ExampleApiState
}>(rootReducer(undefined, { type: '' }))
}
/**
* Test: withLazyLoadedSlices adds partial to state
*/
{
const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices<
WithSlice<typeof numberSlice> & WithSlice<typeof exampleApi>
>()
expectExactType<number | undefined>(0)(
rootReducer(undefined, { type: '' }).number
)
expectExactType<ExampleApiState | undefined>(undefined)(
rootReducer(undefined, { type: '' }).api
)
}
/**
* Test: inject marks injected keys as required
*/
{
const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices<
WithSlice<typeof numberSlice> &
WithSlice<typeof exampleApi> & { boolean: boolean }
>()
expectExactType<number | undefined>(0)(
rootReducer(undefined, { type: '' }).number
)
expectExactType<boolean | undefined>(true)(
rootReducer(undefined, { type: '' }).boolean
)
expectExactType<ExampleApiState | undefined>(undefined)(
rootReducer(undefined, { type: '' }).api
)
const withNumber = rootReducer.inject(numberSlice)
expectExactType<number>(0)(withNumber(undefined, { type: '' }).number)
const withBool = rootReducer.inject({
reducerPath: 'boolean' as const,
reducer: booleanReducer,
})
expectExactType<boolean>(true)(withBool(undefined, { type: '' }).boolean)
const withApi = rootReducer.inject(exampleApi)
expectExactType<ExampleApiState>({} as ExampleApiState)(
withApi(undefined, { type: '' }).api
)
}
declare const wrongNumberSlice: Slice<string, {}, 'number'>
declare const wrongBooleanReducer: Reducer<number>
const wrongApi = createApi({
baseQuery: fetchBaseQuery(),
endpoints: (build) => ({
getThing2: build.query({
query: () => '',
}),
}),
})
/**
* Test: selector() allows defining selectors with injected reducers defined
*/
{
const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices<
WithSlice<typeof numberSlice> & { boolean: boolean }
>()
type RootState = ReturnType<typeof rootReducer>
const withoutInjection = rootReducer.selector(
(state: RootState) => state.number
)
expectExactType<number | undefined>(0)(
withoutInjection(rootReducer(undefined, { type: '' }))
)
const withInjection = rootReducer
.inject(numberSlice)
.selector((state) => state.number)
expectExactType<number>(0)(
withInjection(rootReducer(undefined, { type: '' }))
)
}
/**
* Test: selector() passes arguments through
*/
{
const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices<
WithSlice<typeof numberSlice> & { boolean: boolean }
>()
const selector = rootReducer
.inject(numberSlice)
.selector((state, num: number) => state.number)
const state = rootReducer(undefined, { type: '' })
// @ts-expect-error required argument
selector(state)
// @ts-expect-error number not string
selector(state, '')
selector(state, 0)
}
/**
* Test: nested calls inferred correctly
*/
{
const innerReducer =
combineSlices(stringSlice).withLazyLoadedSlices<
WithSlice<typeof numberSlice>
>()
const innerSelector = innerReducer.inject(numberSlice).selector(
(state) => state.number,
(rootState: RootState) => rootState.inner
)
const outerReducer = combineSlices({ inner: innerReducer })
type RootState = ReturnType<typeof outerReducer>
expectType<{ inner: { string: string } }>(
outerReducer(undefined, { type: '' })
)
expectType<number>(innerSelector(outerReducer(undefined, { type: '' })))
}
/**
* Test: selector errors if selectorFn and selectState are mismatched
*/
{
const combinedReducer =
combineSlices(stringSlice).withLazyLoadedSlices<
WithSlice<typeof numberSlice>
>()
const outerReducer = combineSlices({ inner: combinedReducer })
type RootState = ReturnType<typeof outerReducer>
combinedReducer.selector(
(state) => state.number,
// @ts-expect-error wrong state returned
(rootState: RootState) => rootState.inner.number
)
combinedReducer.selector(
(state, num: number) => state.number,
// @ts-expect-error wrong arguments
(rootState: RootState, str: string) => rootState.inner
)
combinedReducer.selector(
(state, num: number) => state.number,
(rootState: RootState) => rootState.inner
)
// TODO: see if there's a way of making this work
// probably a rare case so not the end of the world if not
combinedReducer.selector(
(state) => state.number,
// @ts-ignore
(rootState: RootState, num: number) => rootState.inner
)
}