import type {
CreateMutation,
UpdateMutation,
QueryVariables,
} from '@dhis2/data-engine'
import { renderHook, act, waitFor } from '@testing-library/react'
import * as React from 'react'
import { CustomDataProvider } from '../components/CustomDataProvider'
import { useDataEngine } from './useDataEngine'
import { useDataMutation } from './useDataMutation'
describe('useDataMutation', () => {
it('should render without failing', async () => {
const mutation: CreateMutation = {
type: 'create',
resource: 'answer',
data: { answer: '?' },
}
const data = { answer: 42 }
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const { result } = renderHook(() => useDataMutation(mutation), {
wrapper,
})
const [mutate, beforeMutation] = result.current
expect(beforeMutation).toMatchObject({
loading: false,
called: false,
})
act(() => {
mutate()
})
await waitFor(() => {
const [, duringMutation] = result.current
expect(duringMutation).toMatchObject({
loading: true,
called: true,
})
})
await waitFor(() => {
const [, afterMutation] = result.current
expect(afterMutation).toMatchObject({
loading: false,
called: true,
data: 42,
})
})
})
it('should run immediately with lazy: false', async () => {
const mutation: CreateMutation = {
type: 'create',
resource: 'answer',
data: { answer: '?' },
}
const data = { answer: 42 }
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const { result } = renderHook(
() => useDataMutation(mutation, { lazy: false }),
{ wrapper }
)
const [, duringMutation] = result.current
expect(duringMutation).toMatchObject({
loading: true,
called: true,
})
await waitFor(() => {
const [, afterMutation] = result.current
expect(afterMutation).toMatchObject({
loading: false,
called: true,
data: 42,
})
})
})
it('should call onComplete on success', async () => {
const onComplete = jest.fn()
const mutation: CreateMutation = {
type: 'create',
resource: 'answer',
data: { answer: '?' },
}
const data = { answer: 42 }
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const { result } = renderHook(
() => useDataMutation(mutation, { onComplete }),
{ wrapper }
)
expect(onComplete).toHaveBeenCalledTimes(0)
const [mutate] = result.current
act(() => {
mutate()
})
await waitFor(() => {
const [, state] = result.current
expect(state).toMatchObject({
loading: false,
called: true,
data: 42,
})
expect(onComplete).toHaveBeenCalledTimes(1)
expect(onComplete).toHaveBeenLastCalledWith(42)
})
})
it('should call onError on error', async () => {
const error = new Error('Something went wrong')
const onError = jest.fn()
const mutation: CreateMutation = {
type: 'create',
resource: 'answer',
data: { answer: 42 },
}
const data = {
answer: () => {
throw error
},
}
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const { result } = renderHook(
() => useDataMutation(mutation, { onError }),
{ wrapper }
)
expect(onError).toHaveBeenCalledTimes(0)
const [mutate] = result.current
act(() => {
mutate()
})
await waitFor(() => {
const [, state] = result.current
expect(state).toMatchObject({
loading: false,
called: true,
error,
})
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenLastCalledWith(error)
})
it('should resolve variables', async () => {
const mutation = {
type: 'update',
resource: 'answer',
id: ({ id }: QueryVariables) => id as string,
data: { answer: '?' },
} as unknown as UpdateMutation
const answerSpy = jest.fn(() => Promise.resolve(42))
const data = { answer: answerSpy }
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const { result } = renderHook(
() =>
useDataMutation(mutation, {
lazy: false,
variables: { id: '1' },
}),
{ wrapper }
)
await waitFor(() => {
expect(answerSpy).toHaveBeenLastCalledWith(
expect.any(String),
expect.objectContaining({
id: '1',
}),
expect.any(Object)
)
})
const [mutate] = result.current
act(() => {
mutate({ id: '2' })
})
await waitFor(() => {
expect(answerSpy).toHaveBeenLastCalledWith(
expect.any(String),
expect.objectContaining({
id: '2',
}),
expect.any(Object)
)
})
})
it('should return a reference to the engine', async () => {
const mutation: CreateMutation = {
type: 'create',
resource: 'answer',
data: { answer: '?' },
}
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const engineHook = renderHook(() => useDataEngine(), { wrapper })
const mutationHook = renderHook(() => useDataMutation(mutation), {
wrapper,
})
/**
* Ideally we'd check referential equality here with .toBe, but since
* both hooks run in a different context that doesn't work.
*/
expect(mutationHook.result.current[1].engine).toStrictEqual(
engineHook.result.current
)
})
it('should return a stable mutate function', async () => {
const mutation: CreateMutation = {
type: 'create',
resource: 'answer',
data: { answer: '?' },
}
const data = { answer: 42 }
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const { result } = renderHook(() => useDataMutation(mutation), {
wrapper,
})
const [firstMutate] = result.current
await act(async () => {
await firstMutate({ variable: 'variable' })
})
const [secondMutate, state] = result.current
expect(state).toMatchObject({
loading: false,
called: true,
})
expect(firstMutate).toBe(secondMutate)
})
it('should resolve with the data from mutate on success', async () => {
const mutation: CreateMutation = {
type: 'create',
resource: 'answer',
data: { answer: '?' },
}
const data = { answer: 42 }
const wrapper = ({ children }: { children?: React.ReactNode }) => (
{children}
)
const { result } = renderHook(() => useDataMutation(mutation), {
wrapper,
})
let mutatePromise!: Promise
const [mutate] = result.current
act(() => {
mutatePromise = mutate()
})
await waitFor(() => {
const [, state] = result.current
expect(state).toMatchObject({
loading: false,
called: true,
data: 42,
})
expect(mutatePromise).resolves.toBe(42)
})
})
})