UNPKG

@reduxjs/toolkit

Version:

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

232 lines (207 loc) 6.92 kB
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' import { configureStore } from '@reduxjs/toolkit' import { vi } from 'vitest' import type { Middleware, Reducer } from 'redux' import { THIRTY_TWO_BIT_MAX_INT, THIRTY_TWO_BIT_MAX_TIMER_SECONDS, } from '../core/buildMiddleware/cacheCollection' import { countObjectKeys } from '../utils/countObjectKeys' beforeAll(() => { vi.useFakeTimers() }) const onCleanup = vi.fn() beforeEach(() => { onCleanup.mockClear() }) test(`query: await cleanup, defaults`, async () => { const { store, api } = storeForApi( createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), endpoints: (build) => ({ query: build.query<unknown, string>({ query: () => '/success', }), }), }), ) const promise = store.dispatch(api.endpoints.query.initiate('arg')) await promise promise.unsubscribe() vi.advanceTimersByTime(59000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) test(`query: await cleanup, keepUnusedDataFor set`, async () => { const { store, api } = storeForApi( createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), endpoints: (build) => ({ query: build.query<unknown, string>({ query: () => '/success', }), }), keepUnusedDataFor: 29, }), ) const promise = store.dispatch(api.endpoints.query.initiate('arg')) await promise promise.unsubscribe() vi.advanceTimersByTime(28000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) test(`query: handles large keepUnuseDataFor values over 32-bit ms`, async () => { const { store, api } = storeForApi( createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), endpoints: (build) => ({ query: build.query<unknown, string>({ query: () => '/success', }), }), keepUnusedDataFor: THIRTY_TWO_BIT_MAX_TIMER_SECONDS - 10, }), ) const promise = store.dispatch(api.endpoints.query.initiate('arg')) await promise promise.unsubscribe() // Shouldn't have been called right away vi.advanceTimersByTime(1000) expect(onCleanup).not.toHaveBeenCalled() // Shouldn't have been called any time in the next few minutes vi.advanceTimersByTime(1_000_000) expect(onCleanup).not.toHaveBeenCalled() // _Should_ be called _wayyyy_ in the future (like 24.8 days from now) vi.advanceTimersByTime(THIRTY_TWO_BIT_MAX_TIMER_SECONDS * 1000), expect(onCleanup).toHaveBeenCalled() }) describe(`query: await cleanup, keepUnusedDataFor set`, () => { const { store, api } = storeForApi( createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), endpoints: (build) => ({ query: build.query<unknown, string>({ query: () => '/success', }), query2: build.query<unknown, string>({ query: () => '/success', keepUnusedDataFor: 35, }), query3: build.query<unknown, string>({ query: () => '/success', keepUnusedDataFor: 0, }), query4: build.query<unknown, string>({ query: () => '/success', keepUnusedDataFor: Infinity, }), }), keepUnusedDataFor: 29, }), ) test('global keepUnusedDataFor', async () => { const promise = store.dispatch(api.endpoints.query.initiate('arg')) await promise promise.unsubscribe() vi.advanceTimersByTime(28000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) test('endpoint keepUnusedDataFor', async () => { const promise = store.dispatch(api.endpoints.query2.initiate('arg')) await promise promise.unsubscribe() vi.advanceTimersByTime(34000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) test('endpoint keepUnusedDataFor: 0 ', async () => { expect(onCleanup).not.toHaveBeenCalled() const promise = store.dispatch(api.endpoints.query3.initiate('arg')) await promise promise.unsubscribe() expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(1) expect(onCleanup).toHaveBeenCalled() }) test('endpoint keepUnusedDataFor: Infinity', async () => { expect(onCleanup).not.toHaveBeenCalled() store.dispatch(api.endpoints.query4.initiate('arg')).unsubscribe() expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(THIRTY_TWO_BIT_MAX_INT) expect(onCleanup).not.toHaveBeenCalled() }) }) describe('resetApiState cleanup', () => { test('resetApiState aborts multiple running queries and mutations', async () => { const { store, api } = storeForApi( createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), endpoints: (build) => ({ query1: build.query<unknown, string>({ query: () => '/success', }), query2: build.query<unknown, string>({ query: () => '/success', }), mutation: build.mutation<unknown, string>({ query: () => ({ url: '/success', method: 'POST', }), }), }), }), ) // Start multiple queries and a mutation const queryPromise1 = store.dispatch(api.endpoints.query1.initiate('arg1')) const queryPromise2 = store.dispatch(api.endpoints.query2.initiate('arg2')) const mutationPromise = store.dispatch( api.endpoints.mutation.initiate('arg'), ) // Spy on abort methods queryPromise1.abort = vi.fn(queryPromise1.abort) queryPromise2.abort = vi.fn(queryPromise2.abort) mutationPromise.abort = vi.fn(mutationPromise.abort) // Dispatch resetApiState store.dispatch(api.util.resetApiState()) // Verify all aborts were called expect(queryPromise1.abort).toHaveBeenCalled() expect(queryPromise2.abort).toHaveBeenCalled() expect(mutationPromise.abort).toHaveBeenCalled() }) }) function storeForApi< A extends { reducerPath: 'api' reducer: Reducer<any, any> middleware: Middleware util: { resetApiState(): any } }, >(api: A) { const store = configureStore({ reducer: { api: api.reducer }, middleware: (gdm) => gdm({ serializableCheck: false, immutableCheck: false }).concat( api.middleware, ), enhancers: (gde) => gde({ autoBatch: false, }), }) let hadQueries = false store.subscribe(() => { const queryState = store.getState().api.queries if (hadQueries && countObjectKeys(queryState) === 0) { onCleanup() } hadQueries = hadQueries || countObjectKeys(queryState) > 0 }) return { api, store } }