Skip to content

Commit fe82bfe

Browse files
committed
fix: improve atom list stability + remove deep parse feature
1 parent 9fe0cdd commit fe82bfe

File tree

14 files changed

+381
-1935
lines changed

14 files changed

+381
-1935
lines changed

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Features
44

5-
- Debug 🐞 raw or deeply nested (atoms-in-atoms) atom values with ease
5+
- Debug 🐞 atom values with ease
66
- Out-of-the-box 🔌 support for async/suspendible atoms
77
- Built-in Dark mode 🌗
88
- ✅ Supports custom `store`
@@ -116,10 +116,6 @@ type DevToolsProps = {
116116
// Custom nonce to allowlist jotai-devtools specific inline styles via CSP
117117
nonce?: string;
118118
options?: {
119-
// Parsing strategy for AtomViewer. Defaults to `raw`
120-
// `raw` - parses the top level atom value but does not parse the values of atoms within atoms
121-
// `deep-nested` - Parses values of atoms within atoms. Linear performance curve. Bigger the object, the slower the performance
122-
atomValueParser?: 'raw' | 'deep-nested';
123119
// Private atoms are used internally in atoms like `atomWithStorage` or `atomWithLocation`, etc. to manage state.
124120
// Defaults to `false`
125121
shouldShowPrivateAtoms?: boolean;

__tests__/devtools/AtomViewer.test.tsx

Lines changed: 67 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,73 @@ describe('DevTools - AtomViewer', () => {
189189
expect(container).toMatchSnapshot();
190190
});
191191
});
192+
193+
describe('auto unmount', () => {
194+
it('should unselect the atom when an atom is unsubscribed', async () => {
195+
const BasicAtoms = () => {
196+
const countAtom = useMemo(() => atom(0), []);
197+
countAtom.debugLabel = 'countAtom';
198+
const doubleCountAtom = useMemo(
199+
() => atom((get) => get(countAtom) * 2),
200+
[countAtom],
201+
);
202+
doubleCountAtom.debugLabel = 'doubleCountAtom';
203+
useAtomValue(doubleCountAtom);
204+
205+
return <div data-testid="basic-atoms"></div>;
206+
};
207+
208+
const ToggleAbleAtomWithDevTools = () => {
209+
const [shouldShow, setShouldShow] = React.useState(true);
210+
211+
const handleOntoggle = React.useCallback(() => {
212+
setShouldShow((s) => !s);
213+
}, [setShouldShow]);
214+
215+
return (
216+
<>
217+
{shouldShow ? <BasicAtoms /> : null}
218+
<button onClick={handleOntoggle}>Toggle</button>
219+
</>
220+
);
221+
};
222+
223+
const TestComponent = () => {
224+
return (
225+
<>
226+
<DevTools isInitialOpen={true} />
227+
<ToggleAbleAtomWithDevTools />
228+
</>
229+
);
230+
};
231+
232+
customRender(<TestComponent />);
233+
await act(async () => {
234+
await userEvent.click(screen.getByText('doubleCountAtom'));
235+
});
236+
expect(
237+
screen.getByTestId('display-detail-item-value-doubleCountAtom'),
238+
).toBeInTheDocument();
239+
240+
await act(async () => {
241+
await userEvent.click(screen.getByText('Toggle'));
242+
});
243+
244+
expect(screen.queryByText('Atom Details')).not.toBeInTheDocument();
245+
expect(
246+
screen.queryByText('display-detail-item-value-doubleCountAtom'),
247+
).not.toBeInTheDocument();
248+
expect(
249+
screen.getByTestId('atom-list-no-atoms-found-message'),
250+
).toHaveTextContent('No Atoms found!');
251+
expect(screen.getByLabelText('Search')).toBeInTheDocument();
252+
expect(
253+
screen.getByText(
254+
'Select an atom from the left panel to view the details',
255+
),
256+
).toBeInTheDocument();
257+
});
258+
});
192259
});
193260

