/**
* 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+recoil
* @flow strict-local
* @format
*/
'use strict';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let React,
useState,
act,
freshSnapshot,
useGotoRecoilSnapshot,
useRecoilCallback,
useRecoilValue,
atom,
constSelector,
selector,
ReadsAtom,
asyncSelector,
componentThatReadsAndWritesAtom,
flushPromisesAndTimers,
renderElements;
const testRecoil = getRecoilTestFn(() => {
React = require('react');
({useState} = require('react'));
({act} = require('ReactTestUtils'));
({freshSnapshot} = require('../../core/Recoil_Snapshot'));
({useRecoilValue} = require('../../hooks/Recoil_Hooks'));
({useGotoRecoilSnapshot} = require('../../hooks/Recoil_SnapshotHooks'));
({useRecoilCallback} = require('../../hooks/Recoil_useRecoilCallback'));
atom = require('../../recoil_values/Recoil_atom');
constSelector = require('../../recoil_values/Recoil_constSelector');
selector = require('../../recoil_values/Recoil_selector');
({
ReadsAtom,
asyncSelector,
componentThatReadsAndWritesAtom,
flushPromisesAndTimers,
renderElements,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils'));
});
testRecoil('Goto mapped snapshot', async () => {
const snapshot = freshSnapshot();
snapshot.retain();
const myAtom = atom({
key: 'Goto Snapshot Atom',
default: 'DEFAULT',
});
const [ReadsAndWritesAtom, setAtom] = componentThatReadsAndWritesAtom(myAtom);
// $FlowFixMe[incompatible-call] added when improving typing for this parameters
const mySelector = constSelector(myAtom);
const updatedSnapshot = snapshot.map(({set}) => {
set(myAtom, 'SET IN SNAPSHOT');
});
updatedSnapshot.retain();
let gotoRecoilSnapshot;
function GotoRecoilSnapshot() {
gotoRecoilSnapshot = useGotoRecoilSnapshot();
return null;
}
const c = renderElements(
<>
>,
);
expect(c.textContent).toEqual('"DEFAULT""DEFAULT"');
act(() => setAtom('SET IN CURRENT'));
expect(c.textContent).toEqual('"SET IN CURRENT""SET IN CURRENT"');
await expect(updatedSnapshot.getPromise(myAtom)).resolves.toEqual(
'SET IN SNAPSHOT',
);
act(() => gotoRecoilSnapshot(updatedSnapshot));
expect(c.textContent).toEqual('"SET IN SNAPSHOT""SET IN SNAPSHOT"');
act(() => setAtom('SET AGAIN IN CURRENT'));
expect(c.textContent).toEqual('"SET AGAIN IN CURRENT""SET AGAIN IN CURRENT"');
// Test that atoms set after snapshot were created are reset
act(() => gotoRecoilSnapshot(snapshot));
expect(c.textContent).toEqual('"DEFAULT""DEFAULT"');
});
testRecoil('Goto callback snapshot', () => {
const myAtom = atom({
key: 'Goto Snapshot From Callback',
default: 'DEFAULT',
});
const [ReadsAndWritesAtom, setAtom] = componentThatReadsAndWritesAtom(myAtom);
// $FlowFixMe[incompatible-call] added when improving typing for this parameters
const mySelector = constSelector(myAtom);
let cb;
function RecoilCallback() {
const gotoSnapshot = useGotoRecoilSnapshot();
cb = useRecoilCallback(({snapshot}) => () => {
const updatedSnapshot = snapshot.map(({set}) => {
set(myAtom, 'SET IN SNAPSHOT');
});
gotoSnapshot(updatedSnapshot);
});
return null;
}
const c = renderElements(
<>
>,
);
expect(c.textContent).toEqual('"DEFAULT""DEFAULT"');
act(() => setAtom('SET IN CURRENT'));
expect(c.textContent).toEqual('"SET IN CURRENT""SET IN CURRENT"');
act(cb);
expect(c.textContent).toEqual('"SET IN SNAPSHOT""SET IN SNAPSHOT"');
});
testRecoil('Goto snapshot with dependent async selector', async () => {
const snapshot = freshSnapshot();
snapshot.retain();
const myAtom = atom({
key: 'atom for dep async snapshot',
default: 'DEFAULT',
});
const [ReadsAndWritesAtom, setAtom] = componentThatReadsAndWritesAtom(myAtom);
const mySelector = selector({
key: 'selector for async snapshot',
get: ({get}) => {
const dep = get(myAtom);
return Promise.resolve(dep);
},
});
const updatedSnapshot = snapshot.map(({set}) => {
set(myAtom, 'SET IN SNAPSHOT');
});
updatedSnapshot.retain();
let gotoRecoilSnapshot;
function GotoRecoilSnapshot() {
gotoRecoilSnapshot = useGotoRecoilSnapshot();
return null;
}
const c = renderElements(
<>
>,
);
expect(c.textContent).toEqual('loading');
await flushPromisesAndTimers();
await flushPromisesAndTimers();
expect(c.textContent).toEqual('"DEFAULT""DEFAULT"');
act(() => setAtom('SET IN CURRENT'));
await flushPromisesAndTimers();
expect(c.textContent).toEqual('"SET IN CURRENT""SET IN CURRENT"');
await expect(updatedSnapshot.getPromise(myAtom)).resolves.toEqual(
'SET IN SNAPSHOT',
);
act(() => gotoRecoilSnapshot(updatedSnapshot));
await flushPromisesAndTimers();
expect(c.textContent).toEqual('"SET IN SNAPSHOT""SET IN SNAPSHOT"');
});
testRecoil('Goto snapshot with async selector', async () => {
const snapshot = freshSnapshot();
snapshot.retain();
const [mySelector, resolve] = asyncSelector();
let gotoRecoilSnapshot;
function GotoRecoilSnapshot() {
gotoRecoilSnapshot = useGotoRecoilSnapshot();
return null;
}
const c = renderElements(
<>
>,
);
expect(c.textContent).toEqual('loading');
act(() => resolve('RESOLVE'));
await flushPromisesAndTimers();
await flushPromisesAndTimers();
expect(c.textContent).toEqual('"RESOLVE"');
act(() => gotoRecoilSnapshot(snapshot));
expect(c.textContent).toEqual('"RESOLVE"');
});
// Test that going to a snapshot where an atom was not yet initialized will
// not cause the atom to be re-initialized when used again.
testRecoil(
'Effects going to previous snapshot',
({strictMode, concurrentMode}) => {
const sm = strictMode && concurrentMode ? 2 : 1;
let init = 0;
const myAtom = atom({
key: 'gotoSnapshot effect',
default: 'DEFAULT',
effects: [
() => {
init++;
},
],
});
let forceUpdate;
function ReadAtom() {
const [_, setValue] = useState({});
forceUpdate = () => setValue({});
return useRecoilValue(myAtom);
}
let gotoRecoilSnapshot;
function GotoRecoilSnapshot() {
gotoRecoilSnapshot = useGotoRecoilSnapshot();
return null;
}
expect(init).toEqual(0);
renderElements(
<>
>,
);
expect(init).toEqual(1 * sm);
act(forceUpdate);
expect(init).toEqual(1 * sm);
act(() => gotoRecoilSnapshot?.(freshSnapshot()));
expect(init).toEqual(1 * sm);
act(forceUpdate);
expect(init).toEqual(1 * sm);
act(forceUpdate);
expect(init).toEqual(1 * sm);
},
);