diff --git a/packages/toolkit/src/autoBatchEnhancer.ts b/packages/toolkit/src/autoBatchEnhancer.ts index 7479d49ef5..ab2d58d614 100644 --- a/packages/toolkit/src/autoBatchEnhancer.ts +++ b/packages/toolkit/src/autoBatchEnhancer.ts @@ -15,13 +15,6 @@ const createQueueWithTimer = (timeout: number) => { } } -// requestAnimationFrame won't exist in SSR environments. -// Fall back to a vague approximation just to keep from erroring. -const rAF = - typeof window !== 'undefined' && window.requestAnimationFrame - ? window.requestAnimationFrame - : createQueueWithTimer(10) - export type AutoBatchOptions = | { type: 'tick' } | { type: 'timer'; timeout: number } @@ -66,7 +59,10 @@ export const autoBatchEnhancer = options.type === 'tick' ? queueMicrotask : options.type === 'raf' - ? rAF + ? // requestAnimationFrame won't exist in SSR environments. Fall back to a vague approximation just to keep from erroring. + typeof window !== 'undefined' && window.requestAnimationFrame + ? window.requestAnimationFrame + : createQueueWithTimer(10) : options.type === 'callback' ? options.queueNotification : createQueueWithTimer(options.timeout) diff --git a/packages/toolkit/src/tests/autoBatchEnhancer.test.ts b/packages/toolkit/src/tests/autoBatchEnhancer.test.ts index 870d2c3106..e1b820c908 100644 --- a/packages/toolkit/src/tests/autoBatchEnhancer.test.ts +++ b/packages/toolkit/src/tests/autoBatchEnhancer.test.ts @@ -125,3 +125,84 @@ describe.each(cases)('autoBatchEnhancer: %j', (autoBatchOptions) => { expect(subscriptionNotifications).toBe(3) }) }) + +describe.each(cases)( + 'autoBatchEnhancer with fake timers: %j', + (autoBatchOptions) => { + beforeAll(() => { + vitest.useFakeTimers({ + toFake: ['setTimeout', 'queueMicrotask', 'requestAnimationFrame'], + }) + }) + afterAll(() => { + vitest.useRealTimers() + }) + beforeEach(() => { + subscriptionNotifications = 0 + store = makeStore(autoBatchOptions) + + store.subscribe(() => { + subscriptionNotifications++ + }) + }) + test('Does not alter normal subscription notification behavior', () => { + store.dispatch(decrementUnbatched()) + expect(subscriptionNotifications).toBe(1) + store.dispatch(decrementUnbatched()) + expect(subscriptionNotifications).toBe(2) + store.dispatch(decrementUnbatched()) + expect(subscriptionNotifications).toBe(3) + store.dispatch(decrementUnbatched()) + + vitest.runAllTimers() + + expect(subscriptionNotifications).toBe(4) + }) + + test('Only notifies once if several batched actions are dispatched in a row', () => { + store.dispatch(incrementBatched()) + expect(subscriptionNotifications).toBe(0) + store.dispatch(incrementBatched()) + expect(subscriptionNotifications).toBe(0) + store.dispatch(incrementBatched()) + expect(subscriptionNotifications).toBe(0) + store.dispatch(incrementBatched()) + + vitest.runAllTimers() + + expect(subscriptionNotifications).toBe(1) + }) + + test('Notifies immediately if a non-batched action is dispatched', () => { + store.dispatch(incrementBatched()) + expect(subscriptionNotifications).toBe(0) + store.dispatch(incrementBatched()) + expect(subscriptionNotifications).toBe(0) + store.dispatch(decrementUnbatched()) + expect(subscriptionNotifications).toBe(1) + store.dispatch(incrementBatched()) + + vitest.runAllTimers() + + expect(subscriptionNotifications).toBe(2) + }) + + test('Does not notify at end of tick if last action was normal priority', () => { + store.dispatch(incrementBatched()) + expect(subscriptionNotifications).toBe(0) + store.dispatch(incrementBatched()) + expect(subscriptionNotifications).toBe(0) + store.dispatch(decrementUnbatched()) + expect(subscriptionNotifications).toBe(1) + store.dispatch(incrementBatched()) + store.dispatch(decrementUnbatched()) + expect(subscriptionNotifications).toBe(2) + store.dispatch(decrementUnbatched()) + expect(subscriptionNotifications).toBe(3) + + vitest.runAllTimers() + + expect(subscriptionNotifications).toBe(3) + }) + }, +)