recoil
Version:
Recoil - A state management library for React
233 lines (221 loc) • 6.73 kB
Flow
/**
* 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
*/
;
const {
getRecoilTestFn
} = require('../../testing/Recoil_TestingUtils');
let React, useRef, useState, act, useStoreRef, atom, atomFamily, selector, useRecoilCallback, useSetRecoilState, ReadsAtom, flushPromisesAndTimers, renderElements, invariant;
const testRecoil = getRecoilTestFn(() => {
React = require('React');
({
useRef,
useState
} = require('React'));
({
act
} = require('ReactTestUtils'));
({
useStoreRef
} = require('../../core/Recoil_RecoilRoot.react'));
({
atom,
atomFamily,
selector,
useRecoilCallback,
useSetRecoilState
} = require('../../Recoil_index'));
({
ReadsAtom,
flushPromisesAndTimers,
renderElements
} = require('../../testing/Recoil_TestingUtils'));
invariant = require('../../util/Recoil_invariant');
});
describe('useRecoilCallback', () => {
testRecoil('Reads Recoil values', async () => {
const anAtom = atom({
key: 'atom1',
default: 'DEFAULT'
});
let pTest = Promise.reject(new Error("Callback didn't resolve"));
let cb;
declare function Component(): any;
renderElements(<Component />);
act(() => void cb());
await pTest;
});
testRecoil('Can read Recoil values without throwing', async () => {
const anAtom = atom({
key: 'atom2',
default: 123
});
const asyncSelector = selector({
key: 'sel',
get: () => {
return new Promise(() => undefined);
}
});
let didRun = false;
let cb;
declare function Component(): any;
renderElements(<Component />);
act(() => void cb());
expect(didRun).toBe(true);
});
testRecoil('Sets Recoil values (by queueing them)', async () => {
const anAtom = atom({
key: 'atom3',
default: 'DEFAULT'
});
let cb;
let pTest = Promise.reject(new Error("Callback didn't resolve"));
declare function Component(): any;
const container = renderElements(<>
<Component />
<ReadsAtom atom={anAtom} />
</>);
expect(container.textContent).toBe('"DEFAULT"');
act(() => void cb(123));
expect(container.textContent).toBe('123');
await pTest;
});
testRecoil('Reset Recoil values', async () => {
const anAtom = atom({
key: 'atomReset',
default: 'DEFAULT'
});
let setCB, resetCB;
declare function Component(): any;
const container = renderElements(<>
<Component />
<ReadsAtom atom={anAtom} />
</>);
expect(container.textContent).toBe('"DEFAULT"');
act(() => void setCB(123));
expect(container.textContent).toBe('123');
act(() => void resetCB());
expect(container.textContent).toBe('"DEFAULT"');
});
testRecoil('Sets Recoil values from async callback', async () => {
const anAtom = atom({
key: 'set async callback',
default: 'DEFAULT'
});
let cb;
const pTest = [];
declare function Component(): any;
const container = renderElements([<Component />, <ReadsAtom atom={anAtom} />]);
expect(container.textContent).toBe('"DEFAULT"');
act(() => void cb(123));
expect(container.textContent).toBe('123');
act(() => void cb(456));
expect(container.textContent).toBe('456');
for (const aTest of pTest) {
await aTest;
}
});
testRecoil('Reads from a snapshot created at callback call time', async () => {
const anAtom = atom({
key: 'atom4',
default: 123
});
let cb;
let setter;
let seenValue = null;
declare var delay: () => any; // no delay initially
declare function Component(): any; // It sees an update flushed after the cb is created:
renderElements(<Component />);
act(() => setter(345));
act(() => void cb());
await flushPromisesAndTimers();
await flushPromisesAndTimers();
expect(seenValue).toBe(345); // But does not see an update flushed while the cb is in progress:
seenValue = null;
declare var resumeCallback: () => any;
delay = () => {
return new Promise(resolve => {
resumeCallback = resolve;
});
};
act(() => void cb());
act(() => setter(678));
resumeCallback();
await flushPromisesAndTimers();
await flushPromisesAndTimers();
expect(seenValue).toBe(345);
});
testRecoil('Setter updater sees current state', () => {
const myAtom = atom({
key: 'useRecoilCallback updater',
default: 'DEFAULT'
});
let setAtom;
let cb;
declare function Component(): any;
const c = renderElements(<>
<ReadsAtom atom={myAtom} />
<Component />
</>);
expect(c.textContent).toEqual('"DEFAULT"'); // Set then callback in the same transaction
act(() => {
setAtom('SET');
cb('SET');
cb('UPDATE AGAIN');
});
expect(c.textContent).toEqual('"UPDATE AGAIN"');
});
testRecoil('goes to snapshot', async () => {
const myAtom = atom({
key: 'Goto Snapshot From Callback',
default: 'DEFAULT'
});
let cb;
declare function RecoilCallback(): any;
const c = renderElements(<>
<ReadsAtom atom={myAtom} />
<RecoilCallback />
</>);
expect(c.textContent).toEqual('"DEFAULT"');
act(() => void cb());
await flushPromisesAndTimers();
expect(c.textContent).toEqual('"SET IN SNAPSHOT"');
});
testRecoil('Updates are batched', () => {
const family = atomFamily({
key: 'useRecoilCallback/batching/family',
default: 0
});
let cb;
declare function RecoilCallback(): any;
let store: any; // flowlint-line unclear-type:off
declare function GetStore(): any;
renderElements(<>
<RecoilCallback />
<GetStore />
</>);
invariant(store, 'store should be initialized');
const originalReplaceState = store.replaceState;
store.replaceState = jest.fn(originalReplaceState);
expect(store.replaceState).toHaveBeenCalledTimes(0);
act(() => cb());
expect(store.replaceState).toHaveBeenCalledTimes(1);
store.replaceState = originalReplaceState;
});
}); // Test that we always get a consistent instance of the callback function
// from useRecoilCallback() when it is memoizaed
testRecoil('Consistent callback function', () => {
let setIteration;
declare var Component: () => any;
const out = renderElements(<Component />);
expect(out.textContent).toBe('0');
act(() => setIteration(1)); // Force a re-render of the Component
expect(out.textContent).toBe('1');
});