UNPKG

@reduxjs/toolkit

Version:

The official, opinionated, batteries-included toolset for efficient Redux development

211 lines (173 loc) 5.45 kB
/* 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 ) }