Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions packages/use-dataloader/src/DataLoaderProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,21 @@ import {
import {
DEFAULT_MAX_CONCURRENT_REQUESTS,
KEY_IS_NOT_STRING_ERROR,
StatusEnum,
} from './constants'
import DataLoader from './dataloader'
import { NeedPollingType, OnErrorFn, PromiseType } from './types'
import { OnErrorFn, PromiseType } from './types'

type CachedData = Record<string, unknown>
type Reloads = Record<string, () => Promise<void | unknown>>
type Requests = Record<string, DataLoader>

type UseDataLoaderInitializerArgs<T = unknown> = {
status?: StatusEnum
method: () => PromiseType<T>
pollingInterval?: number
keepPreviousData?: boolean
/**
* Max time before data from previous success is considered as outdated (in millisecond)
*/
maxDataLifetime?: number
needPolling?: NeedPollingType<T>
enabled?: boolean
}

type GetCachedDataFn = {
Expand Down Expand Up @@ -72,7 +69,8 @@ const DataLoaderProvider = ({
onError: OnErrorFn
maxConcurrentRequests?: number
}): ReactElement => {
const requestsRef = useRef<Record<string, DataLoader>>({})
const requestsRef = useRef<Requests>({})

const computeKey = useCallback(
(key: string) => `${cacheKeyPrefix ? `${cacheKeyPrefix}-` : ''}${key}`,
[cacheKeyPrefix],
Expand Down
185 changes: 75 additions & 110 deletions packages/use-dataloader/src/__tests__/DataLoaderProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { render, screen } from '@testing-library/react'
import { act, renderHook } from '@testing-library/react-hooks'
import { render, renderHook, screen, waitFor } from '@testing-library/react'
import { ReactNode } from 'react'
import DataLoaderProvider, { useDataLoaderContext } from '../DataLoaderProvider'
import { KEY_IS_NOT_STRING_ERROR, StatusEnum } from '../constants'
Expand Down Expand Up @@ -40,134 +39,105 @@ describe('DataLoaderProvider', () => {

test('should add request', async () => {
const method = jest.fn(fakePromise)
const { result, waitFor } = renderHook(useDataLoaderContext, {
const { result, rerender } = renderHook(useDataLoaderContext, {
wrapper,
})
expect(result.current.getRequest(TEST_KEY)).toBeUndefined()

act(() => {
result.current.addRequest(TEST_KEY, {
method,
})
result.current.addRequest(TEST_KEY, {
method,
})

expect(Object.values(result.current.getReloads()).length).toBe(1)
const testRequest = result.current.getRequest(TEST_KEY)
testRequest.addObserver(rerender)

expect(testRequest).toBeDefined()
expect(testRequest.status).toBe(StatusEnum.IDLE)
act(() => {
// launch should never throw
// eslint-disable-next-line @typescript-eslint/no-floating-promises
testRequest.load()
})
expect(method).toBeCalledTimes(1)
testRequest.load().catch(undefined)
expect(testRequest.status).toBe(StatusEnum.LOADING)
expect(method).toBeCalledTimes(1)
await waitFor(() => expect(testRequest.status).toBe(StatusEnum.SUCCESS))
expect(result.current.getCachedData(TEST_KEY)).toBe(true)
await act(async () => {
await result.current.reload(TEST_KEY)
})

const observer = jest.fn()
act(() => {
const request = result.current.getRequest(TEST_KEY)
request.addObserver(observer)
expect(request.getObserversCount()).toBe(1)
})
try {
// @ts-expect-error Should throw an error
await result.current.reload(3).catch(undefined)
fail('It should throw an error')
} catch (error) {
expect((error as Error).message).toBe(KEY_IS_NOT_STRING_ERROR)
}
result.current.reload(TEST_KEY).catch(undefined)
await waitFor(() => expect(testRequest.status).toBe(StatusEnum.LOADING))
await waitFor(() => expect(testRequest.status).toBe(StatusEnum.SUCCESS))
try {
// @ts-expect-error Should throw an error
result.current.clearCachedData(3)
fail('It should throw an error')
} catch (error) {
expect((error as Error).message).toBe(KEY_IS_NOT_STRING_ERROR)
expect(result.current.getCachedData(TEST_KEY)).toBe(true)
}
})

test('should add request with cache key prefix', async () => {
const method = jest.fn(fakePromise)
const { result, waitFor } = renderHook(useDataLoaderContext, {
const { result } = renderHook(useDataLoaderContext, {
wrapper: wrapperWithCacheKey,
})
expect(result.current.getRequest(TEST_KEY)).toBeUndefined()

act(() => {
result.current.addRequest(TEST_KEY, {
method,
})
result.current.addRequest(TEST_KEY, {
method,
})

const testRequest = result.current.getRequest(TEST_KEY)
expect(Object.values(result.current.getReloads()).length).toBe(1)
expect(testRequest).toBeDefined()
expect(testRequest.status).toBe(StatusEnum.IDLE)
act(() => {
// launch should never throw
// eslint-disable-next-line @typescript-eslint/no-floating-promises
testRequest.load()
})
testRequest.load().catch(undefined)
await waitFor(() => expect(testRequest.status).toBe(StatusEnum.SUCCESS))
expect(method).toBeCalledTimes(1)
await waitFor(() => expect(testRequest.getData()).toBe(true))
expect(testRequest.data).toBe(true)
expect(result.current.getCachedData(TEST_KEY)).toBe(true)
await act(async () => {
await result.current.reload(TEST_KEY)
})
result.current.reload(TEST_KEY).catch(undefined)
await waitFor(() => expect(testRequest.status).toBe(StatusEnum.LOADING))
await waitFor(() => expect(testRequest.status).toBe(StatusEnum.SUCCESS))
})

test('should add request with result is null', async () => {
const method = jest.fn(fakeNullPromise)
const { result, waitFor } = renderHook(useDataLoaderContext, {
const { result } = renderHook(useDataLoaderContext, {
wrapper,
})
act(() => {
const request = result.current.getOrAddRequest(TEST_KEY, {
method,
})
result.current.getOrAddRequest(TEST_KEY, {
method,
})
// eslint-disable-next-line @typescript-eslint/no-floating-promises
request.load()
const request = result.current.getOrAddRequest(TEST_KEY, {
method,
})
await request.load()
expect(method).toBeCalledTimes(1)
await waitFor(() =>
expect(result.current.getRequest(TEST_KEY).status).toBe(
StatusEnum.SUCCESS,
),
)
expect(result.current.getRequest(TEST_KEY).status).toBe(StatusEnum.SUCCESS)
const reloads = result.current.getReloads()
expect(reloads).toHaveProperty(TEST_KEY)
const testReload = result.current.getReloads(TEST_KEY)
expect(result.current.getCachedData(TEST_KEY)).toBe(undefined)
expect(result.current.getCachedData()).toStrictEqual({ test: undefined })
expect(testReload).toBeDefined()
if (testReload) {
expect(await testReload()).toBeNull()
} else {
fail('It shoulded be defined')
}
expect(result.current.getCachedData(TEST_KEY)).toBe(null)
expect(result.current.getCachedData()).toStrictEqual({ test: null })
expect(result.current.getRequest(TEST_KEY)).toBeDefined()
const unknownReload = result.current.getReloads('unknown')
expect(unknownReload).toBeUndefined()
await act(async () => {
await reloads.test()
})
expect(method).toBeCalledTimes(2)
expect(testReload).toBeDefined()
await act(async () => {
await (testReload as () => Promise<unknown>)()
})
await reloads.test()
expect(method).toBeCalledTimes(3)
await act(result.current.reloadAll)
await result.current.reloadAll()
expect(method).toBeCalledTimes(4)
act(() => result.current.clearCachedData(TEST_KEY))
act(() => result.current.clearCachedData('unknown'))
result.current.clearCachedData(TEST_KEY)
result.current.clearCachedData('unknown')
expect(result.current.getCachedData(TEST_KEY)).toBeUndefined()
act(() => result.current.clearAllCachedData())
result.current.clearAllCachedData()
expect(result.current.getCachedData()).toStrictEqual({ test: undefined })

try {
// @ts-expect-error Should throw an error
result.current.clearCachedData(3)
} catch (e) {
expect((e as Error).message).toBe(KEY_IS_NOT_STRING_ERROR)
}
expect(result.current.getCachedData(TEST_KEY)).toBe(undefined)
expect(result.current.getCachedData()).toStrictEqual({
test: undefined,
})
try {
// @ts-expect-error Should throw an error
await result.current.reload(3)
} catch (e) {
expect((e as Error).message).toBe(KEY_IS_NOT_STRING_ERROR)
}
})

test('should add request with bad key', () => {
Expand All @@ -176,11 +146,9 @@ describe('DataLoaderProvider', () => {
wrapper,
})
try {
act(() => {
// @ts-expect-error used because we test with bad key
result.current.addRequest(3, {
method,
})
// @ts-expect-error used because we test with bad key
result.current.addRequest(3, {
method,
})
} catch (e) {
expect((e as Error).message).toBe(KEY_IS_NOT_STRING_ERROR)
Expand All @@ -194,30 +162,27 @@ describe('DataLoaderProvider', () => {
setTimeout(() => resolve(true), 100)
}),
)
const { result, waitFor } = renderHook(useDataLoaderContext, {
const { result } = renderHook(useDataLoaderContext, {
wrapper: wrapperWith2ConcurrentRequests,
})
act(() => {
;[
result.current.addRequest(TEST_KEY, {
method,
}),
result.current.addRequest(`${TEST_KEY}-2`, {
method,
}),
result.current.addRequest(`${TEST_KEY}-3`, {
method,
}),
result.current.addRequest(`${TEST_KEY}-4`, {
method,
}),
result.current.addRequest(`${TEST_KEY}-5`, {
method,
}),
].forEach(request => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
request.load()
})
;[
result.current.addRequest(TEST_KEY, {
method,
}),
result.current.addRequest(`${TEST_KEY}-2`, {
method,
}),
result.current.addRequest(`${TEST_KEY}-3`, {
method,
}),
result.current.addRequest(`${TEST_KEY}-4`, {
method,
}),
result.current.addRequest(`${TEST_KEY}-5`, {
method,
}),
].forEach(request => {
request.load().catch(undefined)
})
expect(method).toBeCalledTimes(2)
await waitFor(() => expect(method).toBeCalledTimes(4))
Expand Down
Loading