recoil
Version:
Recoil - A state management library for React
231 lines (225 loc) • 8.2 kB
Flow
/**
* Copyright (c) Facebook, Inc. and its affiliates. Confidential and proprietary.
*
* @emails oncall+recoil
* @flow strict-local
* @format
*/
;
import type { Store } from '../Recoil_State';
const {
getRecoilTestFn
} = require('../../__test_utils__/Recoil_TestingUtils');
let React, useState, ReactDOM, act, useSetRecoilState, atom, constSelector, selector, ReadsAtom, componentThatReadsAndWritesAtom, renderElements, RecoilRoot, useStoreRef;
const testRecoil = getRecoilTestFn(() => {
React = require('react');
({
useState
} = require('react'));
ReactDOM = require('ReactDOMLegacy_DEPRECATED');
({
act
} = require('ReactTestUtils'));
({
useSetRecoilState
} = require('../../hooks/Recoil_Hooks'));
atom = require('../../recoil_values/Recoil_atom');
constSelector = require('../../recoil_values/Recoil_constSelector');
selector = require('../../recoil_values/Recoil_selector');
({
ReadsAtom,
componentThatReadsAndWritesAtom,
renderElements
} = require('../../__test_utils__/Recoil_TestingUtils'));
({
RecoilRoot
} = require('../Recoil_RecoilRoot.react'));
({
useStoreRef
} = require('../Recoil_RecoilRoot.react'));
});
describe('initializeState', () => {
testRecoil('initialize atom', () => {
const myAtom = atom({
key: 'RecoilRoot - initializeState - atom',
default: 'DEFAULT'
}); // $FlowFixMe[incompatible-call] added when improving typing for this parameters
const mySelector = constSelector(myAtom);
declare function initializeState(arg0: any): any;
const container = document.createElement('div');
act(() => {
ReactDOM.render(<RecoilRoot initializeState={initializeState}>
<ReadsAtom atom={myAtom} />
<ReadsAtom atom={mySelector} />
</RecoilRoot>, container);
});
expect(container.textContent).toEqual('"INITIALIZE""INITIALIZE"');
});
testRecoil('initialize selector', () => {
const myAtom = atom({
key: 'RecoilRoot - initializeState - selector',
default: 'DEFAULT'
});
const mySelector = selector({
key: 'RecoilRoot - initializeState - selector selector',
get: ({
get
}) => get(myAtom),
set: ({
set
}, newValue) => set(myAtom, newValue)
});
declare function initializeState(arg0: any): any;
const container = document.createElement('div');
act(() => {
ReactDOM.render(<RecoilRoot initializeState={initializeState}>
<ReadsAtom atom={myAtom} />
<ReadsAtom atom={mySelector} />
</RecoilRoot>, container);
});
expect(container.textContent).toEqual('"INITIALIZE""INITIALIZE"');
});
testRecoil('Atom Effects run with global initialization', () => {
let effectRan = 0;
const myAtom = atom<string>({
key: 'RecoilRoot - initializeState - atom effects',
default: 'DEFAULT',
effects_UNSTABLE: [({
setSelf
}) => {
effectRan++;
setSelf(current => {
// Effects are run first.
expect(current).toEqual('DEFAULT');
return 'EFFECT';
});
}]
});
declare function initializeState(arg0: any): any;
expect(effectRan).toEqual(0);
const container1 = document.createElement('div');
act(() => {
ReactDOM.render(<RecoilRoot initializeState={initializeState}>NO READ</RecoilRoot>, container1);
}); // Effects are run when initialized with initializeState, even if not read.
expect(container1.textContent).toEqual('NO READ');
expect(effectRan).toEqual(1);
const container2 = document.createElement('div');
act(() => {
ReactDOM.render(<RecoilRoot initializeState={initializeState}>
<ReadsAtom atom={myAtom} />
</RecoilRoot>, container2);
}); // Effects are run first, initializeState() takes precedence
expect(container2.textContent).toEqual('"INITIALIZE"');
expect(effectRan).toEqual(2);
});
testRecoil('initialize with nested store', () => {
declare var GetStore: (arg0: {
children: (Store) => React.Node
}) => any;
const container = document.createElement('div');
act(() => {
ReactDOM.render(<RecoilRoot>
<GetStore>
{storeA => <RecoilRoot store_INTERNAL={storeA}>
<GetStore>
{storeB => {
expect(storeA === storeB).toBe(true);
return 'NESTED_ROOT/';
}}
</GetStore>
</RecoilRoot>}
</GetStore>
ROOT
</RecoilRoot>, container);
});
expect(container.textContent).toEqual('NESTED_ROOT/ROOT');
});
});
testRecoil('Impure state updater functions that trigger atom updates are detected', () => {
// This test ensures that we throw a clean error rather than mysterious breakage
// if the user supplies a state updater function that triggers another update
// within its execution. These state updater functions are supposed to be pure.
// We can't detect all forms of impurity but this one in particular will make
// Recoil break, so we detect it and throw an error.
const atomA = atom({
key: 'RecoilRoot/impureUpdater/a',
default: 0
});
const atomB = atom({
key: 'RecoilRoot/impureUpdater/b',
default: 0
});
let update;
declare function Component(): any;
renderElements(<Component />);
expect(() => act(() => {
update();
})).toThrow('pure function');
});
describe('override prop', () => {
testRecoil('RecoilRoots create a new Recoil scope when override is true or undefined', () => {
const myAtom = atom({
key: 'RecoilRoot/override/atom',
default: 'DEFAULT'
});
const [ReadsWritesAtom, setAtom] = componentThatReadsAndWritesAtom(myAtom);
const container = renderElements(<RecoilRoot>
<ReadsAtom atom={myAtom} />
<RecoilRoot>
<ReadsWritesAtom />
</RecoilRoot>
</RecoilRoot>);
expect(container.textContent).toEqual('"DEFAULT""DEFAULT"');
act(() => setAtom('SET'));
expect(container.textContent).toEqual('"DEFAULT""SET"');
});
testRecoil('A RecoilRoot performs no function if override is false and it has an ancestor RecoilRoot', () => {
const myAtom = atom({
key: 'RecoilRoot/override/atom',
default: 'DEFAULT'
});
const [ReadsWritesAtom, setAtom] = componentThatReadsAndWritesAtom(myAtom);
const container = renderElements(<RecoilRoot>
<ReadsAtom atom={myAtom} />
<RecoilRoot override={false}>
<ReadsAtom atom={myAtom} />
<RecoilRoot override={false}>
<ReadsWritesAtom />
</RecoilRoot>
</RecoilRoot>
</RecoilRoot>);
expect(container.textContent).toEqual('"DEFAULT""DEFAULT""DEFAULT"');
act(() => setAtom('SET'));
expect(container.textContent).toEqual('"SET""SET""SET"');
});
testRecoil('Unmounting a nested RecoilRoot with override set to false does not clean up ancestor Recoil atoms', () => {
const myAtom = atom({
key: 'RecoilRoot/override/atom',
default: 'DEFAULT'
});
const [ReadsWritesAtom, setAtom] = componentThatReadsAndWritesAtom(myAtom);
let setRenderNestedRoot;
declare var NestedRootContainer: () => any;
const container = renderElements(<RecoilRoot>
<ReadsAtom atom={myAtom} />
<NestedRootContainer />
</RecoilRoot>);
expect(container.textContent).toEqual('"DEFAULT""DEFAULT"');
act(() => setAtom('SET'));
act(() => setRenderNestedRoot(false));
expect(container.textContent).toEqual('"SET"');
});
testRecoil('A RecoilRoot functions normally if override is false and it does not have an ancestor RecoilRoot', () => {
const myAtom = atom({
key: 'RecoilRoot/override/atom',
default: 'DEFAULT'
});
const [ReadsWritesAtom, setAtom] = componentThatReadsAndWritesAtom(myAtom);
const container = renderElements(<RecoilRoot override={false}>
<ReadsWritesAtom />
</RecoilRoot>);
expect(container.textContent).toEqual('"DEFAULT"');
act(() => setAtom('SET'));
expect(container.textContent).toEqual('"SET"');
});
});