| 1 | import { createSelectorCreator, lruMemoize } from '@reduxjs/toolkit'
|
|---|
| 2 | import {
|
|---|
| 3 | buildCreateApi,
|
|---|
| 4 | coreModule,
|
|---|
| 5 | reactHooksModule,
|
|---|
| 6 | } from '@reduxjs/toolkit/query/react'
|
|---|
| 7 | import { render, screen, waitFor } from '@testing-library/react'
|
|---|
| 8 | import { delay } from 'msw'
|
|---|
| 9 | import * as React from 'react'
|
|---|
| 10 | import type { ReactReduxContextValue } from 'react-redux'
|
|---|
| 11 | import {
|
|---|
| 12 | Provider,
|
|---|
| 13 | createDispatchHook,
|
|---|
| 14 | createSelectorHook,
|
|---|
| 15 | createStoreHook,
|
|---|
| 16 | } from 'react-redux'
|
|---|
| 17 | import { setupApiStore, useRenderCounter } from '../../tests/utils/helpers'
|
|---|
| 18 |
|
|---|
| 19 | const MyContext = React.createContext<ReactReduxContextValue | null>(null)
|
|---|
| 20 |
|
|---|
| 21 | describe('buildCreateApi', () => {
|
|---|
| 22 | test('Works with all hooks provided', async () => {
|
|---|
| 23 | const customCreateApi = buildCreateApi(
|
|---|
| 24 | coreModule(),
|
|---|
| 25 | reactHooksModule({
|
|---|
| 26 | hooks: {
|
|---|
| 27 | useDispatch: createDispatchHook(MyContext),
|
|---|
| 28 | useSelector: createSelectorHook(MyContext),
|
|---|
| 29 | useStore: createStoreHook(MyContext),
|
|---|
| 30 | },
|
|---|
| 31 | }),
|
|---|
| 32 | )
|
|---|
| 33 |
|
|---|
| 34 | const api = customCreateApi({
|
|---|
| 35 | baseQuery: async (arg: any) => {
|
|---|
| 36 | await delay(150)
|
|---|
| 37 |
|
|---|
| 38 | return {
|
|---|
| 39 | data: arg?.body ? { ...arg.body } : {},
|
|---|
| 40 | }
|
|---|
| 41 | },
|
|---|
| 42 | endpoints: (build) => ({
|
|---|
| 43 | getUser: build.query<{ name: string }, number>({
|
|---|
| 44 | query: () => ({
|
|---|
| 45 | body: { name: 'Timmy' },
|
|---|
| 46 | }),
|
|---|
| 47 | }),
|
|---|
| 48 | }),
|
|---|
| 49 | })
|
|---|
| 50 |
|
|---|
| 51 | let getRenderCount: () => number = () => 0
|
|---|
| 52 |
|
|---|
| 53 | const storeRef = setupApiStore(api, {}, { withoutTestLifecycles: true })
|
|---|
| 54 |
|
|---|
| 55 | // Copy of 'useQuery hook basic render count assumptions' from `buildHooks.test.tsx`
|
|---|
| 56 | function User() {
|
|---|
| 57 | const { isFetching } = api.endpoints.getUser.useQuery(1)
|
|---|
| 58 | getRenderCount = useRenderCounter()
|
|---|
| 59 |
|
|---|
| 60 | return (
|
|---|
| 61 | <div>
|
|---|
| 62 | <div data-testid="isFetching">{String(isFetching)}</div>
|
|---|
| 63 | </div>
|
|---|
| 64 | )
|
|---|
| 65 | }
|
|---|
| 66 |
|
|---|
| 67 | function Wrapper({ children }: any) {
|
|---|
| 68 | return (
|
|---|
| 69 | <Provider store={storeRef.store} context={MyContext}>
|
|---|
| 70 | {children}
|
|---|
| 71 | </Provider>
|
|---|
| 72 | )
|
|---|
| 73 | }
|
|---|
| 74 |
|
|---|
| 75 | render(<User />, { wrapper: Wrapper })
|
|---|
| 76 | // By the time this runs, the initial render will happen, and the query
|
|---|
| 77 | // will start immediately running by the time we can expect this
|
|---|
| 78 | expect(getRenderCount()).toBe(2)
|
|---|
| 79 |
|
|---|
| 80 | await waitFor(() =>
|
|---|
| 81 | expect(screen.getByTestId('isFetching').textContent).toBe('false'),
|
|---|
| 82 | )
|
|---|
| 83 | expect(getRenderCount()).toBe(3)
|
|---|
| 84 | })
|
|---|
| 85 |
|
|---|
| 86 | test("Throws an error if you don't provide all hooks", async () => {
|
|---|
| 87 | const callBuildCreateApi = () => {
|
|---|
| 88 | const customCreateApi = buildCreateApi(
|
|---|
| 89 | coreModule(),
|
|---|
| 90 | reactHooksModule({
|
|---|
| 91 | // @ts-ignore
|
|---|
| 92 | hooks: {
|
|---|
| 93 | useDispatch: createDispatchHook(MyContext),
|
|---|
| 94 | useSelector: createSelectorHook(MyContext),
|
|---|
| 95 | },
|
|---|
| 96 | }),
|
|---|
| 97 | )
|
|---|
| 98 | }
|
|---|
| 99 |
|
|---|
| 100 | expect(callBuildCreateApi).toThrowErrorMatchingInlineSnapshot(
|
|---|
| 101 | `
|
|---|
| 102 | [Error: When using custom hooks for context, all 3 hooks need to be provided: useDispatch, useSelector, useStore.
|
|---|
| 103 | Hook useStore was either not provided or not a function.]
|
|---|
| 104 | `,
|
|---|
| 105 | )
|
|---|
| 106 | })
|
|---|
| 107 | test('allows passing createSelector instance', async () => {
|
|---|
| 108 | const memoize = vi.fn(lruMemoize)
|
|---|
| 109 | const createSelector = createSelectorCreator(memoize)
|
|---|
| 110 | const createApi = buildCreateApi(
|
|---|
| 111 | coreModule({ createSelector }),
|
|---|
| 112 | reactHooksModule({ createSelector }),
|
|---|
| 113 | )
|
|---|
| 114 | const api = createApi({
|
|---|
| 115 | baseQuery: async (arg: any) => {
|
|---|
| 116 | await delay(150)
|
|---|
| 117 |
|
|---|
| 118 | return {
|
|---|
| 119 | data: arg?.body ? { ...arg.body } : {},
|
|---|
| 120 | }
|
|---|
| 121 | },
|
|---|
| 122 | endpoints: (build) => ({
|
|---|
| 123 | getUser: build.query<{ name: string }, number>({
|
|---|
| 124 | query: () => ({
|
|---|
| 125 | body: { name: 'Timmy' },
|
|---|
| 126 | }),
|
|---|
| 127 | }),
|
|---|
| 128 | }),
|
|---|
| 129 | })
|
|---|
| 130 |
|
|---|
| 131 | const storeRef = setupApiStore(api, {}, { withoutTestLifecycles: true })
|
|---|
| 132 |
|
|---|
| 133 | await storeRef.store.dispatch(api.endpoints.getUser.initiate(1))
|
|---|
| 134 |
|
|---|
| 135 | const selectUser = api.endpoints.getUser.select(1)
|
|---|
| 136 |
|
|---|
| 137 | expect(selectUser(storeRef.store.getState()).data).toEqual({
|
|---|
| 138 | name: 'Timmy',
|
|---|
| 139 | })
|
|---|
| 140 |
|
|---|
| 141 | expect(memoize).toHaveBeenCalledTimes(4)
|
|---|
| 142 |
|
|---|
| 143 | memoize.mockClear()
|
|---|
| 144 |
|
|---|
| 145 | function User() {
|
|---|
| 146 | const { isFetching } = api.endpoints.getUser.useQuery(1)
|
|---|
| 147 |
|
|---|
| 148 | return (
|
|---|
| 149 | <div>
|
|---|
| 150 | <div data-testid="isFetching">{String(isFetching)}</div>
|
|---|
| 151 | </div>
|
|---|
| 152 | )
|
|---|
| 153 | }
|
|---|
| 154 |
|
|---|
| 155 | function Wrapper({ children }: any) {
|
|---|
| 156 | return <Provider store={storeRef.store}>{children}</Provider>
|
|---|
| 157 | }
|
|---|
| 158 |
|
|---|
| 159 | render(<User />, { wrapper: Wrapper })
|
|---|
| 160 |
|
|---|
| 161 | await waitFor(() =>
|
|---|
| 162 | expect(screen.getByTestId('isFetching').textContent).toBe('false'),
|
|---|
| 163 | )
|
|---|
| 164 |
|
|---|
| 165 | // select() + selectFromResult
|
|---|
| 166 | expect(memoize).toHaveBeenCalledTimes(8)
|
|---|
| 167 | })
|
|---|
| 168 | })
|
|---|