-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { afterEach, test } from 'vitest'; | ||
import { StrictMode, Suspense } from 'react'; | ||
import { cleanup, fireEvent, render } from '@testing-library/react'; | ||
import { expectType } from 'ts-expect'; | ||
import { useAtom } from 'jotai/react'; | ||
import { atom } from 'jotai/vanilla'; | ||
import type { SetStateAction, WritableAtom } from 'jotai/vanilla'; | ||
import * as O from 'optics-ts'; | ||
import { focusAtom } from 'jotai-optics'; | ||
import type { NotAnArrayType } from 'node_modules/optics-ts/utils.js'; | ||
import { useSetAtom } from 'jotai'; | ||
|
||
afterEach(cleanup); | ||
|
||
test('basic derivation using focus works', async () => { | ||
const bigAtom = atom([{ a: 0 }]); | ||
const focusFunction = (optic: O.OpticFor_<{ a: number }[]>) => | ||
optic.appendTo(); | ||
|
||
const Counter = () => { | ||
const appendNumber = useSetAtom(focusAtom(bigAtom, focusFunction)); | ||
const [bigAtomValue] = useAtom(bigAtom); | ||
return ( | ||
<> | ||
<div>bigAtom: {JSON.stringify(bigAtomValue)}</div> | ||
<button onClick={() => appendNumber({ a: bigAtomValue.length })}> | ||
Append to bigAtom | ||
</button> | ||
</> | ||
); | ||
}; | ||
|
||
const { getByText, findByText } = render( | ||
<StrictMode> | ||
<Counter /> | ||
</StrictMode>, | ||
); | ||
|
||
await findByText('bigAtom: [{"a":0}]'); | ||
|
||
fireEvent.click(getByText('Append to bigAtom')); | ||
await findByText('bigAtom: [{"a":0},{"a":1}]'); | ||
|
||
fireEvent.click(getByText('Append to bigAtom')); | ||
await findByText('bigAtom: [{"a":0},{"a":1},{"a":2}]'); | ||
}); | ||
|
||
test('double-focus on an atom works', async () => { | ||
const bigAtom = atom({ a: [0] }); | ||
const atomA = focusAtom(bigAtom, (optic) => optic.prop('a')); | ||
const atomAppend = focusAtom(atomA, (optic) => optic.appendTo()); | ||
|
||
const Counter = () => { | ||
const [bigAtomValue, setBigAtom] = useAtom(bigAtom); | ||
const [atomAValue, setAtomA] = useAtom(atomA); | ||
const append = useSetAtom(atomAppend); | ||
return ( | ||
<> | ||
<div>bigAtom: {JSON.stringify(bigAtomValue)}</div> | ||
<div>atomA: {JSON.stringify(atomAValue)}</div> | ||
<button onClick={() => setBigAtom((v) => ({ a: [...v.a, 1] }))}> | ||
inc bigAtom | ||
</button> | ||
<button onClick={() => setAtomA((v) => [...v, 2])}>inc atomA</button> | ||
<button onClick={() => append(3)}>append</button> | ||
</> | ||
); | ||
}; | ||
|
||
const { getByText, findByText } = render( | ||
<StrictMode> | ||
<Counter /> | ||
</StrictMode>, | ||
); | ||
|
||
await findByText('bigAtom: {"a":[0]}'); | ||
await findByText('atomA: [0]'); | ||
|
||
fireEvent.click(getByText('inc bigAtom')); | ||
await findByText('bigAtom: {"a":[0,1]}'); | ||
await findByText('atomA: [0,1]'); | ||
|
||
fireEvent.click(getByText('inc atomA')); | ||
await findByText('bigAtom: {"a":[0,1,2]}'); | ||
await findByText('atomA: [0,1,2]'); | ||
|
||
fireEvent.click(getByText('append')); | ||
await findByText('bigAtom: {"a":[0,1,2,3]}'); | ||
await findByText('atomA: [0,1,2,3]'); | ||
}); | ||
|
||
test('focus on async atom works', async () => { | ||
const baseAtom = atom([0]); | ||
const asyncAtom = atom( | ||
(get) => Promise.resolve(get(baseAtom)), | ||
async (get, set, param: SetStateAction<Promise<number[]>>) => { | ||
const prev = Promise.resolve(get(baseAtom)); | ||
const next = await (typeof param === 'function' ? param(prev) : param); | ||
set(baseAtom, next); | ||
}, | ||
); | ||
const focusFunction = (optic: O.OpticFor_<number[]>) => optic.appendTo(); | ||
|
||
const Counter = () => { | ||
const append = useSetAtom(focusAtom(asyncAtom, focusFunction)); | ||
const [asyncValue, setAsync] = useAtom(asyncAtom); | ||
const [baseValue, setBase] = useAtom(baseAtom); | ||
return ( | ||
<> | ||
<div>baseAtom: {JSON.stringify(baseValue)}</div> | ||
<div>asyncAtom: {JSON.stringify(asyncValue)}</div> | ||
<button onClick={() => append(baseValue.length)}>append</button> | ||
<button | ||
onClick={() => setAsync((p) => p.then((v) => [...v, v.length]))} | ||
> | ||
incr async | ||
</button> | ||
<button onClick={() => setBase((v) => [...v, v.length])}> | ||
incr base | ||
</button> | ||
</> | ||
); | ||
}; | ||
|
||
const { getByText, findByText } = render( | ||
<StrictMode> | ||
<Suspense fallback={<div>Loading...</div>}> | ||
<Counter /> | ||
</Suspense> | ||
</StrictMode>, | ||
); | ||
|
||
await findByText('baseAtom: [0]'); | ||
await findByText('asyncAtom: [0]'); | ||
|
||
fireEvent.click(getByText('append')); | ||
await findByText('baseAtom: [0,1]'); | ||
await findByText('asyncAtom: [0,1]'); | ||
|
||
fireEvent.click(getByText('incr async')); | ||
await findByText('baseAtom: [0,1,2]'); | ||
await findByText('asyncAtom: [0,1,2]'); | ||
|
||
fireEvent.click(getByText('incr base')); | ||
await findByText('baseAtom: [0,1,2,3]'); | ||
await findByText('asyncAtom: [0,1,2,3]'); | ||
}); | ||
|
||
type BillingData = { | ||
id: string; | ||
}; | ||
|
||
type CustomerData = { | ||
id: string; | ||
billing: BillingData[]; | ||
someOtherData: string; | ||
}; | ||
|
||
test('typescript should accept "undefined" as valid value for lens', async () => { | ||
const customerListAtom = atom<CustomerData[]>([]); | ||
|
||
const foundCustomerAtom = focusAtom(customerListAtom, (optic) => | ||
optic.find((el) => el.id === 'some-invalid-id'), | ||
); | ||
|
||
const derivedLens = focusAtom(foundCustomerAtom, (optic) => optic.appendTo()); | ||
|
||
expectType< | ||
WritableAtom<void, [NotAnArrayType<CustomerData | undefined>], void> | ||
>(derivedLens); | ||
}); | ||
|
||
test('should work with promise based atoms with "undefined" value', async () => { | ||
const customerBaseAtom = atom<CustomerData | undefined>(undefined); | ||
|
||
const asyncCustomerDataAtom = atom( | ||
async (get) => get(customerBaseAtom), | ||
async (_, set, nextValue: Promise<CustomerData>) => { | ||
set(customerBaseAtom, await nextValue); | ||
}, | ||
); | ||
|
||
const focusedPromiseAtom = focusAtom(asyncCustomerDataAtom, (optic) => | ||
optic.appendTo(), | ||
); | ||
|
||
expectType< | ||
WritableAtom< | ||
Promise<void>, | ||
[NotAnArrayType<CustomerData | undefined>], | ||
Promise<void> | ||
> | ||
>(focusedPromiseAtom); | ||
}); |