Skip to main content
Module

x/valtio/tests/derive.test.tsx

πŸ’Š Valtio makes proxy-state simple for React and Vanilla
Go to Latest
File
import { StrictMode, Suspense } from 'react'import { fireEvent, render } from '@testing-library/react'import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'import { derive, underive } from 'valtio/utils'
const consoleError = console.errorbeforeEach(() => { console.error = jest.fn((message) => { if ( process.env.NODE_ENV === 'production' && message.startsWith('act(...) is not supported in production') ) { return } consoleError(message) })})afterEach(() => { console.error = consoleError})
const sleep = (ms: number) => new Promise((resolve) => { setTimeout(resolve, ms) })
it('basic derive', async () => { const computeDouble = jest.fn((x) => x * 2) const state = proxy({ text: '', count: 0, }) const derived = derive({ doubled: (get) => computeDouble(get(state).count), })
const callback = jest.fn() subscribe(derived, callback)
expect(snapshot(derived)).toMatchObject({ doubled: 0 }) expect(computeDouble).toBeCalledTimes(1) expect(callback).toBeCalledTimes(0)
state.count += 1 await Promise.resolve() expect(snapshot(derived)).toMatchObject({ doubled: 2 }) expect(computeDouble).toBeCalledTimes(2) await Promise.resolve() expect(callback).toBeCalledTimes(1)
state.text = 'a' await Promise.resolve() expect(snapshot(derived)).toMatchObject({ doubled: 2 }) expect(computeDouble).toBeCalledTimes(3) await Promise.resolve() expect(callback).toBeCalledTimes(1)})
it('derive another proxy', async () => { const computeDouble = jest.fn((x) => x * 2) const state = proxy({ text: '', count: 0, }) const anotherState = proxy({}) derive( { doubled: (get) => computeDouble(get(state).count), }, { proxy: anotherState, } )
const callback = jest.fn() subscribe(anotherState, callback)
expect(snapshot(anotherState)).toMatchObject({ doubled: 0 }) expect(computeDouble).toBeCalledTimes(1) expect(callback).toBeCalledTimes(0)
state.count += 1 await Promise.resolve() expect(snapshot(anotherState)).toMatchObject({ doubled: 2 }) expect(computeDouble).toBeCalledTimes(2) await Promise.resolve() expect(callback).toBeCalledTimes(1)
state.text = 'a' await Promise.resolve() expect(snapshot(anotherState)).toMatchObject({ doubled: 2 }) expect(computeDouble).toBeCalledTimes(3) await Promise.resolve() expect(callback).toBeCalledTimes(1)})
it('derive with self', async () => { const computeDouble = jest.fn((x) => x * 2) const state = proxy({ text: '', count: 0, }) derive( { doubled: (get) => computeDouble(get(state).count), }, { proxy: state, } )
const callback = jest.fn() subscribe(state, callback)
expect(snapshot(state)).toMatchObject({ text: '', count: 0, doubled: 0 }) expect(computeDouble).toBeCalledTimes(1) expect(callback).toBeCalledTimes(0)
state.count += 1 await Promise.resolve() expect(snapshot(state)).toMatchObject({ text: '', count: 1, doubled: 2 }) expect(computeDouble).toBeCalledTimes(2) await Promise.resolve() expect(callback).toBeCalledTimes(1)
state.text = 'a' await Promise.resolve() expect(snapshot(state)).toMatchObject({ text: 'a', count: 1, doubled: 2 }) expect(computeDouble).toBeCalledTimes(3) await Promise.resolve() expect(callback).toBeCalledTimes(2)})
it('derive with two dependencies', async () => { const computeSum = jest.fn((x, y) => x + y) const state1 = proxy({ count: 1 }) const state2 = proxy({ count: 10 }) const derived = derive({ sum: (get) => computeSum(get(state1).count, get(state2).count), })
const callback = jest.fn() subscribe(derived, callback)
expect(snapshot(derived)).toMatchObject({ sum: 11 }) expect(computeSum).toBeCalledTimes(1) expect(callback).toBeCalledTimes(0)
state1.count += 1 await Promise.resolve() expect(snapshot(derived)).toMatchObject({ sum: 12 }) expect(computeSum).toBeCalledTimes(2) await Promise.resolve() expect(callback).toBeCalledTimes(1)
state1.count += 1 state2.count += 10 await Promise.resolve() expect(snapshot(derived)).toMatchObject({ sum: 23 }) expect(computeSum).toBeCalledTimes(3) await Promise.resolve() expect(callback).toBeCalledTimes(2)})
it('async derive', async () => { const state = proxy({ count: 0 }) derive( { delayedCount: async (get) => { await sleep(300) return get(state).count + 1 }, }, { proxy: state } )
const Counter = () => { const snap = useSnapshot( state as { count: number; delayedCount: Promise<number> } ) return ( <> <div> count: {snap.count}, delayedCount: {snap.delayedCount} </div> <button onClick={() => ++state.count}>button</button> </> ) }
const { getByText, findByText } = render( <StrictMode> <Suspense fallback="loading"> <Counter /> </Suspense> </StrictMode> )
await findByText('loading') await findByText('count: 0, delayedCount: 1')
fireEvent.click(getByText('button')) await findByText('loading') await findByText('count: 1, delayedCount: 2')})
it('nested emulation with derive', async () => { const computeDouble = jest.fn((x) => x * 2) const state = proxy({ text: '', math: { count: 0 } }) derive( { doubled: (get) => computeDouble(get(state.math).count), }, { proxy: state.math } )
const callback = jest.fn() subscribe(state, callback)
expect(snapshot(state)).toMatchObject({ text: '', math: { count: 0, doubled: 0 }, }) expect(computeDouble).toBeCalledTimes(1) expect(callback).toBeCalledTimes(0)
state.math.count += 1 await Promise.resolve() expect(snapshot(state)).toMatchObject({ text: '', math: { count: 1, doubled: 2 }, }) expect(computeDouble).toBeCalledTimes(2) await Promise.resolve() expect(callback).toBeCalledTimes(2)
state.text = 'a' await Promise.resolve() expect(snapshot(state)).toMatchObject({ text: 'a', math: { count: 1, doubled: 2 }, }) expect(computeDouble).toBeCalledTimes(2) expect(callback).toBeCalledTimes(3)})
it('derive with array.pop', async () => { const state = proxy({ arr: [{ n: 1 }, { n: 2 }, { n: 3 }], }) derive( { nums: (get) => get(state.arr).map((item) => item.n), }, { proxy: state } )
expect(snapshot(state)).toMatchObject({ arr: [{ n: 1 }, { n: 2 }, { n: 3 }], nums: [1, 2, 3], })
state.arr.pop() await Promise.resolve() expect(snapshot(state)).toMatchObject({ arr: [{ n: 1 }, { n: 2 }], nums: [1, 2], })})
it('basic underive', async () => { const computeDouble = jest.fn((x) => x * 2) const state = proxy({ count: 0 }) const derived = derive({ doubled: (get) => computeDouble(get(state).count), })
const callback = jest.fn() subscribe(derived, callback)
expect(snapshot(derived)).toMatchObject({ doubled: 0 }) expect(computeDouble).toBeCalledTimes(1) expect(callback).toBeCalledTimes(0)
state.count += 1 await Promise.resolve() expect(snapshot(derived)).toMatchObject({ doubled: 2 }) expect(computeDouble).toBeCalledTimes(2) await Promise.resolve() expect(callback).toBeCalledTimes(1)
underive(derived)
state.count += 1 await Promise.resolve() expect(snapshot(derived)).toMatchObject({ doubled: 2 }) expect(computeDouble).toBeCalledTimes(2) await Promise.resolve() expect(callback).toBeCalledTimes(1)})