deno.land / x / jotai@v1.8.4 / tests / utils / waitForAll.test.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374import { Component, StrictMode, Suspense, useEffect } from 'react'import type { ReactNode } from 'react'import { fireEvent, render, waitFor } from '@testing-library/react'import { atom, useAtom, useSetAtom } from 'jotai'import { atomFamily, waitForAll } from 'jotai/utils'import { getTestProvider } from '../testUtils'
const Provider = getTestProvider()
const consoleWarn = console.warnconst consoleError = console.errorbeforeEach(() => { console.warn = jest.fn() console.error = jest.fn()})afterEach(() => { console.warn = consoleWarn console.error = consoleError})
class ErrorBoundary extends Component< { message?: string; children: ReactNode }, { hasError: boolean }> { constructor(props: { message?: string; children: ReactNode }) { super(props) this.state = { hasError: false } } static getDerivedStateFromError() { return { hasError: true } } render() { return this.state.hasError ? ( <div>{this.props.message || 'errored'}</div> ) : ( this.props.children ) }}
it('waits for two async atoms', async () => { let isAsyncAtomRunning = false let isAnotherAsyncAtomRunning = false const asyncAtom = atom(async () => { isAsyncAtomRunning = true await new Promise((resolve) => { setTimeout(() => { isAsyncAtomRunning = false resolve(true) }, 100) }) return 1 }) const anotherAsyncAtom = atom(async () => { isAnotherAsyncAtomRunning = true await new Promise((resolve) => { setTimeout(() => { isAnotherAsyncAtomRunning = false resolve(true) }, 100) }) return 'a' })
const Counter = () => { const [[num, str]] = useAtom(waitForAll([asyncAtom, anotherAsyncAtom])) return ( <div> num: {num * 1}, str: {str.toLowerCase()} </div> ) }
const { getByText } = render( <StrictMode> <Provider> <Suspense fallback="loading"> <Counter /> </Suspense> </Provider> </StrictMode> )
await waitFor(() => { getByText('loading') expect(isAsyncAtomRunning).toBe(true) expect(isAnotherAsyncAtomRunning).toBe(true) })
await waitFor(() => { getByText('num: 1, str: a') expect(isAsyncAtomRunning).toBe(false) expect(isAnotherAsyncAtomRunning).toBe(false) })})
it('can use named atoms in derived atom', async () => { let isAsyncAtomRunning = false let isAnotherAsyncAtomRunning = false const asyncAtom = atom(async () => { isAsyncAtomRunning = true await new Promise((resolve) => { setTimeout(() => { isAsyncAtomRunning = false resolve(true) }, 100) }) return 1 }) const anotherAsyncAtom = atom(async () => { isAnotherAsyncAtomRunning = true await new Promise((resolve) => { setTimeout(() => { isAnotherAsyncAtomRunning = false resolve(true) }, 100) }) return 'a' })
const combinedWaitingAtom = atom((get) => { const { num, str } = get( waitForAll({ num: asyncAtom, str: anotherAsyncAtom, }) ) return { num, str } })
const Counter = () => { const [{ num, str }] = useAtom(combinedWaitingAtom) return ( <div> num: {num}, str: {str} </div> ) }
const { getByText } = render( <StrictMode> <Provider> <Suspense fallback="loading"> <Counter /> </Suspense> </Provider> </StrictMode> )
await waitFor(() => { getByText('loading') expect(isAsyncAtomRunning).toBe(true) expect(isAnotherAsyncAtomRunning).toBe(true) })
await waitFor(() => { getByText('num: 1, str: a') expect(isAsyncAtomRunning).toBe(false) expect(isAnotherAsyncAtomRunning).toBe(false) })})
it('can handle errors', async () => { let isAsyncAtomRunning = false let isErrorAtomRunning = false const asyncAtom = atom(async () => { isAsyncAtomRunning = true await new Promise((resolve) => { setTimeout(() => { isAsyncAtomRunning = false resolve(true) }, 100) }) return 1 }) const errorAtom = atom(async () => { isErrorAtomRunning = true await new Promise((_, reject) => { setTimeout(() => { isErrorAtomRunning = false reject(false) }, 100) }) return 'a' })
const combinedWaitingAtom = atom((get) => { return get( waitForAll({ num: asyncAtom, error: errorAtom, }) ) })
const Counter = () => { const [{ num, error }] = useAtom(combinedWaitingAtom) return ( <> <div>num: {num}</div> <div>str: {error}</div> </> ) }
const { getByText } = render( <StrictMode> <Provider> <ErrorBoundary> <Suspense fallback="loading"> <Counter /> </Suspense> </ErrorBoundary> </Provider> </StrictMode> )
await waitFor(() => { getByText('loading') expect(isAsyncAtomRunning).toBe(true) expect(isErrorAtomRunning).toBe(true) })
await waitFor(() => { getByText('errored') expect(isAsyncAtomRunning).toBe(false) expect(isErrorAtomRunning).toBe(false) })})
it('handles scope', async () => { const scope = Symbol() let isAsyncAtomRunning = false let isAnotherAsyncAtomRunning = false const valueAtom = atom(1) const asyncAtom = atom(async (get) => { isAsyncAtomRunning = true await new Promise((resolve) => { setTimeout(() => { isAsyncAtomRunning = false resolve(true) }, 100) }) return get(valueAtom) })
const anotherAsyncAtom = atom(async () => { isAnotherAsyncAtomRunning = true await new Promise((resolve) => { setTimeout(() => { isAnotherAsyncAtomRunning = false resolve(true) }, 100) }) return '2' })
const Counter = () => { const [[num1, num2]] = useAtom( waitForAll([asyncAtom, anotherAsyncAtom]), scope ) const setValue = useSetAtom(valueAtom, scope) return ( <> <div> num1: {num1}, num2: {num2} </div> <button onClick={() => setValue((v) => v + 1)}>increment</button> </> ) }
const { getByText, findByText } = render( <StrictMode> <Provider scope={scope}> <Suspense fallback="loading"> <Counter /> </Suspense> </Provider> </StrictMode> )
await waitFor(() => { getByText('loading') expect(isAsyncAtomRunning).toBe(true) expect(isAnotherAsyncAtomRunning).toBe(true) })
await waitFor(() => { getByText('num1: 1, num2: 2') expect(isAsyncAtomRunning).toBe(false) expect(isAnotherAsyncAtomRunning).toBe(false) })
await new Promise((r) => setTimeout(r, 100)) fireEvent.click(getByText('increment')) await findByText('loading') await findByText('num1: 2, num2: 2')})
it('large atom count', async () => { const createArray = (n: number) => Array(n) .fill(0) .map((_, i) => i)
let result: number[] | null = null
const chunksFamily = atomFamily((i: number) => atom(() => i))
const selector = atomFamily((count: number) => atom((getter) => { const data = createArray(count) const atoms = data.map(chunksFamily) const values = waitForAll(atoms) return getter(values) }) )
const Loader = ({ count }: { count: number }) => { const [value, _] = useAtom(selector(count))
useEffect(() => { result = value }, [value])
return <div></div> }
const passingCount = 500 render( <StrictMode> <Provider> <Loader count={passingCount} /> </Provider> </StrictMode> )
await waitFor(() => { expect(result).toEqual(createArray(passingCount)) })
const failingCount = 8000 render( <StrictMode> <Provider> <Loader count={failingCount} /> </Provider> </StrictMode> )
await waitFor(() => { expect(result).toEqual(createArray(failingCount)) })})
it('works with an empty list (#1177)', async () => { const Component = () => { useAtom(waitForAll([])) return <div>works!</div> }
const { findByText } = render( <StrictMode> <Provider> <Component /> </Provider> </StrictMode> )
await findByText('works!')})
Version Info