| 1 | import type { TagDescription } from '@reduxjs/toolkit/query'
|
|---|
| 2 | import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query'
|
|---|
| 3 | import { waitFor } from '@testing-library/react'
|
|---|
| 4 | import { delay } from 'msw'
|
|---|
| 5 | import { setupApiStore } from '../../tests/utils/helpers'
|
|---|
| 6 |
|
|---|
| 7 | const tagTypes = [
|
|---|
| 8 | 'apple',
|
|---|
| 9 | 'pear',
|
|---|
| 10 | 'banana',
|
|---|
| 11 | 'tomato',
|
|---|
| 12 | 'cat',
|
|---|
| 13 | 'dog',
|
|---|
| 14 | 'giraffe',
|
|---|
| 15 | ] as const
|
|---|
| 16 | type TagTypes = (typeof tagTypes)[number]
|
|---|
| 17 | type ProvidedTags = TagDescription<TagTypes>[]
|
|---|
| 18 | type InvalidatesTags = (ProvidedTags[number] | null | undefined)[]
|
|---|
| 19 | /** providesTags, invalidatesTags, shouldInvalidate */
|
|---|
| 20 | const caseMatrix: [ProvidedTags, InvalidatesTags, boolean][] = [
|
|---|
| 21 | // *****************************
|
|---|
| 22 | // basic invalidation behavior
|
|---|
| 23 | // *****************************
|
|---|
| 24 |
|
|---|
| 25 | // string
|
|---|
| 26 | [['apple'], ['apple'], true],
|
|---|
| 27 | [['apple'], ['pear'], false],
|
|---|
| 28 | // string and type only behave identical
|
|---|
| 29 | [[{ type: 'apple' }], ['apple'], true],
|
|---|
| 30 | [[{ type: 'apple' }], ['pear'], false],
|
|---|
| 31 | [['apple'], [{ type: 'apple' }], true],
|
|---|
| 32 | [['apple'], [{ type: 'pear' }], false],
|
|---|
| 33 | // type only invalidates type + id
|
|---|
| 34 | [[{ type: 'apple', id: 1 }], [{ type: 'apple' }], true],
|
|---|
| 35 | [[{ type: 'pear', id: 1 }], ['apple'], false],
|
|---|
| 36 | // type + id never invalidates type only
|
|---|
| 37 | [['apple'], [{ type: 'apple', id: 1 }], false],
|
|---|
| 38 | [['pear'], [{ type: 'apple', id: 1 }], false],
|
|---|
| 39 | // type + id invalidates type + id
|
|---|
| 40 | [[{ type: 'apple', id: 1 }], [{ type: 'apple', id: 1 }], true],
|
|---|
| 41 | [[{ type: 'apple', id: 1 }], [{ type: 'apple', id: 2 }], false],
|
|---|
| 42 | // null and undefined
|
|---|
| 43 | [['apple'], [null], false],
|
|---|
| 44 | [['apple'], [undefined], false],
|
|---|
| 45 | [['apple'], [null, 'apple'], true],
|
|---|
| 46 | [['apple'], [undefined, 'apple'], true],
|
|---|
| 47 | // *****************************
|
|---|
| 48 | // test multiple values in array
|
|---|
| 49 | // *****************************
|
|---|
| 50 |
|
|---|
| 51 | [['apple', 'banana', 'tomato'], ['apple'], true],
|
|---|
| 52 | [['apple'], ['pear', 'banana', 'tomato'], false],
|
|---|
| 53 | [
|
|---|
| 54 | [
|
|---|
| 55 | { type: 'apple', id: 1 },
|
|---|
| 56 | { type: 'apple', id: 3 },
|
|---|
| 57 | { type: 'apple', id: 4 },
|
|---|
| 58 | ],
|
|---|
| 59 | [{ type: 'apple', id: 1 }],
|
|---|
| 60 | true,
|
|---|
| 61 | ],
|
|---|
| 62 | [
|
|---|
| 63 | [{ type: 'apple', id: 1 }],
|
|---|
| 64 | [
|
|---|
| 65 | { type: 'apple', id: 2 },
|
|---|
| 66 | { type: 'apple', id: 3 },
|
|---|
| 67 | { type: 'apple', id: 4 },
|
|---|
| 68 | ],
|
|---|
| 69 | false,
|
|---|
| 70 | ],
|
|---|
| 71 | ]
|
|---|
| 72 |
|
|---|
| 73 | test.each(caseMatrix)(
|
|---|
| 74 | '\tprovidesTags: %O,\n\tinvalidatesTags: %O,\n\tshould invalidate: %s',
|
|---|
| 75 | async (providesTags, invalidatesTags, shouldInvalidate) => {
|
|---|
| 76 | let queryCount = 0
|
|---|
| 77 | const {
|
|---|
| 78 | store,
|
|---|
| 79 | api,
|
|---|
| 80 | api: {
|
|---|
| 81 | endpoints: { invalidating, providing, unrelated },
|
|---|
| 82 | },
|
|---|
| 83 | } = setupApiStore(
|
|---|
| 84 | createApi({
|
|---|
| 85 | baseQuery: fakeBaseQuery(),
|
|---|
| 86 | tagTypes,
|
|---|
| 87 | endpoints: (build) => ({
|
|---|
| 88 | providing: build.query<unknown, void>({
|
|---|
| 89 | queryFn() {
|
|---|
| 90 | queryCount++
|
|---|
| 91 | return { data: {} }
|
|---|
| 92 | },
|
|---|
| 93 | providesTags,
|
|---|
| 94 | }),
|
|---|
| 95 | unrelated: build.query<unknown, void>({
|
|---|
| 96 | queryFn() {
|
|---|
| 97 | return { data: {} }
|
|---|
| 98 | },
|
|---|
| 99 | providesTags: ['cat', 'dog', { type: 'giraffe', id: 8 }],
|
|---|
| 100 | }),
|
|---|
| 101 | invalidating: build.mutation<unknown, void>({
|
|---|
| 102 | queryFn() {
|
|---|
| 103 | return { data: {} }
|
|---|
| 104 | },
|
|---|
| 105 | invalidatesTags,
|
|---|
| 106 | }),
|
|---|
| 107 | }),
|
|---|
| 108 | }),
|
|---|
| 109 | undefined,
|
|---|
| 110 | { withoutTestLifecycles: true },
|
|---|
| 111 | )
|
|---|
| 112 |
|
|---|
| 113 | store.dispatch(providing.initiate())
|
|---|
| 114 | store.dispatch(unrelated.initiate())
|
|---|
| 115 | expect(queryCount).toBe(1)
|
|---|
| 116 | await waitFor(() => {
|
|---|
| 117 | expect(api.endpoints.providing.select()(store.getState()).status).toBe(
|
|---|
| 118 | 'fulfilled',
|
|---|
| 119 | )
|
|---|
| 120 | expect(api.endpoints.unrelated.select()(store.getState()).status).toBe(
|
|---|
| 121 | 'fulfilled',
|
|---|
| 122 | )
|
|---|
| 123 | })
|
|---|
| 124 | const toInvalidate = api.util.selectInvalidatedBy(
|
|---|
| 125 | store.getState(),
|
|---|
| 126 | invalidatesTags,
|
|---|
| 127 | )
|
|---|
| 128 |
|
|---|
| 129 | if (shouldInvalidate) {
|
|---|
| 130 | expect(toInvalidate).toEqual([
|
|---|
| 131 | {
|
|---|
| 132 | queryCacheKey: 'providing(undefined)',
|
|---|
| 133 | endpointName: 'providing',
|
|---|
| 134 | originalArgs: undefined,
|
|---|
| 135 | },
|
|---|
| 136 | ])
|
|---|
| 137 | } else {
|
|---|
| 138 | expect(toInvalidate).toEqual([])
|
|---|
| 139 | }
|
|---|
| 140 |
|
|---|
| 141 | store.dispatch(invalidating.initiate())
|
|---|
| 142 | expect(queryCount).toBe(1)
|
|---|
| 143 | await delay(2)
|
|---|
| 144 | expect(queryCount).toBe(shouldInvalidate ? 2 : 1)
|
|---|
| 145 | },
|
|---|
| 146 | )
|
|---|