Skip to main content
Module

x/jotai/tests/onmount.test.tsx

👻 Primitive and flexible state management for React
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
import { StrictMode, Suspense, useState } from 'react'import { act, fireEvent, render, waitFor } from '@testing-library/react'import { atom, useAtom } from 'jotai'import { StrictModeUnlessVersionedWrite, getTestProvider } from './testUtils'
const Provider = getTestProvider()
it('one atom, one effect', async () => { const countAtom = atom(1) const onMountFn = jest.fn() countAtom.onMount = onMountFn
const Counter = () => { const [count, setCount] = useAtom(countAtom) return ( <> <div>count: {count}</div> <button onClick={() => setCount((c) => c + 1)}>button</button> </> ) }
const { getByText, findByText } = render( <> <Provider> <Counter /> </Provider> </> )
await findByText('count: 1') expect(onMountFn).toBeCalledTimes(1)
fireEvent.click(getByText('button')) await findByText('count: 2') expect(onMountFn).toBeCalledTimes(1)})
it('two atoms, one each', async () => { const countAtom = atom(1) const countAtom2 = atom(1) const onMountFn = jest.fn() const onMountFn2 = jest.fn() countAtom.onMount = onMountFn countAtom2.onMount = onMountFn2
const Counter = () => { const [count, setCount] = useAtom(countAtom) const [count2, setCount2] = useAtom(countAtom2) return ( <> <div>count: {count}</div> <div>count2: {count2}</div> <button onClick={() => { setCount((c) => c + 1) setCount2((c) => c + 1) }}> button </button> </> ) }
const { getByText } = render( <> <Provider> <Counter /> </Provider> </> )
await waitFor(() => { getByText('count: 1') getByText('count2: 1') }) expect(onMountFn).toBeCalledTimes(1) expect(onMountFn2).toBeCalledTimes(1)
fireEvent.click(getByText('button')) await waitFor(() => { getByText('count: 2') getByText('count2: 2') })
expect(onMountFn).toBeCalledTimes(1) expect(onMountFn2).toBeCalledTimes(1)})
it('one derived atom, one onMount', async () => { const countAtom = atom(1) const countAtom2 = atom((get) => get(countAtom)) const onMountFn = jest.fn() countAtom.onMount = onMountFn
const Counter = () => { const [count] = useAtom(countAtom2) return ( <> <div>count: {count}</div> </> ) }
const { findByText } = render( <> <Provider> <Counter /> </Provider> </> )
await findByText('count: 1') expect(onMountFn).toBeCalledTimes(1)})
it('mount/unmount test', async () => { const countAtom = atom(1)
const onUnMountFn = jest.fn() const onMountFn = jest.fn(() => onUnMountFn) countAtom.onMount = onMountFn
const Counter = () => { const [count] = useAtom(countAtom) return ( <> <div>count: {count}</div> </> ) }
const Display = () => { const [display, setDisplay] = useState(true) return ( <> {display ? <Counter /> : null} <button onClick={() => setDisplay((c) => !c)}>button</button> </> ) }
const { getByText } = render( <> <Provider> <Display /> </Provider> </> )
expect(onMountFn).toBeCalledTimes(1) expect(onUnMountFn).toBeCalledTimes(0)
fireEvent.click(getByText('button')) // FIXME is there a better way? await waitFor(() => {})
expect(onMountFn).toBeCalledTimes(1) expect(onUnMountFn).toBeCalledTimes(1)})
it('one derived atom, one onMount for the derived one, and one for the regular atom + onUnMount', async () => { const countAtom = atom(1) const derivedAtom = atom( (get) => get(countAtom), (_get, set, update: number) => { set(countAtom, update) set(derivedAtom, update) } ) const onUnMountFn = jest.fn() const onMountFn = jest.fn(() => onUnMountFn) countAtom.onMount = onMountFn const derivedOnUnMountFn = jest.fn() const derivedOnMountFn = jest.fn(() => derivedOnUnMountFn) derivedAtom.onMount = derivedOnMountFn
const Counter = () => { const [count] = useAtom(derivedAtom) return ( <> <div>count: {count}</div> </> ) }
const Display = () => { const [display, setDisplay] = useState(true) return ( <> {display ? <Counter /> : null} <button onClick={() => setDisplay((c) => !c)}>button</button> </> ) }
const { getByText } = render( <> <Provider> <Display /> </Provider> </> ) expect(derivedOnMountFn).toBeCalledTimes(1) expect(derivedOnUnMountFn).toBeCalledTimes(0) expect(onMountFn).toBeCalledTimes(1) expect(onUnMountFn).toBeCalledTimes(0)
fireEvent.click(getByText('button')) // FIXME is there a better way? await waitFor(() => {})
expect(derivedOnMountFn).toBeCalledTimes(1) expect(derivedOnUnMountFn).toBeCalledTimes(1) expect(onMountFn).toBeCalledTimes(1) expect(onUnMountFn).toBeCalledTimes(1)})
it('mount/unMount order', async () => { const committed: number[] = [0, 0] const countAtom = atom(1) const derivedAtom = atom( (get) => get(countAtom), (_get, set, update: number) => { set(countAtom, update) set(derivedAtom, update) } ) const onUnMountFn = jest.fn(() => { committed[0] = 0 }) const onMountFn = jest.fn(() => { committed[0] = 1 return onUnMountFn }) countAtom.onMount = onMountFn const derivedOnUnMountFn = jest.fn(() => { committed[1] = 0 }) const derivedOnMountFn = jest.fn(() => { committed[1] = 1 return derivedOnUnMountFn }) derivedAtom.onMount = derivedOnMountFn
const Counter2 = () => { const [count] = useAtom(derivedAtom) return ( <> <div>count: {count}</div> </> ) } const Counter = () => { const [count] = useAtom(countAtom) const [display, setDisplay] = useState(false) return ( <> <div>count: {count}</div> <button onClick={() => setDisplay((c) => !c)}>derived atom</button> {display ? <Counter2 /> : null} </> ) }
const Display = () => { const [display, setDisplay] = useState(false) return ( <> {display ? <Counter /> : null} <button onClick={() => setDisplay((c) => !c)}>button</button> </> ) }
const { getByText } = render( <StrictMode> <Provider> <Display /> </Provider> </StrictMode> ) expect(committed).toEqual([0, 0])
fireEvent.click(getByText('button')) // FIXME is there a better way? await waitFor(() => {})
expect(committed).toEqual([1, 0])
fireEvent.click(getByText('derived atom')) // FIXME is there a better way? await waitFor(() => {})
expect(committed).toEqual([1, 1])
fireEvent.click(getByText('derived atom')) // FIXME is there a better way? await waitFor(() => {})
expect(committed).toEqual([1, 0])
fireEvent.click(getByText('button')) // FIXME is there a better way? await waitFor(() => {})
expect(committed).toEqual([0, 0])})
it('mount/unmount test with async atom', async () => { const countAtom = atom( async () => { await new Promise((r) => setTimeout(r, 100)) return 0 }, () => {} )
const onUnMountFn = jest.fn() const onMountFn = jest.fn(() => onUnMountFn) countAtom.onMount = onMountFn
const Counter = () => { const [count] = useAtom(countAtom) return ( <> <div>count: {count}</div> </> ) }
const Display = () => { const [display, setDisplay] = useState(true) return ( <> {display ? <Counter /> : null} <button onClick={() => setDisplay((c) => !c)}>button</button> </> ) }
const { getByText, findByText } = render( <> <Provider> <Suspense fallback="loading"> <Display /> </Suspense> </Provider> </> )
await findByText('loading') await waitFor(() => { getByText('count: 0') expect(onMountFn).toBeCalledTimes(1) expect(onUnMountFn).toBeCalledTimes(0) })
fireEvent.click(getByText('button')) expect(onMountFn).toBeCalledTimes(1) expect(onUnMountFn).toBeCalledTimes(1)})
it('subscription usage test', async () => { const store = { count: 10, listeners: new Set<() => void>(), inc: () => { store.count += 1 store.listeners.forEach((listener) => listener()) }, }
const countAtom = atom(1) countAtom.onMount = (setCount) => { const callback = () => { setCount(store.count) } store.listeners.add(callback) callback() return () => store.listeners.delete(callback) }
const Counter = () => { const [count] = useAtom(countAtom) return ( <> <div>count: {count}</div> </> ) }
const Display = () => { const [display, setDisplay] = useState(true) return ( <> {display ? <Counter /> : 'N/A'} <button onClick={() => setDisplay((c) => !c)}>button</button> </> ) }
const { getByText, findByText } = render( <StrictMode> <Provider> <Display /> </Provider> </StrictMode> )
await findByText('count: 10')
act(() => { store.inc() }) await findByText('count: 11')
fireEvent.click(getByText('button')) await findByText('N/A')
fireEvent.click(getByText('button')) await findByText('count: 11')
fireEvent.click(getByText('button')) await findByText('N/A')
act(() => { store.inc() }) await findByText('N/A')
fireEvent.click(getByText('button')) await findByText('count: 12')})
it('subscription in base atom test', async () => { const store = { count: 10, listeners: new Set<() => void>(), add: (n: number) => { store.count += n store.listeners.forEach((listener) => listener()) }, }
const countAtom = atom(1) countAtom.onMount = (setCount) => { const callback = () => { setCount(store.count) } store.listeners.add(callback) callback() return () => store.listeners.delete(callback) } const derivedAtom = atom( (get) => get(countAtom), (_get, _set, n: number) => { store.add(n) } )
const Counter = () => { const [count, add] = useAtom(derivedAtom) return ( <> <div>count: {count}</div> <button onClick={() => add(1)}>button</button> </> ) }
const { getByText, findByText } = render( <StrictModeUnlessVersionedWrite> <Provider> <Counter /> </Provider> </StrictModeUnlessVersionedWrite> )
await findByText('count: 10')
fireEvent.click(getByText('button')) await findByText('count: 11')
fireEvent.click(getByText('button')) await findByText('count: 12')})
it('create atom with onMount in async get', async () => { const store = { count: 10, listeners: new Set<() => void>(), add: (n: number) => { store.count += n store.listeners.forEach((listener) => listener()) }, }
const holderAtom = atom(async () => { const countAtom = atom(1) countAtom.onMount = (setCount) => { const callback = () => { setCount(store.count) } store.listeners.add(callback) callback() return () => store.listeners.delete(callback) } return countAtom }) const derivedAtom = atom( (get) => get(get(holderAtom)), (_get, _set, n: number) => { store.add(n) } )
const Counter = () => { const [count, add] = useAtom(derivedAtom) return ( <> <div>count: {count}</div> <button onClick={() => add(1)}>button</button> </> ) }
const { getByText, findByText } = render( <StrictModeUnlessVersionedWrite> <Provider> <Suspense fallback="loading"> <Counter /> </Suspense> </Provider> </StrictModeUnlessVersionedWrite> )
await findByText('count: 10')
fireEvent.click(getByText('button')) await findByText('count: 11')
fireEvent.click(getByText('button')) await findByText('count: 12')})