/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+perf_viz * @flow strict-local * @format */ 'use strict'; const { getRecoilTestFn, } = require('recoil-shared/__test_utils__/Recoil_TestingUtils'); let React, act, freshSnapshot, atom, atomFamily, selector, ReadsAtom, asyncSelector, componentThatReadsAndWritesAtom, renderElements, useRecoilTransactionObserver; const testRecoil = getRecoilTestFn(() => { React = require('react'); ({act} = require('ReactTestUtils')); ({freshSnapshot} = require('../../core/Recoil_Snapshot')); atom = require('../../recoil_values/Recoil_atom'); atomFamily = require('../../recoil_values/Recoil_atomFamily'); selector = require('../../recoil_values/Recoil_selector'); ({ ReadsAtom, asyncSelector, componentThatReadsAndWritesAtom, renderElements, } = require('recoil-shared/__test_utils__/Recoil_TestingUtils')); ({useRecoilTransactionObserver} = require('../Recoil_SnapshotHooks')); }); function TransactionObserver({callback}) { useRecoilTransactionObserver(callback); return null; } // Run test first since it deals with all registered atoms testRecoil('getNodes', () => { let snapshot = freshSnapshot(); function UseRecoilTransactionObserver() { useRecoilTransactionObserver(p => { p.snapshot.retain(); snapshot = p.snapshot; }); return null; } const atoms = atomFamily({ key: 'useRecoilTransactionObserver getNodes atom', default: x => x, }); const [ReadsAtomA, setAtomA, resetAtomA] = componentThatReadsAndWritesAtom( atoms('A'), ); const [ReadsAtomB, setAtomB] = componentThatReadsAndWritesAtom(atoms('B')); const selectorA = selector({ key: 'useRecoilTransactionObserver getNodes selector', get: ({get}) => get(atoms('A')) + '-SELECTOR', }); const c = renderElements( <> , ); expect(c.textContent).toEqual('"A""B""A-SELECTOR"'); expect( Array.from(snapshot.getNodes_UNSTABLE({isInitialized: true})).length, ).toEqual(0); act(() => setAtomA('A')); // >= 3 because we expect at least nodes for atom's A and B from // the family and selectorA. In reality we could get more due to internal // helper selectors and default fallback atoms. expect( Array.from(snapshot.getNodes_UNSTABLE({isInitialized: true})).length, ).toBeGreaterThanOrEqual(3); const nodes = Array.from(snapshot.getNodes_UNSTABLE({isInitialized: true})); expect(nodes).toEqual( expect.arrayContaining([atoms('A'), atoms('B'), selectorA]), ); // Test atom A is set const aDirty = Array.from(snapshot.getNodes_UNSTABLE({isModified: true})); expect(aDirty.length).toEqual(1); expect(snapshot.getLoadable(aDirty[0]).contents).toEqual('A'); // Test atom B is set act(() => setAtomB('B')); const bDirty = Array.from(snapshot.getNodes_UNSTABLE({isModified: true})); expect(bDirty.length).toEqual(1); expect(snapshot.getLoadable(bDirty[0]).contents).toEqual('B'); // Test atoms const atomNodes = Array.from( snapshot.getNodes_UNSTABLE({isInitialized: true}), ); expect(atomNodes.map(atom => snapshot.getLoadable(atom).contents)).toEqual( expect.arrayContaining(['A', 'B']), ); // Test selector const selectorNodes = Array.from( snapshot.getNodes_UNSTABLE({isInitialized: true}), ); expect( selectorNodes.map(atom => snapshot.getLoadable(atom).contents), ).toEqual(expect.arrayContaining(['A-SELECTOR'])); // Test Reset act(resetAtomA); const resetDirty = Array.from(snapshot.getNodes_UNSTABLE({isModified: true})); expect(resetDirty.length).toEqual(1); expect(resetDirty[0]).toBe(aDirty[0]); // TODO Test dirty selectors }); testRecoil('Can observe atom value', async () => { const atomA = atom({ key: 'Observe Atom A', default: 'DEFAULT A', }); const atomB = atom({ key: 'Observe Atom B', default: 'DEFAULT B', }); const [WriteAtomA, setAtomA, resetA] = componentThatReadsAndWritesAtom(atomA); const [WriteAtomB, setAtomB] = componentThatReadsAndWritesAtom(atomB); const transactions = []; renderElements( <> { snapshot.retain(); previousSnapshot.retain(); transactions.push({snapshot, previousSnapshot}); }} /> , ); act(() => setAtomB('SET B')); expect(transactions.length).toEqual(1); await expect(transactions[0].snapshot.getPromise(atomA)).resolves.toEqual( 'DEFAULT A', ); await expect( transactions[0].previousSnapshot.getPromise(atomA), ).resolves.toEqual('DEFAULT A'); await expect(transactions[0].snapshot.getPromise(atomB)).resolves.toEqual( 'SET B', ); await expect( transactions[0].previousSnapshot.getPromise(atomB), ).resolves.toEqual('DEFAULT B'); act(() => setAtomA('SET A')); expect(transactions.length).toEqual(2); await expect(transactions[1].snapshot.getPromise(atomA)).resolves.toEqual( 'SET A', ); await expect( transactions[1].previousSnapshot.getPromise(atomA), ).resolves.toEqual('DEFAULT A'); await expect(transactions[1].snapshot.getPromise(atomB)).resolves.toEqual( 'SET B', ); await expect( transactions[1].previousSnapshot.getPromise(atomB), ).resolves.toEqual('SET B'); act(() => resetA()); expect(transactions.length).toEqual(3); await expect(transactions[2].snapshot.getPromise(atomA)).resolves.toEqual( 'DEFAULT A', ); await expect( transactions[2].previousSnapshot.getPromise(atomA), ).resolves.toEqual('SET A'); await expect(transactions[2].snapshot.getPromise(atomB)).resolves.toEqual( 'SET B', ); await expect( transactions[2].previousSnapshot.getPromise(atomB), ).resolves.toEqual('SET B'); }); testRecoil('Can observe selector value', async () => { const atomA = atom({ key: 'Observe Atom for Selector', default: 'DEFAULT', }); const selectorA = selector({ key: 'Observer Selector As', get: ({get}) => `SELECTOR ${get(atomA)}`, }); const [WriteAtom, setAtom] = componentThatReadsAndWritesAtom(atomA); const transactions = []; renderElements( <> { snapshot.retain(); previousSnapshot.retain(); transactions.push({snapshot, previousSnapshot}); }} /> , ); act(() => setAtom('SET')); expect(transactions.length).toEqual(1); await expect(transactions[0].snapshot.getPromise(atomA)).resolves.toEqual( 'SET', ); await expect( transactions[0].previousSnapshot.getPromise(atomA), ).resolves.toEqual('DEFAULT'); await expect(transactions[0].snapshot.getPromise(selectorA)).resolves.toEqual( 'SELECTOR SET', ); await expect( transactions[0].previousSnapshot.getPromise(selectorA), ).resolves.toEqual('SELECTOR DEFAULT'); }); testRecoil('Can observe async selector value', async () => { const atomA = atom({ key: 'Observe Atom for Async Selector', default: 'DEFAULT', }); const [WriteAtom, setAtom] = componentThatReadsAndWritesAtom(atomA); const [selectorA, resolveA] = asyncSelector(); const transactions = []; renderElements( <> { snapshot.retain(); previousSnapshot.retain(); transactions.push({snapshot, previousSnapshot}); }} /> , ); act(() => setAtom('SET')); expect(transactions.length).toEqual(1); expect(transactions[0].snapshot.getLoadable(selectorA).state).toEqual( 'loading', ); act(() => resolveA('RESOLVE')); expect(transactions.length).toEqual(1); await expect(transactions[0].snapshot.getPromise(selectorA)).resolves.toEqual( 'RESOLVE', ); });