Skip to content

Commit

Permalink
Merge branch 'master' into v1.9-integration
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/toolkit/src/query/tests/buildSlice.test.ts
  • Loading branch information
markerikson committed Oct 8, 2022
2 parents 2d000a5 + 1dd128b commit 73abb6a
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({
const valuesArray = Array.from(toInvalidate.values())
for (const { queryCacheKey } of valuesArray) {
const querySubState = state.queries[queryCacheKey]
const subscriptionSubState = state.subscriptions[queryCacheKey]
if (querySubState && subscriptionSubState) {
const subscriptionSubState = state.subscriptions[queryCacheKey] ?? {}

if (querySubState) {
if (Object.keys(subscriptionSubState).length === 0) {
mwApi.dispatch(
removeQueryResult({
Expand All @@ -77,7 +78,6 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({
)
} else if (querySubState.status !== QueryStatus.uninitialized) {
mwApi.dispatch(refetchQuery(querySubState, queryCacheKey))
} else {
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions packages/toolkit/src/query/core/buildSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,17 @@ export function buildSlice({
)
const { queryCacheKey } = action.meta.arg

for (const tagTypeSubscriptions of Object.values(draft)) {
for (const idSubscriptions of Object.values(
tagTypeSubscriptions
)) {
const foundAt = idSubscriptions.indexOf(queryCacheKey)
if (foundAt !== -1) {
idSubscriptions.splice(foundAt, 1)
}
}
}

for (const { type, id } of providedTags) {
const subscribedQueries = ((draft[type] ??= {})[
id || '__internal_without_id'
Expand Down
126 changes: 80 additions & 46 deletions packages/toolkit/src/query/tests/buildSlice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ import { createSlice } from '@reduxjs/toolkit'
import { createApi } from '@reduxjs/toolkit/query'
import { setupApiStore } from './helpers'

let shouldApiResponseSuccess = true

function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

const baseQuery = (args?: any) => ({ data: args })
const api = createApi({
baseQuery,
tagTypes: ['SUCCEED', 'FAILED'],
endpoints: (build) => ({
getUser: build.query<unknown, number>({
getUser: build.query<{ url: string; success: boolean }, number>({
query(id) {
return { url: `user/${id}` }
return { url: `user/${id}`, success: shouldApiResponseSuccess }
},
providesTags: (result) => (result?.success ? ['SUCCEED'] : ['FAILED']),
}),
}),
})
Expand All @@ -29,60 +37,86 @@ const authSlice = createSlice({

const storeRef = setupApiStore(api, { auth: authSlice.reducer })

function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
describe('buildSlice', () => {
beforeEach(() => {
shouldApiResponseSuccess = true
})

it('only resets the api state when resetApiState is dispatched', async () => {
storeRef.store.dispatch({ type: 'unrelated' }) // trigger "registered middleware" into place
const initialState = storeRef.store.getState()

await storeRef.store.dispatch(
getUser.initiate(1, { subscriptionOptions: { pollingInterval: 10 } })
)

expect(storeRef.store.getState()).toEqual({
api: {
config: {
focused: true,
keepUnusedDataFor: 60,
middlewareRegistered: true,
online: true,
reducerPath: 'api',
refetchOnFocus: false,
refetchOnMountOrArgChange: false,
refetchOnReconnect: false,
},
mutations: {},
provided: {},
queries: {
'getUser(1)': {
data: {
url: 'user/1',
it('only resets the api state when resetApiState is dispatched', async () => {
storeRef.store.dispatch({ type: 'unrelated' }) // trigger "registered middleware" into place
const initialState = storeRef.store.getState()

await storeRef.store.dispatch(
getUser.initiate(1, { subscriptionOptions: { pollingInterval: 10 } })
)

expect(storeRef.store.getState()).toEqual({
api: {
config: {
focused: true,
keepUnusedDataFor: 60,
middlewareRegistered: true,
online: true,
reducerPath: 'api',
refetchOnFocus: false,
refetchOnMountOrArgChange: false,
refetchOnReconnect: false,
},
mutations: {},
provided: {},
queries: {
'getUser(1)': {
data: {
url: 'user/1',
},
endpointName: 'getUser',
fulfilledTimeStamp: expect.any(Number),
originalArgs: 1,
requestId: expect.any(String),
startedTimeStamp: expect.any(Number),
status: 'fulfilled',
},
endpointName: 'getUser',
fulfilledTimeStamp: expect.any(Number),
originalArgs: 1,
requestId: expect.any(String),
startedTimeStamp: expect.any(Number),
status: 'fulfilled',
},
subscriptions: {
'getUser(1)': expect.any(Object),
},
},
subscriptions: {
'getUser(1)': expect.any(Object),
auth: {
token: '1234',
},
},
auth: {
token: '1234',
},
})

storeRef.store.dispatch(api.util.resetApiState())

expect(storeRef.store.getState()).toEqual(initialState)
})

storeRef.store.dispatch(api.util.resetApiState())
it('replaces previous tags with new provided tags', async () => {
await storeRef.store.dispatch(getUser.initiate(1))

expect(
api.util.selectInvalidatedBy(storeRef.store.getState(), ['SUCCEED'])
).toHaveLength(1)
expect(
api.util.selectInvalidatedBy(storeRef.store.getState(), ['FAILED'])
).toHaveLength(0)

expect(storeRef.store.getState()).toEqual(initialState)
shouldApiResponseSuccess = false

storeRef.store.dispatch(getUser.initiate(1)).refetch()

await delay(10)

expect(
api.util.selectInvalidatedBy(storeRef.store.getState(), ['SUCCEED'])
).toHaveLength(0)
expect(
api.util.selectInvalidatedBy(storeRef.store.getState(), ['FAILED'])
).toHaveLength(1)
})
})

describe.only('`merge` callback', () => {
describe('`merge` callback', () => {
const baseQuery = (args?: any) => ({ data: args })

interface Todo {
Expand Down
39 changes: 39 additions & 0 deletions packages/toolkit/src/tests/configureStore.typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
configureStore,
getDefaultMiddleware,
createSlice,
ConfigureStoreOptions,
} from '@reduxjs/toolkit'
import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk'
import thunk from 'redux-thunk'
Expand Down Expand Up @@ -341,6 +342,18 @@ const _anyMiddleware: any = () => () => () => {}
// @ts-expect-error
const result2: string = store.dispatch(5)
}
/**
* Test: read-only middleware tuple
*/
{
const store = configureStore({
reducer: reducerA,
middleware: [] as any as readonly [Middleware<(a: StateA) => boolean, StateA>],
})
const result: boolean = store.dispatch(5)
// @ts-expect-error
const result2: string = store.dispatch(5)
}
/**
* Test: multiple custom middleware
*/
Expand Down Expand Up @@ -510,6 +523,32 @@ const _anyMiddleware: any = () => () => () => {}
expectNotAny(store.dispatch)
}

/**
* Test: decorated `configureStore` won't make `dispatch` `never`
*/
{
const someSlice = createSlice({
name: 'something',
initialState: null as any,
reducers: {
set(state) {
return state;
},
},
});

function configureMyStore<S>(options: Omit<ConfigureStoreOptions<S>, 'reducer'>) {
return configureStore({
...options,
reducer: someSlice.reducer,
});
}

const store = configureMyStore({});

expectType<Function>(store.dispatch);
}

{
interface CounterState {
value: number
Expand Down
2 changes: 1 addition & 1 deletion packages/toolkit/src/tsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export type ExtractDispatchExtensions<M> = M extends MiddlewareArray<
infer MiddlewareTuple
>
? ExtractDispatchFromMiddlewareTuple<MiddlewareTuple, {}>
: M extends Middleware[]
: M extends ReadonlyArray<Middleware>
? ExtractDispatchFromMiddlewareTuple<[...M], {}>
: never

Expand Down

0 comments on commit 73abb6a

Please sign in to comment.