gs-port / node_modules /@reduxjs /toolkit /src /query /tests /useMutation-fixedCacheKey.test.tsx
Scribbler310's picture
feat: enhance dashboard
c2b7eb3 verified
import { createApi } from '@reduxjs/toolkit/query/react'
import {
act,
getByTestId,
render,
screen,
waitFor,
} from '@testing-library/react'
import { delay } from 'msw'
import { vi } from 'vitest'
import { setupApiStore } from '../../tests/utils/helpers'
describe('fixedCacheKey', () => {
const onNewCacheEntry = vi.fn()
const api = createApi({
async baseQuery(arg: string | Promise<string>) {
return { data: await arg }
},
endpoints: (build) => ({
send: build.mutation<string, string | Promise<string>>({
query: (arg) => arg,
}),
}),
})
const storeRef = setupApiStore(api)
function Component({
name,
fixedCacheKey,
value = name,
}: {
name: string
fixedCacheKey?: string
value?: string | Promise<string>
}) {
const [trigger, result] = api.endpoints.send.useMutation({ fixedCacheKey })
return (
<div data-testid={name}>
<div data-testid="status">{result.status}</div>
<div data-testid="data">{result.data}</div>
<div data-testid="originalArgs">{String(result.originalArgs)}</div>
<button data-testid="trigger" onClick={() => trigger(value)}>
trigger
</button>
<button data-testid="reset" onClick={result.reset}>
reset
</button>
</div>
)
}
test('two mutations without `fixedCacheKey` do not influence each other', async () => {
render(
<>
<Component name="C1" />
<Component name="C2" />
</>,
{ wrapper: storeRef.wrapper },
)
const c1 = screen.getByTestId('C1')
const c2 = screen.getByTestId('C2')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
act(() => {
getByTestId(c1, 'trigger').click()
})
await waitFor(() =>
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
)
expect(getByTestId(c1, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
})
test('two mutations with the same `fixedCacheKey` do influence each other', async () => {
render(
<>
<Component name="C1" fixedCacheKey="test" />
<Component name="C2" fixedCacheKey="test" />
</>,
{ wrapper: storeRef.wrapper },
)
const c1 = screen.getByTestId('C1')
const c2 = screen.getByTestId('C2')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
act(() => {
getByTestId(c1, 'trigger').click()
})
await waitFor(() => {
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c1, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c2, 'data').textContent).toBe('C1')
})
// test reset from the other component
act(() => {
getByTestId(c2, 'reset').click()
})
await waitFor(() => {
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c1, 'data').textContent).toBe('')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'data').textContent).toBe('')
})
})
test('resetting from the component that triggered the mutation resets for each shared result', async () => {
render(
<>
<Component name="C1" fixedCacheKey="test-A" />
<Component name="C2" fixedCacheKey="test-A" />
<Component name="C3" fixedCacheKey="test-B" />
<Component name="C4" fixedCacheKey="test-B" />
</>,
{ wrapper: storeRef.wrapper },
)
const c1 = screen.getByTestId('C1')
const c2 = screen.getByTestId('C2')
const c3 = screen.getByTestId('C3')
const c4 = screen.getByTestId('C4')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c3, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c4, 'status').textContent).toBe('uninitialized')
// trigger with a component using the first cache key
act(() => {
getByTestId(c1, 'trigger').click()
})
await waitFor(() =>
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
)
// the components with the first cache key should be affected
expect(getByTestId(c1, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c2, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
// the components with the second cache key should be unaffected
expect(getByTestId(c3, 'data').textContent).toBe('')
expect(getByTestId(c3, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c4, 'data').textContent).toBe('')
expect(getByTestId(c4, 'status').textContent).toBe('uninitialized')
// trigger with a component using the second cache key
act(() => {
getByTestId(c3, 'trigger').click()
})
await waitFor(() =>
expect(getByTestId(c3, 'status').textContent).toBe('fulfilled'),
)
// the components with the first cache key should be unaffected
await waitFor(() => {
expect(getByTestId(c1, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c2, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
// the component with the second cache key should be affected
expect(getByTestId(c3, 'data').textContent).toBe('C3')
expect(getByTestId(c3, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c4, 'data').textContent).toBe('C3')
expect(getByTestId(c4, 'status').textContent).toBe('fulfilled')
})
// test reset from the component that triggered the mutation for the first cache key
act(() => {
getByTestId(c1, 'reset').click()
})
await waitFor(() => {
// the components with the first cache key should be affected
expect(getByTestId(c1, 'data').textContent).toBe('')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'data').textContent).toBe('')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
// the components with the second cache key should be unaffected
expect(getByTestId(c3, 'data').textContent).toBe('C3')
expect(getByTestId(c3, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c4, 'data').textContent).toBe('C3')
expect(getByTestId(c4, 'status').textContent).toBe('fulfilled')
})
})
test('two mutations with different `fixedCacheKey` do not influence each other', async () => {
render(
<>
<Component name="C1" fixedCacheKey="test" />
<Component name="C2" fixedCacheKey="toast" />
</>,
{ wrapper: storeRef.wrapper },
)
const c1 = screen.getByTestId('C1')
const c2 = screen.getByTestId('C2')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
act(() => {
getByTestId(c1, 'trigger').click()
})
await waitFor(() =>
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
)
expect(getByTestId(c1, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
})
test('unmounting and remounting keeps data intact', async () => {
const { rerender } = render(<Component name="C1" fixedCacheKey="test" />, {
wrapper: storeRef.wrapper,
})
let c1 = screen.getByTestId('C1')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
act(() => {
getByTestId(c1, 'trigger').click()
})
await waitFor(() =>
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
)
expect(getByTestId(c1, 'data').textContent).toBe('C1')
rerender(<div />)
expect(screen.queryByTestId('C1')).toBe(null)
rerender(<Component name="C1" fixedCacheKey="test" />)
c1 = screen.getByTestId('C1')
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c1, 'data').textContent).toBe('C1')
})
test('(limitation) mutations using `fixedCacheKey` do not return `originalArgs`', async () => {
render(
<>
<Component name="C1" fixedCacheKey="test" />
<Component name="C2" fixedCacheKey="test" />
</>,
{ wrapper: storeRef.wrapper },
)
const c1 = screen.getByTestId('C1')
const c2 = screen.getByTestId('C2')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
act(() => {
getByTestId(c1, 'trigger').click()
})
await waitFor(() =>
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
)
expect(getByTestId(c1, 'data').textContent).toBe('C1')
expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c2, 'data').textContent).toBe('C1')
})
test('a component without `fixedCacheKey` has `originalArgs`', async () => {
render(<Component name="C1" />, {
wrapper: storeRef.wrapper,
})
let c1 = screen.getByTestId('C1')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
await act(async () => {
getByTestId(c1, 'trigger').click()
await Promise.resolve()
})
expect(getByTestId(c1, 'originalArgs').textContent).toBe('C1')
})
test('a component with `fixedCacheKey` does never have `originalArgs`', async () => {
render(<Component name="C1" fixedCacheKey="test" />, {
wrapper: storeRef.wrapper,
})
let c1 = screen.getByTestId('C1')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
await act(async () => {
getByTestId(c1, 'trigger').click()
})
expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
})
test('using `fixedCacheKey` will always use the latest dispatched thunk, prevent races', async () => {
let resolve1: (str: string) => void, resolve2: (str: string) => void
const p1 = new Promise<string>((resolve) => {
resolve1 = resolve
})
const p2 = new Promise<string>((resolve) => {
resolve2 = resolve
})
render(
<>
<Component name="C1" fixedCacheKey="test" value={p1} />
<Component name="C2" fixedCacheKey="test" value={p2} />
</>,
{ wrapper: storeRef.wrapper },
)
const c1 = screen.getByTestId('C1')
const c2 = screen.getByTestId('C2')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
await act(async () => {
getByTestId(c1, 'trigger').click()
await Promise.resolve()
})
expect(getByTestId(c1, 'status').textContent).toBe('pending')
expect(getByTestId(c1, 'data').textContent).toBe('')
act(() => {
getByTestId(c2, 'trigger').click()
})
expect(getByTestId(c1, 'status').textContent).toBe('pending')
expect(getByTestId(c1, 'data').textContent).toBe('')
await act(async () => {
resolve1!('this should not show up any more')
await Promise.resolve()
})
await delay(150)
expect(getByTestId(c1, 'status').textContent).toBe('pending')
expect(getByTestId(c1, 'data').textContent).toBe('')
await act(async () => {
resolve2!('this should be visible')
await Promise.resolve()
})
await delay(150)
expect(getByTestId(c1, 'status').textContent).toBe('fulfilled')
expect(getByTestId(c1, 'data').textContent).toBe('this should be visible')
})
test('using fixedCacheKey should create a new cache entry', async () => {
api.enhanceEndpoints({
endpoints: {
send: {
onCacheEntryAdded: (arg) => onNewCacheEntry(arg),
},
},
})
render(<Component name="C1" fixedCacheKey={'testKey'} />, {
wrapper: storeRef.wrapper,
})
let c1 = screen.getByTestId('C1')
expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
await act(async () => {
getByTestId(c1, 'trigger').click()
await Promise.resolve()
})
expect(onNewCacheEntry).toHaveBeenCalledWith('C1')
api.enhanceEndpoints({
endpoints: {
send: {
onCacheEntryAdded: undefined,
},
},
})
})
})