194261
describe('Atom details', () => {
@@ -310,99 +377,5 @@ describe('DevTools - AtomViewer', () => {
310377
);
311378
});
312379
});
313-
314-
describe('Deep nested values', () => {
315-
it('should display atom details with deeply parsed value when an atom is selected', async () => {
316-
const NestedAtomsWithDevTools = () => {
317-
// Create atoms inside the component so that they are recreated for each test
318-
const countAtom = useMemo(() => atom(0), []);
319-
countAtom.debugLabel = 'countAtom';
320-
321-
const doubleNestedAtom = useMemo(
322-
() => atom(atom((get) => get(countAtom) * 2 + 1)),
323-
[countAtom],
324-
);
325-
326-
useAtomValue(countAtom);
327-
useAtomValue(doubleNestedAtom);
328-
return (
329-
<DevTools
330-
isInitialOpen={true}
331-
options={{ atomValueParser: 'deep-nested' }}
332-
/>
333-
);
334-
};
335-
336-
const { container } = customRender(<NestedAtomsWithDevTools />);
337-
338-
await act(async () => {
339-
await userEvent.click(screen.getByText('<unlabeled-atom>'));
340-
});
341-
342-
expect(screen.getByText('Atom Details')).toBeInTheDocument();
343-
expect(screen.getByText('Meta')).toBeInTheDocument();
344-
expect(screen.getByText('Debug Label')).toBeInTheDocument();
345-
expect(
346-
screen.getByTestId('display-detail-item-value-<unlabeled-atom>'),
347-
).toHaveTextContent('<unlabeled-atom>');
348-
expect(screen.getByText('Value type')).toBeInTheDocument();
349-
expect(
350-
screen.getByTestId('display-detail-item-value-atom'),
351-
).toHaveTextContent('atom');
352-
353-
expect(screen.getByText('Parsed value')).toBeInTheDocument();
354-
expect(screen.getByTestId('atom-parsed-value')).toHaveTextContent('1');
355-
356-
expect(screen.getByText('Dependents')).toBeInTheDocument();
357-
// There are no dependents for this atom yet because those dependents are not yet mounted
358-
expect(screen.getByText('No dependents')).toBeInTheDocument();
359-
await waitFor(() => expect(container).toMatchSnapshot());
360-
});
361-
362-
describe('Supports most primitive value types', () => {
363-
const AtomRenderer = ({ atom }: { atom: AnyAtom }) => {
364-
useAtomValue(atom);
365-
return (
366-
<DevTools
367-
isInitialOpen={true}
368-
options={{
369-
atomValueParser: 'deep-nested',
370-
}}
371-
/>
372-
);
373-
};
374-
375-
it.each`
376-
type | value | expected
377-
${'string'} | ${'some-string'} | ${'some-string'}
378-
${'number'} | ${123} | ${123}
379-
${'boolean'} | ${true} | ${true}
380-
${'boolean'} | ${false} | ${false}
381-
${'null'} | ${null} | ${'null'}
382-
${'undefined'} | ${undefined} | ${'undefined'}
383-
${'bigint'} | ${BigInt(123)} | ${'123'}
384-
${'symbol'} | ${Symbol('some-symbol')} | ${'Symbol(some-symbol)'}
385-
${'function'} | ${() => () => 'hello'} | ${"()=>'hello'"}
386-
${'object'} | ${{ foo: 'bar' }} | ${'{ "foo": "bar"}'}
387-
${'array'} | ${[1, 2, 3]} | ${'[ 1, 2, 3]'}
388-
`(
389-
'should parse "$type" value correctly',
390-
async ({ value, expected }) => {
391-
const valueAtom = atom(value);
392-
valueAtom.debugLabel = 'valueAtom';
393-
394-
customRender(<AtomRenderer atom={valueAtom} />);
395-
396-
await act(async () => {
397-
await userEvent.click(screen.getByText('valueAtom'));
398-
});
399-
400-
expect(screen.getByTestId('atom-parsed-value')).toHaveTextContent(
401-
expected,
402-
);
403-
},
404-
);
405-
});
406-
});
407380
});
408381
});

0 commit comments

Comments
 (0)