Skip to content

Commit 5edde72

Browse files
committed
add test for Setter
1 parent f0a2998 commit 5edde72

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed

tests/focus-setter.spec.tsx

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { afterEach, test } from 'vitest';
2+
import { StrictMode, Suspense } from 'react';
3+
import { cleanup, fireEvent, render } from '@testing-library/react';
4+
import { expectType } from 'ts-expect';
5+
import { useAtom } from 'jotai/react';
6+
import { atom } from 'jotai/vanilla';
7+
import type { SetStateAction, WritableAtom } from 'jotai/vanilla';
8+
import * as O from 'optics-ts';
9+
import { focusAtom } from 'jotai-optics';
10+
import type { NotAnArrayType } from 'node_modules/optics-ts/utils.js';
11+
import { useSetAtom } from 'jotai';
12+
13+
afterEach(cleanup);
14+
15+
test('basic derivation using focus works', async () => {
16+
const bigAtom = atom([{ a: 0 }]);
17+
const focusFunction = (optic: O.OpticFor_<{ a: number }[]>) =>
18+
optic.appendTo();
19+
20+
const Counter = () => {
21+
const appendNumber = useSetAtom(focusAtom(bigAtom, focusFunction));
22+
const [bigAtomValue] = useAtom(bigAtom);
23+
return (
24+
<>
25+
<div>bigAtom: {JSON.stringify(bigAtomValue)}</div>
26+
<button onClick={() => appendNumber({ a: bigAtomValue.length })}>
27+
Append to bigAtom
28+
</button>
29+
</>
30+
);
31+
};
32+
33+
const { getByText, findByText } = render(
34+
<StrictMode>
35+
<Counter />
36+
</StrictMode>,
37+
);
38+
39+
await findByText('bigAtom: [{"a":0}]');
40+
41+
fireEvent.click(getByText('Append to bigAtom'));
42+
await findByText('bigAtom: [{"a":0},{"a":1}]');
43+
44+
fireEvent.click(getByText('Append to bigAtom'));
45+
await findByText('bigAtom: [{"a":0},{"a":1},{"a":2}]');
46+
});
47+
48+
test('double-focus on an atom works', async () => {
49+
const bigAtom = atom({ a: [0] });
50+
const atomA = focusAtom(bigAtom, (optic) => optic.prop('a'));
51+
const atomAppend = focusAtom(atomA, (optic) => optic.appendTo());
52+
53+
const Counter = () => {
54+
const [bigAtomValue, setBigAtom] = useAtom(bigAtom);
55+
const [atomAValue, setAtomA] = useAtom(atomA);
56+
const append = useSetAtom(atomAppend);
57+
return (
58+
<>
59+
<div>bigAtom: {JSON.stringify(bigAtomValue)}</div>
60+
<div>atomA: {JSON.stringify(atomAValue)}</div>
61+
<button onClick={() => setBigAtom((v) => ({ a: [...v.a, 1] }))}>
62+
inc bigAtom
63+
</button>
64+
<button onClick={() => setAtomA((v) => [...v, 2])}>inc atomA</button>
65+
<button onClick={() => append(3)}>append</button>
66+
</>
67+
);
68+
};
69+
70+
const { getByText, findByText } = render(
71+
<StrictMode>
72+
<Counter />
73+
</StrictMode>,
74+
);
75+
76+
await findByText('bigAtom: {"a":[0]}');
77+
await findByText('atomA: [0]');
78+
79+
fireEvent.click(getByText('inc bigAtom'));
80+
await findByText('bigAtom: {"a":[0,1]}');
81+
await findByText('atomA: [0,1]');
82+
83+
fireEvent.click(getByText('inc atomA'));
84+
await findByText('bigAtom: {"a":[0,1,2]}');
85+
await findByText('atomA: [0,1,2]');
86+
87+
fireEvent.click(getByText('append'));
88+
await findByText('bigAtom: {"a":[0,1,2,3]}');
89+
await findByText('atomA: [0,1,2,3]');
90+
});
91+
92+
test('focus on async atom works', async () => {
93+
const baseAtom = atom([0]);
94+
const asyncAtom = atom(
95+
(get) => Promise.resolve(get(baseAtom)),
96+
async (get, set, param: SetStateAction<Promise<number[]>>) => {
97+
const prev = Promise.resolve(get(baseAtom));
98+
const next = await (typeof param === 'function' ? param(prev) : param);
99+
set(baseAtom, next);
100+
},
101+
);
102+
const focusFunction = (optic: O.OpticFor_<number[]>) => optic.appendTo();
103+
104+
const Counter = () => {
105+
const append = useSetAtom(focusAtom(asyncAtom, focusFunction));
106+
const [asyncValue, setAsync] = useAtom(asyncAtom);
107+
const [baseValue, setBase] = useAtom(baseAtom);
108+
return (
109+
<>
110+
<div>baseAtom: {JSON.stringify(baseValue)}</div>
111+
<div>asyncAtom: {JSON.stringify(asyncValue)}</div>
112+
<button onClick={() => append(baseValue.length)}>append</button>
113+
<button
114+
onClick={() => setAsync((p) => p.then((v) => [...v, v.length]))}
115+
>
116+
incr async
117+
</button>
118+
<button onClick={() => setBase((v) => [...v, v.length])}>
119+
incr base
120+
</button>
121+
</>
122+
);
123+
};
124+
125+
const { getByText, findByText } = render(
126+
<StrictMode>
127+
<Suspense fallback={<div>Loading...</div>}>
128+
<Counter />
129+
</Suspense>
130+
</StrictMode>,
131+
);
132+
133+
await findByText('baseAtom: [0]');
134+
await findByText('asyncAtom: [0]');
135+
136+
fireEvent.click(getByText('append'));
137+
await findByText('baseAtom: [0,1]');
138+
await findByText('asyncAtom: [0,1]');
139+
140+
fireEvent.click(getByText('incr async'));
141+
await findByText('baseAtom: [0,1,2]');
142+
await findByText('asyncAtom: [0,1,2]');
143+
144+
fireEvent.click(getByText('incr base'));
145+
await findByText('baseAtom: [0,1,2,3]');
146+
await findByText('asyncAtom: [0,1,2,3]');
147+
});
148+
149+
type BillingData = {
150+
id: string;
151+
};
152+
153+
type CustomerData = {
154+
id: string;
155+
billing: BillingData[];
156+
someOtherData: string;
157+
};
158+
159+
test('typescript should accept "undefined" as valid value for lens', async () => {
160+
const customerListAtom = atom<CustomerData[]>([]);
161+
162+
const foundCustomerAtom = focusAtom(customerListAtom, (optic) =>
163+
optic.find((el) => el.id === 'some-invalid-id'),
164+
);
165+
166+
const derivedLens = focusAtom(foundCustomerAtom, (optic) => optic.appendTo());
167+
168+
expectType<
169+
WritableAtom<void, [NotAnArrayType<CustomerData | undefined>], void>
170+
>(derivedLens);
171+
});
172+
173+
test('should work with promise based atoms with "undefined" value', async () => {
174+
const customerBaseAtom = atom<CustomerData | undefined>(undefined);
175+
176+
const asyncCustomerDataAtom = atom(
177+
async (get) => get(customerBaseAtom),
178+
async (_, set, nextValue: Promise<CustomerData>) => {
179+
set(customerBaseAtom, await nextValue);
180+
},
181+
);
182+
183+
const focusedPromiseAtom = focusAtom(asyncCustomerDataAtom, (optic) =>
184+
optic.appendTo(),
185+
);
186+
187+
expectType<
188+
WritableAtom<
189+
Promise<void>,
190+
[NotAnArrayType<CustomerData | undefined>],
191+
Promise<void>
192+
>
193+
>(focusedPromiseAtom);
194+
});

0 commit comments

Comments
 (0)