Skip to content

Commit 73abb6a

Browse files
committed
Merge branch 'master' into v1.9-integration
# Conflicts: # packages/toolkit/src/query/tests/buildSlice.test.ts
2 parents 2d000a5 + 1dd128b commit 73abb6a

File tree

5 files changed

+134
-50
lines changed

5 files changed

+134
-50
lines changed

packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({
6767
const valuesArray = Array.from(toInvalidate.values())
6868
for (const { queryCacheKey } of valuesArray) {
6969
const querySubState = state.queries[queryCacheKey]
70-
const subscriptionSubState = state.subscriptions[queryCacheKey]
71-
if (querySubState && subscriptionSubState) {
70+
const subscriptionSubState = state.subscriptions[queryCacheKey] ?? {}
71+
72+
if (querySubState) {
7273
if (Object.keys(subscriptionSubState).length === 0) {
7374
mwApi.dispatch(
7475
removeQueryResult({
@@ -77,7 +78,6 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({
7778
)
7879
} else if (querySubState.status !== QueryStatus.uninitialized) {
7980
mwApi.dispatch(refetchQuery(querySubState, queryCacheKey))
80-
} else {
8181
}
8282
}
8383
}

packages/toolkit/src/query/core/buildSlice.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,17 @@ export function buildSlice({
354354
)
355355
const { queryCacheKey } = action.meta.arg
356356

357+
for (const tagTypeSubscriptions of Object.values(draft)) {
358+
for (const idSubscriptions of Object.values(
359+
tagTypeSubscriptions
360+
)) {
361+
const foundAt = idSubscriptions.indexOf(queryCacheKey)
362+
if (foundAt !== -1) {
363+
idSubscriptions.splice(foundAt, 1)
364+
}
365+
}
366+
}
367+
357368
for (const { type, id } of providedTags) {
358369
const subscribedQueries = ((draft[type] ??= {})[
359370
id || '__internal_without_id'

packages/toolkit/src/query/tests/buildSlice.test.ts

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,22 @@ import { createSlice } from '@reduxjs/toolkit'
22
import { createApi } from '@reduxjs/toolkit/query'
33
import { setupApiStore } from './helpers'
44

5+
let shouldApiResponseSuccess = true
6+
7+
function delay(ms: number) {
8+
return new Promise((resolve) => setTimeout(resolve, ms))
9+
}
10+
511
const baseQuery = (args?: any) => ({ data: args })
612
const api = createApi({
713
baseQuery,
14+
tagTypes: ['SUCCEED', 'FAILED'],
815
endpoints: (build) => ({
9-
getUser: build.query<unknown, number>({
16+
getUser: build.query<{ url: string; success: boolean }, number>({
1017
query(id) {
11-
return { url: `user/${id}` }
18+
return { url: `user/${id}`, success: shouldApiResponseSuccess }
1219
},
20+
providesTags: (result) => (result?.success ? ['SUCCEED'] : ['FAILED']),
1321
}),
1422
}),
1523
})
@@ -29,60 +37,86 @@ const authSlice = createSlice({
2937

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

32-
function delay(ms: number) {
33-
return new Promise((resolve) => setTimeout(resolve, ms))
34-
}
40+
describe('buildSlice', () => {
41+
beforeEach(() => {
42+
shouldApiResponseSuccess = true
43+
})
3544

36-
it('only resets the api state when resetApiState is dispatched', async () => {
37-
storeRef.store.dispatch({ type: 'unrelated' }) // trigger "registered middleware" into place
38-
const initialState = storeRef.store.getState()
39-
40-
await storeRef.store.dispatch(
41-
getUser.initiate(1, { subscriptionOptions: { pollingInterval: 10 } })
42-
)
43-
44-
expect(storeRef.store.getState()).toEqual({
45-
api: {
46-
config: {
47-
focused: true,
48-
keepUnusedDataFor: 60,
49-
middlewareRegistered: true,
50-
online: true,
51-
reducerPath: 'api',
52-
refetchOnFocus: false,
53-
refetchOnMountOrArgChange: false,
54-
refetchOnReconnect: false,
55-
},
56-
mutations: {},
57-
provided: {},
58-
queries: {
59-
'getUser(1)': {
60-
data: {
61-
url: 'user/1',
45+
it('only resets the api state when resetApiState is dispatched', async () => {
46+
storeRef.store.dispatch({ type: 'unrelated' }) // trigger "registered middleware" into place
47+
const initialState = storeRef.store.getState()
48+
49+
await storeRef.store.dispatch(
50+
getUser.initiate(1, { subscriptionOptions: { pollingInterval: 10 } })
51+
)
52+
53+
expect(storeRef.store.getState()).toEqual({
54+
api: {
55+
config: {
56+
focused: true,
57+
keepUnusedDataFor: 60,
58+
middlewareRegistered: true,
59+
online: true,
60+
reducerPath: 'api',
61+
refetchOnFocus: false,
62+
refetchOnMountOrArgChange: false,
63+
refetchOnReconnect: false,
64+
},
65+
mutations: {},
66+
provided: {},
67+
queries: {
68+
'getUser(1)': {
69+
data: {
70+
url: 'user/1',
71+
},
72+
endpointName: 'getUser',
73+
fulfilledTimeStamp: expect.any(Number),
74+
originalArgs: 1,
75+
requestId: expect.any(String),
76+
startedTimeStamp: expect.any(Number),
77+
status: 'fulfilled',
6278
},
63-
endpointName: 'getUser',
64-
fulfilledTimeStamp: expect.any(Number),
65-
originalArgs: 1,
66-
requestId: expect.any(String),
67-
startedTimeStamp: expect.any(Number),
68-
status: 'fulfilled',
79+
},
80+
subscriptions: {
81+
'getUser(1)': expect.any(Object),
6982
},
7083
},
71-
subscriptions: {
72-
'getUser(1)': expect.any(Object),
84+
auth: {
85+
token: '1234',
7386
},
74-
},
75-
auth: {
76-
token: '1234',
77-
},
87+
})
88+
89+
storeRef.store.dispatch(api.util.resetApiState())
90+
91+
expect(storeRef.store.getState()).toEqual(initialState)
7892
})
7993

80-
storeRef.store.dispatch(api.util.resetApiState())
94+
it('replaces previous tags with new provided tags', async () => {
95+
await storeRef.store.dispatch(getUser.initiate(1))
96+
97+
expect(
98+
api.util.selectInvalidatedBy(storeRef.store.getState(), ['SUCCEED'])
99+
).toHaveLength(1)
100+
expect(
101+
api.util.selectInvalidatedBy(storeRef.store.getState(), ['FAILED'])
102+
).toHaveLength(0)
81103

82-
expect(storeRef.store.getState()).toEqual(initialState)
104+
shouldApiResponseSuccess = false
105+
106+
storeRef.store.dispatch(getUser.initiate(1)).refetch()
107+
108+
await delay(10)
109+
110+
expect(
111+
api.util.selectInvalidatedBy(storeRef.store.getState(), ['SUCCEED'])
112+
).toHaveLength(0)
113+
expect(
114+
api.util.selectInvalidatedBy(storeRef.store.getState(), ['FAILED'])
115+
).toHaveLength(1)
116+
})
83117
})
84118

85-
describe.only('`merge` callback', () => {
119+
describe('`merge` callback', () => {
86120
const baseQuery = (args?: any) => ({ data: args })
87121

88122
interface Todo {

packages/toolkit/src/tests/configureStore.typetest.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
configureStore,
1515
getDefaultMiddleware,
1616
createSlice,
17+
ConfigureStoreOptions,
1718
} from '@reduxjs/toolkit'
1819
import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk'
1920
import thunk from 'redux-thunk'
@@ -341,6 +342,18 @@ const _anyMiddleware: any = () => () => () => {}
341342
// @ts-expect-error
342343
const result2: string = store.dispatch(5)
343344
}
345+
/**
346+
* Test: read-only middleware tuple
347+
*/
348+
{
349+
const store = configureStore({
350+
reducer: reducerA,
351+
middleware: [] as any as readonly [Middleware<(a: StateA) => boolean, StateA>],
352+
})
353+
const result: boolean = store.dispatch(5)
354+
// @ts-expect-error
355+
const result2: string = store.dispatch(5)
356+
}
344357
/**
345358
* Test: multiple custom middleware
346359
*/
@@ -510,6 +523,32 @@ const _anyMiddleware: any = () => () => () => {}
510523
expectNotAny(store.dispatch)
511524
}
512525

526+
/**
527+
* Test: decorated `configureStore` won't make `dispatch` `never`
528+
*/
529+
{
530+
const someSlice = createSlice({
531+
name: 'something',
532+
initialState: null as any,
533+
reducers: {
534+
set(state) {
535+
return state;
536+
},
537+
},
538+
});
539+
540+
function configureMyStore<S>(options: Omit<ConfigureStoreOptions<S>, 'reducer'>) {
541+
return configureStore({
542+
...options,
543+
reducer: someSlice.reducer,
544+
});
545+
}
546+
547+
const store = configureMyStore({});
548+
549+
expectType<Function>(store.dispatch);
550+
}
551+
513552
{
514553
interface CounterState {
515554
value: number

packages/toolkit/src/tsHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export type ExtractDispatchExtensions<M> = M extends MiddlewareArray<
9797
infer MiddlewareTuple
9898
>
9999
? ExtractDispatchFromMiddlewareTuple<MiddlewareTuple, {}>
100-
: M extends Middleware[]
100+
: M extends ReadonlyArray<Middleware>
101101
? ExtractDispatchFromMiddlewareTuple<[...M], {}>
102102
: never
103103

0 commit comments

Comments
 (0)