diff --git a/__tests__/atomEffect.strict.test.ts b/__tests__/atomEffect.strict.test.ts
index 63da819..5428d34 100644
--- a/__tests__/atomEffect.strict.test.ts
+++ b/__tests__/atomEffect.strict.test.ts
@@ -1,4 +1,4 @@
-import { StrictMode, useEffect } from 'react'
+import { StrictMode } from 'react'
import { act, renderHook, waitFor } from '@testing-library/react'
import { useAtom, useAtomValue, useSetAtom } from 'jotai/react'
import { atom, getDefaultStore } from 'jotai/vanilla'
@@ -7,26 +7,21 @@ import { assert, delay, increment, incrementLetter } from './test-utils'
const wrapper = StrictMode
-it('should run the effect on mount and cleanup on unmount once', async () => {
+it('should run the effect on mount and cleanup on unmount once', () => {
expect.assertions(5)
const effect = { mount: 0, unmount: 0 }
- let hasMounted = false
const effectAtom = atomEffect(() => {
effect.mount++
- hasMounted = true
return () => {
effect.unmount++
}
})
- let hasRun = false
function useTest() {
- hasRun = true
return useAtomValue(effectAtom)
}
const { result, rerender, unmount } = renderHook(useTest, { wrapper })
- await waitFor(() => assert(hasRun && hasMounted))
// effect does not return a value
expect(result.current).toBe(undefined)
// initial render should run the effect
@@ -44,7 +39,7 @@ it('should run the effect on mount and cleanup on unmount once', async () => {
expect(effect.unmount).toBe(1)
})
-it('should run the effect on mount and cleanup on unmount and whenever countAtom changes', async () => {
+it('should run the effect on mount and cleanup on unmount and whenever countAtom changes', () => {
expect.assertions(11)
const effect = { mount: 0, unmount: 0 }
@@ -58,21 +53,16 @@ it('should run the effect on mount and cleanup on unmount and whenever countAtom
}
})
- let didMount = false
function useTest() {
const [count, setCount] = useAtom(countAtom)
useAtomValue(effectAtom)
- useEffect(() => {
- didMount = true
- }, [count])
return setCount
}
const { result, rerender, unmount } = renderHook(useTest, { wrapper })
- async function incrementCount() {
+ function incrementCount() {
const setCount = result.current
- await act(async () => setCount(increment))
+ act(() => setCount(increment))
}
- await waitFor(() => assert(didMount && effect.mount === 1))
// initial render should run the effect but not the cleanup
expect(effect.unmount).toBe(0)
@@ -83,13 +73,13 @@ it('should run the effect on mount and cleanup on unmount and whenever countAtom
expect(effect.unmount).toBe(0)
expect(effect.mount).toBe(1)
- await incrementCount()
+ incrementCount()
// changing the value should run the effect again and the previous cleanup
expect(effect.unmount).toBe(1)
expect(effect.mount).toBe(2)
- await incrementCount()
+ incrementCount()
// changing the value should run the effect again and the previous cleanup
expect(effect.unmount).toBe(2)
@@ -107,7 +97,7 @@ it('should run the effect on mount and cleanup on unmount and whenever countAtom
expect(effect.unmount).toBe(3)
})
-it('should not cause infinite loops when effect updates the watched atom', async () => {
+it('should not cause infinite loops when effect updates the watched atom', () => {
expect.assertions(1)
const watchedAtom = atom(0)
let runCount = 0
@@ -125,11 +115,8 @@ it('should not cause infinite loops when effect updates the watched atom', async
}
const { rerender } = renderHook(useTest, { wrapper })
- // initial render should run the effect once
- await waitFor(() => assert(runCount === 1))
// rerender should not run the effect again
rerender()
- await delay(0)
expect({ runCount, watched: store.get(watchedAtom) }).toEqual({
runCount: 1,
@@ -151,7 +138,7 @@ it('should not cause infinite loops when effect updates the watched atom asynchr
function useTest() {
useAtom(effectAtom)
const setCount = useSetAtom(watchedAtom)
- return () => act(async () => setCount(increment))
+ return () => act(() => setCount(increment))
}
const { result } = renderHook(useTest, { wrapper })
await delay(0)
@@ -164,7 +151,7 @@ it('should not cause infinite loops when effect updates the watched atom asynchr
expect(runCount).toBe(2)
})
-it('should allow synchronous infinite loops with opt-in for first run', async () => {
+it('should allow synchronous infinite loops with opt-in for first run', () => {
expect.assertions(1)
let runCount = 0
const watchedAtom = atom(0)
@@ -179,18 +166,16 @@ it('should allow synchronous infinite loops with opt-in for first run', async ()
function useTest() {
useAtom(effectAtom, { store })
const setCount = useSetAtom(watchedAtom, { store })
- return () => act(async () => setCount(increment))
+ return () => act(() => setCount(increment))
}
const { result } = renderHook(useTest, { wrapper })
- await delay(0)
- await act(async () => result.current())
- await delay(0)
+ act(() => result.current())
expect({ runCount, watched: store.get(watchedAtom) }).toEqual({
runCount: 7, // extra run for strict mode render
watched: 6,
})
})
-it('should conditionally run the effect and cleanup when effectAtom is unmounted', async () => {
+it('should conditionally run the effect and cleanup when effectAtom is unmounted', () => {
expect.assertions(6)
const booleanAtom = atom(false)
@@ -215,19 +200,19 @@ it('should conditionally run the effect and cleanup when effectAtom is unmounted
const { result } = renderHook(useTest, { wrapper })
const setBoolean = result.current
- const toggleBoolean = () => act(async () => setBoolean((prev) => !prev))
+ const toggleBoolean = () => act(() => setBoolean((prev) => !prev))
// Initially the effectAtom should not run as booleanAtom is false
expect(effectRunCount).toBe(0)
expect(cleanupRunCount).toBe(0)
// Set booleanAtom to true, so effectAtom should run
- await toggleBoolean()
+ toggleBoolean()
expect(effectRunCount).toBe(1)
expect(cleanupRunCount).toBe(0)
// Set booleanAtom to false, so effectAtom should cleanup
- await toggleBoolean()
+ toggleBoolean()
expect(effectRunCount).toBe(1)
expect(cleanupRunCount).toBe(1)
})
@@ -384,7 +369,7 @@ describe('should correctly process synchronous updates to the same atom', () =>
it.each(solutions)(
'should correctly process synchronous updates when effectIncrementCountBy is $effectIncrementCountBy and incrementCountBy is $incrementCountBy',
- async ({ effectIncrementCountBy, incrementCountBy, runs }) => {
+ ({ effectIncrementCountBy, incrementCountBy, runs }) => {
expect.assertions(3)
const { result, runCount } = setup({
effectIncrementCountBy,
@@ -393,14 +378,11 @@ describe('should correctly process synchronous updates to the same atom', () =>
const [before, after] = runs
- // initial value after $effectIncrementCountBy synchronous updates in the effect
- await waitFor(() => assert(runCount.current === before.runCount))
-
// initial render should run the effect once
expect(runCount.current).toBe(before.runCount)
// perform $incrementCountBy synchronous updates
- await act(async () => result.current.incrementCount())
+ act(() => result.current.incrementCount())
// final value after synchronous updates and rerun of the effect
expect(result.current.count).toBe(after.resultCount)
@@ -410,7 +392,7 @@ describe('should correctly process synchronous updates to the same atom', () =>
)
})
-it('should not batch effect setStates', async () => {
+it('should not batch effect setStates', () => {
expect.assertions(4)
const valueAtom = atom(0)
const runCount = { current: 0 }
@@ -432,17 +414,17 @@ it('should not batch effect setStates', async () => {
const { result } = renderHook(() => useSetAtom(triggerAtom), { wrapper })
const setTrigger = result.current
- await waitFor(() => assert(runCount.current === 1))
+ waitFor(() => assert(runCount.current === 1))
expect(valueResult.current).toBe(0)
expect(runCount.current).toBe(1)
- await act(async () => setTrigger((x) => !x))
+ act(() => setTrigger((x) => !x))
expect(valueResult.current).toBe(2)
expect(runCount.current).toBe(3) // <--- not batched (we would expect runCount to be 2 if batched)
})
-it('should batch synchronous updates as a single transaction', async () => {
+it('should batch synchronous updates as a single transaction', () => {
expect.assertions(4)
const lettersAtom = atom('a')
lettersAtom.debugLabel = 'lettersAtom'
@@ -450,6 +432,10 @@ it('should batch synchronous updates as a single transaction', async () => {
numbersAtom.debugLabel = 'numbersAtom'
const lettersAndNumbersAtom = atom([] as string[])
lettersAndNumbersAtom.debugLabel = 'lettersAndNumbersAtom'
+ const setLettersAndNumbersAtom = atom(null, (_get, set) => {
+ set(lettersAtom, incrementLetter)
+ set(numbersAtom, increment)
+ })
let runCount = 0
const effectAtom = atomEffect((get, set) => {
runCount++
@@ -462,30 +448,30 @@ it('should batch synchronous updates as a single transaction', async () => {
})
function useTest() {
useAtomValue(effectAtom)
- const setLetters = useSetAtom(lettersAtom)
- const setNumbers = useSetAtom(numbersAtom)
const lettersAndNumbers = useAtomValue(lettersAndNumbersAtom)
- return { setLetters, setNumbers, lettersAndNumbers }
+ const setLettersAndNumbers = useSetAtom(setLettersAndNumbersAtom)
+ return { setLettersAndNumbers, lettersAndNumbers }
}
const { result } = renderHook(useTest, { wrapper })
- const { setLetters, setNumbers } = result.current
- await waitFor(() => assert(!!runCount))
+ const { setLettersAndNumbers } = result.current
expect(runCount).toBe(1)
expect(result.current.lettersAndNumbers).toEqual(['a0'])
- await act(async () => {
- setLetters(incrementLetter)
- setNumbers(increment)
- })
+ act(setLettersAndNumbers)
expect(runCount).toBe(2)
expect(result.current.lettersAndNumbers).toEqual(['a0', 'b1'])
})
-it('should run the effect once even if the effect is mounted multiple times', async () => {
+it('should run the effect once even if the effect is mounted multiple times', () => {
expect.assertions(3)
const lettersAtom = atom('a')
lettersAtom.debugLabel = 'lettersAtom'
const numbersAtom = atom(0)
numbersAtom.debugLabel = 'numbersAtom'
+ const setLettersAndNumbersAtom = atom(null, (_get, set) => {
+ set(lettersAtom, incrementLetter)
+ set(numbersAtom, increment)
+ })
+ setLettersAndNumbersAtom.debugLabel = 'setLettersAndNumbersAtom'
let runCount = 0
const effectAtom = atomEffect((get) => {
runCount++
@@ -515,23 +501,14 @@ it('should run the effect once even if the effect is mounted multiple times', as
useAtomValue(derivedAtom2)
useAtomValue(derivedAtom3)
useAtomValue(derivedAtom4)
- const setLetters = useSetAtom(lettersAtom)
- const setNumbers = useSetAtom(numbersAtom)
- return { setLetters, setNumbers }
+ return useSetAtom(setLettersAndNumbersAtom)
}
const { result } = renderHook(useTest, { wrapper })
- const { setLetters, setNumbers } = result.current
- await waitFor(() => assert(!!runCount))
+ const setLettersAndNumbers = result.current
expect(runCount).toBe(1)
- await act(async () => {
- setLetters(incrementLetter)
- setNumbers(increment)
- })
+ act(setLettersAndNumbers)
expect(runCount).toBe(2)
- await act(async () => {
- setLetters(incrementLetter)
- setNumbers(increment)
- })
+ act(setLettersAndNumbers)
expect(runCount).toBe(3)
})
@@ -574,7 +551,6 @@ it('should abort the previous promise', async () => {
async function resolveAll() {
resolves.forEach((resolve) => resolve())
resolves.length = 0
- await delay(0)
}
function useTest() {
useAtomValue(effectAtom)
@@ -582,20 +558,19 @@ it('should abort the previous promise', async () => {
}
const { result } = renderHook(useTest, { wrapper })
const setCount = result.current
- await waitFor(() => assert(!!runCount))
await resolveAll()
expect(runCount).toBe(1)
expect(abortedRuns).toEqual([])
expect(completedRuns).toEqual([0])
- await act(async () => setCount(increment))
+ act(() => setCount(increment))
expect(runCount).toBe(2)
expect(abortedRuns).toEqual([])
expect(completedRuns).toEqual([0])
// aborted run
- await act(async () => setCount(increment))
+ act(() => setCount(increment))
expect(runCount).toBe(3)
expect(abortedRuns).toEqual([1])
expect(completedRuns).toEqual([0])
@@ -606,7 +581,7 @@ it('should abort the previous promise', async () => {
expect(completedRuns).toEqual([0, 2])
})
-it('should not run the effect when the effectAtom is unmounted', async () => {
+it('should not run the effect when the effectAtom is unmounted', () => {
const countAtom = atom(0)
let runCount = 0
const effectAtom = atomEffect((get) => {
@@ -619,8 +594,7 @@ it('should not run the effect when the effectAtom is unmounted', async () => {
}
const { result } = renderHook(useTest, { wrapper })
const setCount = result.current
- await delay(0)
expect(runCount).toBe(1)
- await act(() => setCount(increment))
+ act(() => setCount(increment))
expect(runCount).toBe(2)
})
diff --git a/__tests__/atomEffect.test.tsx b/__tests__/atomEffect.test.tsx
index f377d79..8c9df45 100644
--- a/__tests__/atomEffect.test.tsx
+++ b/__tests__/atomEffect.test.tsx
@@ -11,7 +11,7 @@ import {
incrementLetter,
} from './test-utils'
-it('should run the effect on vanilla store', async () => {
+it('should run the effect on vanilla store', () => {
const store = getDefaultStore()
const countAtom = atom(0)
const effectAtom = atomEffect((_, set) => {
@@ -21,42 +21,26 @@ it('should run the effect on vanilla store', async () => {
}
})
const unsub = store.sub(effectAtom, () => void 0)
- expect(store.get(countAtom)).toBe(0)
- await waitFor(() => expect(store.get(countAtom)).toBe(1))
+ expect(store.get(countAtom)).toBe(1)
unsub()
- await waitFor(() => expect(store.get(countAtom)).toBe(0))
-})
-
-it('should not call effect if immediately unsubscribed', async () => {
- expect.assertions(1)
- const store = getDefaultStore()
- const effect = jest.fn()
- const effectAtom = atomEffect(effect)
- const unsub = store.sub(effectAtom, () => void 0)
- unsub()
- expect(effect).not.toHaveBeenCalled()
+ expect(store.get(countAtom)).toBe(0)
})
-it('should run the effect on mount and cleanup on unmount once', async () => {
+it('should run the effect on mount and cleanup on unmount once', () => {
expect.assertions(5)
const effect = { mount: 0, unmount: 0 }
- let hasMounted = false
const effectAtom = atomEffect(() => {
effect.mount++
- hasMounted = true
return () => {
effect.unmount++
}
})
- let hasRun = false
function useTest() {
- hasRun = true
return useAtomValue(effectAtom)
}
const { result, rerender, unmount } = renderHook(useTest)
- await waitFor(() => assert(hasRun && hasMounted))
// effect does not return a value
expect(result.current).toBe(undefined)
@@ -75,7 +59,7 @@ it('should run the effect on mount and cleanup on unmount once', async () => {
expect(effect.unmount).toBe(1)
})
-it('should run the effect on mount and cleanup on unmount and whenever countAtom changes', async () => {
+it('should run the effect on mount and cleanup on unmount and whenever countAtom changes', () => {
expect.assertions(11)
const effect = { mount: 0, unmount: 0 }
@@ -99,11 +83,11 @@ it('should run the effect on mount and cleanup on unmount and whenever countAtom
return setCount
}
const { result, rerender, unmount } = renderHook(useTest)
- async function incrementCount() {
+ function incrementCount() {
const setCount = result.current
- await act(async () => setCount(increment))
+ setCount(increment)
}
- await waitFor(() => assert(didMount && effect.mount === 1))
+ waitFor(() => assert(didMount && effect.mount === 1))
// initial render should run the effect but not the cleanup
expect(effect.unmount).toBe(0)
@@ -114,13 +98,13 @@ it('should run the effect on mount and cleanup on unmount and whenever countAtom
expect(effect.unmount).toBe(0)
expect(effect.mount).toBe(1)
- await incrementCount()
+ incrementCount()
// changing the value should run the effect again and the previous cleanup
expect(effect.unmount).toBe(1)
expect(effect.mount).toBe(2)
- await incrementCount()
+ incrementCount()
// changing the value should run the effect again and the previous cleanup
expect(effect.unmount).toBe(2)
@@ -138,7 +122,7 @@ it('should run the effect on mount and cleanup on unmount and whenever countAtom
expect(effect.unmount).toBe(3)
})
-it('should not cause infinite loops when effect updates the watched atom', async () => {
+it('should not cause infinite loops when effect updates the watched atom', () => {
expect.assertions(2)
const watchedAtom = atom(0)
let runCount = 0
@@ -150,17 +134,15 @@ it('should not cause infinite loops when effect updates the watched atom', async
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- const incrementWatched = async () => store.set(watchedAtom, increment)
- await delay(0)
+ const incrementWatched = () => store.set(watchedAtom, increment)
// initial render should run the effect once
- await waitFor(() => assert(runCount === 1))
expect(runCount).toBe(1)
// changing the value should run the effect again one time
- await incrementWatched()
+ incrementWatched()
expect(runCount).toBe(2)
})
-it('should not cause infinite loops when effect updates the watched atom asynchronous', async () => {
+it('should not cause infinite loops when effect updates the watched atom asynchronous', () => {
expect.assertions(1)
const watchedAtom = atom(0)
let runCount = 0
@@ -173,41 +155,32 @@ it('should not cause infinite loops when effect updates the watched atom asynchr
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await delay(0)
- // initial render should run the effect once
- await waitFor(() => assert(runCount === 1))
-
// changing the value should run the effect again one time
store.set(watchedAtom, increment)
-
- await delay(0)
expect(runCount).toBe(2)
})
-it('should allow synchronous recursion with set.recurse for first run', async () => {
+it('should allow synchronous recursion with set.recurse for first run', () => {
expect.assertions(1)
let runCount = 0
const watchedAtom = atom(0)
- let done = false
const effectAtom = atomEffect((get, { recurse }) => {
const value = get(watchedAtom)
runCount++
if (value >= 3) {
- done = true
return
}
recurse(watchedAtom, increment)
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await waitFor(() => assert(done))
expect({ runCount, watched: store.get(watchedAtom) }).toEqual({
runCount: 4,
watched: 3,
})
})
-it('should allow synchronous recursion with set.recurse', async () => {
+it('should allow synchronous recursion with set.recurse', () => {
expect.assertions(2)
let runCount = 0
const watchedAtom = atom(0)
@@ -224,14 +197,12 @@ it('should allow synchronous recursion with set.recurse', async () => {
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await delay(0)
store.set(watchedAtom, increment)
- await waitFor(() => assert(store.get(watchedAtom) === 5))
expect(store.get(watchedAtom)).toBe(5)
expect(runCount).toBe(6)
})
-it('should allow multiple synchronous recursion with set.recurse', async () => {
+it('should allow multiple synchronous recursion with set.recurse', () => {
expect.assertions(1)
let runCount = 0
const watchedAtom = atom(0)
@@ -249,16 +220,14 @@ it('should allow multiple synchronous recursion with set.recurse', async () => {
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await delay(0)
store.set(watchedAtom, increment)
- await delay(0)
expect({ runCount, value: store.get(watchedAtom) }).toEqual({
runCount: 6,
value: 5,
})
})
-it('should batch updates during synchronous recursion with set.recurse', async () => {
+it('should batch updates during synchronous recursion with set.recurse', () => {
expect.assertions(2)
let runCount = 0
const lettersAtom = atom('a')
@@ -288,9 +257,7 @@ it('should batch updates during synchronous recursion with set.recurse', async (
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await delay(0)
store.set(watchedAtom, increment)
- await delay(0)
expect(store.get(lettersAndNumbersAtom)).toEqual(['a0', 'b1'])
expect(runCount).toBe(4)
})
@@ -335,12 +302,12 @@ it('should allow asynchronous recursion with microtask delay with set.recurse',
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await delay(500)
+ await waitFor(() => assert(store.get(watchedAtom) >= 3))
expect(store.get(watchedAtom)).toBe(3)
expect(runCount).toBe(4)
})
-it('should work with both set.recurse and set', async () => {
+it('should work with both set.recurse and set', () => {
expect.assertions(3)
let runCount = 0
const watchedAtom = atom(0)
@@ -358,14 +325,13 @@ it('should work with both set.recurse and set', async () => {
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await waitFor(() => assert(store.get(countAtom) === 3))
expect(store.get(countAtom)).toBe(3)
expect(store.get(watchedAtom)).toBe(4)
expect(runCount).toBe(4)
})
-it('should disallow synchronous set.recurse in cleanup', async () => {
- expect.assertions(2)
+it('should disallow synchronous set.recurse in cleanup', () => {
+ expect.assertions(1)
const watchedAtom = atom(0)
const anotherAtom = atom(0)
let cleanup
@@ -379,18 +345,15 @@ it('should disallow synchronous set.recurse in cleanup', async () => {
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await delay(0)
store.set(anotherAtom, increment)
- await delay(0)
- expect(store.get(watchedAtom)).toBe(0)
- expect(() => store.get(effectAtom)).toThrowError(
+ expect(() => store.set(anotherAtom, increment)).toThrowError(
'set.recurse is not allowed in cleanup'
)
})
// FIXME: is there a way to disallow asynchronous infinite loops in cleanup?
-it('should return value from set.recurse', async () => {
+it('should return value from set.recurse', () => {
expect.assertions(1)
const countAtom = atom(0)
const incrementCountAtom = atom(null, (get, set) => {
@@ -398,23 +361,20 @@ it('should return value from set.recurse', async () => {
return get(countAtom)
})
const results = [] as number[]
- let done = false
const effectAtom = atomEffect((get, { recurse }) => {
const value = get(countAtom)
if (value < 5) {
const result = recurse(incrementCountAtom)
results.unshift(result)
- done = true
return
}
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await waitFor(() => assert(done))
expect(results).toEqual([1, 2, 3, 4, 5])
})
-it('should conditionally run the effect and cleanup when effectAtom is unmounted', async () => {
+it('should conditionally run the effect and cleanup when effectAtom is unmounted', () => {
expect.assertions(6)
const booleanAtom = atom(false)
@@ -439,19 +399,19 @@ it('should conditionally run the effect and cleanup when effectAtom is unmounted
const { result } = renderHook(useTest)
const setBoolean = result.current
- const toggleBoolean = () => act(async () => setBoolean((prev) => !prev))
+ const toggleBoolean = () => setBoolean((prev) => !prev)
// Initially the effectAtom should not run as booleanAtom is false
expect(effectRunCount).toBe(0)
expect(cleanupRunCount).toBe(0)
// Set booleanAtom to true, so effectAtom should run
- await toggleBoolean()
+ toggleBoolean()
expect(effectRunCount).toBe(1)
expect(cleanupRunCount).toBe(0)
// Set booleanAtom to false, so effectAtom should cleanup
- await toggleBoolean()
+ toggleBoolean()
expect(effectRunCount).toBe(1)
expect(cleanupRunCount).toBe(1)
})
@@ -522,6 +482,7 @@ describe('should correctly process synchronous updates to the same atom', () =>
// 2. incrementing count: count = 1
// 3. incrementing count: count = 2
// 4. incrementing count reruns the effect (batched): run = 2
+ // FIXME: understand why run-result: 3-2
effectIncrementCountBy: 0,
incrementCountBy: 2,
runs: [
@@ -560,6 +521,7 @@ describe('should correctly process synchronous updates to the same atom', () =>
// 4. incrementing count: count = 3
// 5. incrementing count reruns the effect (batched): run = 2
// 6. effect increments count: count = 4
+ // FIXME: understand why run-result: 3-5
effectIncrementCountBy: 1,
incrementCountBy: 2,
runs: [
@@ -597,6 +559,7 @@ describe('should correctly process synchronous updates to the same atom', () =>
// 4. incrementing count: count = 4
// 5. incrementing count reruns the effect (batched): run = 2
// 6. effect increments count by two: count = 6
+ // FIXME: understand why run-result: 3-8
effectIncrementCountBy: 2,
incrementCountBy: 2,
runs: [
@@ -654,8 +617,6 @@ it('should not batch effect setStates', async () => {
const { result } = renderHook(() => useSetAtom(triggerAtom))
const setTrigger = result.current
- await waitFor(() => assert(runCount.current === 1))
-
expect(valueResult.current).toBe(0)
expect(runCount.current).toBe(1)
@@ -664,7 +625,7 @@ it('should not batch effect setStates', async () => {
expect(runCount.current).toBe(3) // <--- not batched (we would expect runCount to be 2 if batched)
})
-it('should batch synchronous updates as a single transaction', async () => {
+it('should batch synchronous updates as a single transaction', () => {
expect.assertions(4)
const lettersAtom = atom('a')
lettersAtom.debugLabel = 'lettersAtom'
@@ -685,23 +646,25 @@ it('should batch synchronous updates as a single transaction', async () => {
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await waitFor(() => assert(!!runCount))
expect(runCount).toBe(1)
expect(store.get(lettersAndNumbersAtom)).toEqual(['a0'])
- await act(async () => {
- store.set(lettersAtom, incrementLetter)
- store.set(numbersAtom, increment)
+ const w = atom(null, (_get, set) => {
+ set(lettersAtom, incrementLetter)
+ set(numbersAtom, increment)
})
+ store.set(w)
expect(runCount).toBe(2)
expect(store.get(lettersAndNumbersAtom)).toEqual(['a0', 'b1'])
})
-it('should run the effect once even if the effect is mounted multiple times', async () => {
+it('should run the effect once even if the effect is mounted multiple times', () => {
expect.assertions(3)
const lettersAtom = atom('a')
- lettersAtom.debugLabel = 'lettersAtom'
const numbersAtom = atom(0)
- numbersAtom.debugLabel = 'numbersAtom'
+ const lettersAndNumbersAtom = atom(null, (_get, set) => {
+ set(lettersAtom, incrementLetter)
+ set(numbersAtom, increment)
+ })
let runCount = 0
const effectAtom = atomEffect((get) => {
runCount++
@@ -731,23 +694,14 @@ it('should run the effect once even if the effect is mounted multiple times', as
useAtomValue(derivedAtom2)
useAtomValue(derivedAtom3)
useAtomValue(derivedAtom4)
- const setLetters = useSetAtom(lettersAtom)
- const setNumbers = useSetAtom(numbersAtom)
- return { setLetters, setNumbers }
+ return useSetAtom(lettersAndNumbersAtom)
}
const { result } = renderHook(useTest)
- const { setLetters, setNumbers } = result.current
- await waitFor(() => assert(!!runCount))
+ const setLettersAndNumbers = result.current
expect(runCount).toBe(1)
- await act(async () => {
- setLetters(incrementLetter)
- setNumbers(increment)
- })
+ act(setLettersAndNumbers)
expect(runCount).toBe(2)
- await act(async () => {
- setLetters(incrementLetter)
- setNumbers(increment)
- })
+ act(setLettersAndNumbers)
expect(runCount).toBe(3)
})
@@ -790,7 +744,6 @@ it('should abort the previous promise', async () => {
async function resolveAll() {
resolves.forEach((resolve) => resolve())
resolves.length = 0
- await delay(0)
}
function useTest() {
useAtomValue(effectAtom)
@@ -798,20 +751,19 @@ it('should abort the previous promise', async () => {
}
const { result } = renderHook(useTest)
const setCount = result.current
- await waitFor(() => assert(!!runCount))
await resolveAll()
expect(runCount).toBe(1)
expect(abortedRuns).toEqual([])
expect(completedRuns).toEqual([0])
- await act(async () => setCount(increment))
+ act(() => setCount(increment))
expect(runCount).toBe(2)
expect(abortedRuns).toEqual([])
expect(completedRuns).toEqual([0])
// aborted run
- await act(async () => setCount(increment))
+ act(() => setCount(increment))
expect(runCount).toBe(3)
expect(abortedRuns).toEqual([1])
expect(completedRuns).toEqual([0])
@@ -835,10 +787,12 @@ it('should not infinite loop with nested atomEffects', async () => {
return () => ++metrics.unmounted
}
+ let delayedIncrement = false
const effectAtom = atomEffect((_get, set) => {
++metrics.runCount1
if (metrics.runCount1 > 1) throw new Error('infinite loop')
Promise.resolve().then(() => {
+ delayedIncrement = true
set(countAtom, increment)
})
})
@@ -856,7 +810,7 @@ it('should not infinite loop with nested atomEffects', async () => {
const store = getDefaultStore()
store.sub(effect2Atom, () => void 0)
- await waitFor(() => assert(!!metrics.runCount1))
+ await waitFor(() => assert(delayedIncrement))
if (!('dev4_get_mounted_atoms' in store)) return
const atomSet = new Set(store.dev4_get_mounted_atoms())
@@ -878,7 +832,7 @@ it('should not infinite loop with nested atomEffects', async () => {
})
})
-it('should not rerun with get.peek', async () => {
+it('should not rerun with get.peek', () => {
expect.assertions(1)
const countAtom = atom(0)
let runCount = 0
@@ -888,15 +842,12 @@ it('should not rerun with get.peek', async () => {
})
const store = getDefaultStore()
store.sub(effectAtom, () => void 0)
- await waitFor(() => assert(runCount === 1))
store.set(countAtom, increment)
- await delay(0)
expect(runCount).toBe(1)
})
it('should trigger the error boundary when an error is thrown', async () => {
expect.assertions(1)
-
const effectAtom = atomEffect((_get, _set) => {
throw new Error('effect error')
})
@@ -913,7 +864,13 @@ it('should trigger the error boundary when an error is thrown', async () => {
/>
)
}
- render(, { wrapper })
+ const originalConsoleError = console.error
+ try {
+ console.error = jest.fn()
+ render(, { wrapper })
+ } finally {
+ console.error = originalConsoleError
+ }
await waitFor(() => assert(didThrow))
expect(didThrow).toBe(true)
})
@@ -950,12 +907,17 @@ it('should trigger an error boundary when an error is thrown in a cleanup', asyn
)
}
render(, { wrapper })
- await delay(0)
- act(() => store.set(refreshAtom, increment))
+ const originalConsoleError = console.error
+ try {
+ console.error = jest.fn()
+ act(() => store.set(refreshAtom, increment))
+ } finally {
+ console.error = originalConsoleError
+ }
await waitFor(() => assert(didThrow))
})
-it('should not suspend the component', async () => {
+it('should not suspend the component', () => {
const countAtom = atom(0)
const watchCounterEffect = atomEffect((get) => {
get(countAtom)
diff --git a/__tests__/withAtomEffect.test.ts b/__tests__/withAtomEffect.test.ts
index 8813cdd..5db5524 100644
--- a/__tests__/withAtomEffect.test.ts
+++ b/__tests__/withAtomEffect.test.ts
@@ -3,7 +3,6 @@ import { useAtomValue } from 'jotai/react'
import { atom, createStore } from 'jotai/vanilla'
import { atomEffect } from '../src/atomEffect'
import { withAtomEffect } from '../src/withAtomEffect'
-import { delay } from './test-utils'
describe('withAtomEffect', () => {
it('ensures readonly atoms remain readonly', () => {
@@ -29,7 +28,7 @@ describe('withAtomEffect', () => {
expect(store.get(enhancedAtom)).toBe(6)
})
- it('calls effect on initial use and on dependencies change of the base atom', async () => {
+ it('calls effect on initial use and on dependencies change of the base atom', () => {
const baseAtom = atom(0)
const effectMock = jest.fn()
const enhancedAtom = withAtomEffect(baseAtom, (get) => {
@@ -38,14 +37,12 @@ describe('withAtomEffect', () => {
})
const store = createStore()
store.sub(enhancedAtom, () => {})
- await Promise.resolve()
expect(effectMock).toHaveBeenCalledTimes(1)
store.set(enhancedAtom, 1)
- await Promise.resolve()
expect(effectMock).toHaveBeenCalledTimes(2)
})
- it('calls effect on initial use and on dependencies change of the enhanced atom', async () => {
+ it('calls effect on initial use and on dependencies change of the enhanced atom', () => {
const baseAtom = atom(0)
const effectMock = jest.fn()
const enhancedAtom = withAtomEffect(baseAtom, (get) => {
@@ -54,14 +51,12 @@ describe('withAtomEffect', () => {
})
const store = createStore()
store.sub(enhancedAtom, () => {})
- await Promise.resolve()
expect(effectMock).toHaveBeenCalledTimes(1)
store.set(enhancedAtom, 1)
- await Promise.resolve()
expect(effectMock).toHaveBeenCalledTimes(2)
})
- it('cleans up when the atom is no longer in use', async () => {
+ it('cleans up when the atom is no longer in use', () => {
const cleanupMock = jest.fn()
const baseAtom = atom(0)
const mountMock = jest.fn()
@@ -73,10 +68,8 @@ describe('withAtomEffect', () => {
})
const store = createStore()
const unsubscribe = store.sub(enhancedAtom, () => {})
- await Promise.resolve()
expect(mountMock).toHaveBeenCalledTimes(1)
unsubscribe()
- await Promise.resolve()
expect(cleanupMock).toHaveBeenCalledTimes(1)
})
@@ -88,21 +81,21 @@ describe('withAtomEffect', () => {
expect(enhancedAtom.read).not.toBe(read)
})
- it('does not cause infinite loops when it references itself', async () => {
+ it('does not cause infinite loops when it references itself', () => {
const countWithEffectAtom = withAtomEffect(atom(0), (get, set) => {
get(countWithEffectAtom)
set(countWithEffectAtom, (v) => v + 1)
})
const store = createStore()
store.sub(countWithEffectAtom, () => {})
- await Promise.resolve()
expect(store.get(countWithEffectAtom)).toBe(1)
- store.set(countWithEffectAtom, (v) => ++v)
- await Promise.resolve()
+ store.set(countWithEffectAtom, (v) => {
+ return v + 1
+ })
expect(store.get(countWithEffectAtom)).toBe(3)
})
- it('can change the effect of the enhanced atom', async () => {
+ it('can change the effect of the enhanced atom', () => {
const baseAtom = atom(0)
const effectA = jest.fn((get) => {
get(enhancedAtom)
@@ -111,22 +104,19 @@ describe('withAtomEffect', () => {
expect(enhancedAtom.effect).toBe(effectA)
const store = createStore()
store.sub(enhancedAtom, () => {})
- await Promise.resolve()
effectA.mockClear()
store.set(enhancedAtom, (v) => ++v)
- await Promise.resolve()
expect(effectA).toHaveBeenCalledTimes(1)
effectA.mockClear()
const effectB = jest.fn((get) => get(baseAtom))
enhancedAtom.effect = effectB
expect(enhancedAtom.effect).toBe(effectB)
store.set(enhancedAtom, (v) => ++v)
- await Promise.resolve()
expect(effectA).not.toHaveBeenCalled()
expect(effectB).toHaveBeenCalledTimes(1)
})
- it('runs the cleanup function the same number of times as the effect function', async () => {
+ it('runs the cleanup function the same number of times as the effect function', () => {
const baseAtom = atom(0)
const effectMock = jest.fn()
const cleanupMock = jest.fn()
@@ -139,20 +129,17 @@ describe('withAtomEffect', () => {
})
const store = createStore()
const unsub = store.sub(enhancedAtom, () => {})
- await Promise.resolve()
expect(effectMock).toHaveBeenCalledTimes(1)
expect(cleanupMock).toHaveBeenCalledTimes(0)
store.set(enhancedAtom, 1)
- await Promise.resolve()
expect(effectMock).toHaveBeenCalledTimes(2)
expect(cleanupMock).toHaveBeenCalledTimes(1)
unsub()
- await Promise.resolve()
expect(effectMock).toHaveBeenCalledTimes(2)
expect(cleanupMock).toHaveBeenCalledTimes(2)
})
- it('runs the cleanup function the same number of times as the effect function in React', async () => {
+ it('runs the cleanup function the same number of times as the effect function in React', () => {
const baseAtom = atom(0)
const effectMock1 = jest.fn()
const cleanupMock1 = jest.fn()
@@ -183,21 +170,21 @@ describe('withAtomEffect', () => {
useAtomValue(enhancedAtom2, { store })
}
const { unmount } = renderHook(Test)
- await waitFor(() => expect(effectMock1).toHaveBeenCalledTimes(1))
- await waitFor(() => expect(effectMock2).toHaveBeenCalledTimes(1))
+ expect(effectMock1).toHaveBeenCalledTimes(1)
+ expect(effectMock2).toHaveBeenCalledTimes(1)
expect(cleanupMock1).toHaveBeenCalledTimes(0)
expect(cleanupMock2).toHaveBeenCalledTimes(0)
act(() => store.set(baseAtom, 1))
- await waitFor(() => expect(effectMock1).toHaveBeenCalledTimes(2))
- await waitFor(() => expect(effectMock2).toHaveBeenCalledTimes(2))
+ expect(effectMock1).toHaveBeenCalledTimes(2)
+ expect(effectMock2).toHaveBeenCalledTimes(2)
expect(cleanupMock1).toHaveBeenCalledTimes(1)
expect(cleanupMock2).toHaveBeenCalledTimes(1)
act(unmount)
- await waitFor(() => expect(cleanupMock1).toHaveBeenCalledTimes(2))
- await waitFor(() => expect(cleanupMock2).toHaveBeenCalledTimes(2))
+ expect(cleanupMock1).toHaveBeenCalledTimes(2)
+ expect(cleanupMock2).toHaveBeenCalledTimes(2)
})
- it('calculates price and discount', async () => {
+ it('calculates price and discount', () => {
// https://github.com/pmndrs/jotai/discussions/2876
/*
How can be implemented an atom to hold either a value or a calculated value at the same time?
@@ -273,22 +260,18 @@ describe('withAtomEffect', () => {
const store = createStore()
store.sub(priceAndDiscount, () => void 0)
- await delay(0)
expect(store.get(priceAtom)).toBe(100) // value
expect(store.get(discountAtom)).toBe(0) // (100-100)/100*100 = 0)
store.set(discountAtom, 20)
- await delay(0)
expect(store.get(priceAtom)).toBe(80) // 100*(1-20/100) = 80)
expect(store.get(discountAtom)).toBe(20) // value
store.set(priceAtom, 50)
- await delay(0)
expect(store.get(priceAtom)).toBe(50) // value
expect(store.get(discountAtom)).toBe(50) // (100-50)/100*100 = 50)
store.set(unitPriceAtom, 200)
- await delay(0)
expect(store.get(priceAtom)).toBe(100) // 200*(1-50/100) = 100)
expect(store.get(discountAtom)).toBe(50) // (200-100)/200*100 = 50)
})