recoil
Version:
Recoil - A state management library for React
302 lines (283 loc) • 13 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
*/
;
import type { RecoilValue } from 'Recoil_RecoilValue';
const {
flushPromisesAndTimers,
getRecoilTestFn
} = require('../../testing/Recoil_TestingUtils');
let loadableWithError, loadableWithValue, getRecoilValueAsLoadable, noWait, waitForAll, waitForAny, waitForNone, store, selector, invariant;
const testRecoil = getRecoilTestFn(() => {
const {
makeStore
} = require('../../testing/Recoil_TestingUtils');
invariant = require('../../util/Recoil_invariant');
({
loadableWithError,
loadableWithValue
} = require('../../adt/Recoil_Loadable'));
({
getRecoilValueAsLoadable
} = require('../../core/Recoil_RecoilValueInterface'));
selector = require('../Recoil_selector');
({
noWait,
waitForAll,
waitForAny,
waitForNone
} = require('../Recoil_WaitFor'));
store = makeStore();
});
declare function get(atom: any): any;
declare function getValue<T>(recoilValue: RecoilValue<T>): T;
declare function getPromise<T>(recoilValue: RecoilValue<T>): Promise<T>;
let id = 0;
declare function asyncSelector<T, S>(dep?: RecoilValue<S>): [RecoilValue<T>, (T) => void, (Error) => void, () => boolean];
/* eslint-disable jest/valid-expect */
testRecoil('noWait - resolve', async () => {
const [dep, resolve] = asyncSelector();
const pTest = expect(getValue(noWait(dep)).toPromise()).resolves.toBe(42);
expect(getValue(noWait(dep)).contents).toBeInstanceOf(Promise);
resolve(42);
await flushPromisesAndTimers();
expect(getValue(noWait(dep)).contents).toBe(42);
await pTest;
});
testRecoil('noWait - reject', async () => {
const [dep, _resolve, reject] = asyncSelector();
declare class MyError extends Error {}
const pTest = expect(getValue(noWait(dep)).toPromise()).rejects.toBeInstanceOf(MyError);
expect(getValue(noWait(dep)).contents).toBeInstanceOf(Promise);
reject(new MyError());
await flushPromisesAndTimers();
expect(getValue(noWait(dep)).contents).toBeInstanceOf(MyError);
await pTest;
}); // TRUTH TABLE
// Dependencies waitForNone waitForAny waitForAll
// [loading, loading] [Promise, Promise] Promise Promise
// [value, loading] [value, Promise] [value, Promise] Promise
// [value, value] [value, value] [value, value] [value, value]
testRecoil('waitFor - resolve to values', async () => {
const [depA, resolveA] = asyncSelector();
const [depB, resolveB] = asyncSelector();
const deps = [depA, depB]; // Test for initial values
// watiForNone returns loadables with promises that resolve to their values
expect(getValue(waitForNone(deps)).every(r => r.state === 'loading')).toBe(true);
const depTest0 = expect(getValue(waitForNone(deps))[0].promiseMaybe()).resolves.toBe(0);
const depTest1 = expect(getValue(waitForNone(deps))[1].promiseMaybe()).resolves.toBe(1); // waitForAny returns a promise that resolves to the state with the next
// resolved value. So, that includes the first value and a promise for the second.
expect(get(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest0 = expect(getPromise(waitForAny(deps)).then(value => {
expect(value[0].valueMaybe()).toEqual(0);
return value[0].valueMaybe();
})).resolves.toEqual(0);
const anyTest1 = expect(getPromise(waitForAny(deps)).then(value => {
expect(value[1].promiseMaybe()).toBeInstanceOf(Promise);
return value[1].promiseMaybe();
})).resolves.toBe(1); // waitForAll returns a promise that resolves to the actual values
expect(get(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest0 = expect(getPromise(waitForAll(deps))).resolves.toEqual([0, 1]); // Resolve the first dep
resolveA(0);
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps))[0].contents).toBe(0);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(getValue(waitForAny(deps))[0].contents).toBe(0);
expect(getValue(waitForAny(deps))[1].contents).toBeInstanceOf(Promise);
expect(get(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest1 = expect(getPromise(waitForAll(deps))).resolves.toEqual([0, 1]); // Resolve the second dep
resolveB(1);
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps))[0].contents).toBe(0);
expect(getValue(waitForNone(deps))[1].contents).toBe(1);
expect(getValue(waitForAny(deps))[0].contents).toBe(0);
expect(getValue(waitForAny(deps))[1].contents).toBe(1);
expect(getValue(waitForAll(deps))[0]).toBe(0);
expect(getValue(waitForAll(deps))[1]).toBe(1);
await depTest0;
await depTest1;
await anyTest0;
await anyTest1;
await allTest0;
await allTest1;
}); // TRUTH TABLE
// Dependencies waitForNone waitForAny waitForAll
// [loading, loading] [Promise, Promise] Promise Promise
// [error, loading] [Error, Promise] Promise Error
// [error, error] [Error, Error] Error Error
testRecoil('waitFor - rejected', async () => {
const [depA, _resolveA, rejectA] = asyncSelector();
const [depB, _resolveB, rejectB] = asyncSelector();
const deps = [depA, depB];
get(waitForNone(deps));
declare class Error1 extends Error {}
declare class Error2 extends Error {} // We already tested for the initial values in the last test.
// But, test that the initial returned promises here resolve to their
// appropriate errors here.
expect(get(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest0 = expect(getPromise(waitForAny(deps)).then(r => {
expect(r[0].errorMaybe()).toBeInstanceOf(Error1);
return r[0].errorMaybe();
})).rejects.toBeInstanceOf(Error1);
const anyTest1 = expect(getPromise(waitForAny(deps)).then(r => {
expect(r[1].promiseMaybe()).toBeInstanceOf(Promise);
return r[1].promiseMaybe();
})).rejects.toBeInstanceOf(Error1);
const allTest = expect(get(waitForAll(deps))).rejects.toBeInstanceOf(Error1);
rejectA(new Error1());
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(get(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest2 = expect(get(waitForAny(deps))).rejects.toBeInstanceOf(Error1);
expect(get(waitForAll(deps))).toBeInstanceOf(Error1);
rejectB(new Error2());
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Error2);
expect(get(waitForAny(deps))).toBeInstanceOf(Error1);
expect(get(waitForAll(deps))).toBeInstanceOf(Error1);
await anyTest0;
await anyTest1;
await anyTest2;
await allTest;
}); // TRUTH TABLE
// Dependencies waitForNone waitForAny waitForAll
// [loading, loading] [Promise, Promise] Promise Promise
// [value, loading] [value, Promise] [value, Promise] Promise
// [value, error] [value, Error] [value, Error] Error
testRecoil('waitFor - resolve then reject', async () => {
const [depA, resolveA] = asyncSelector();
const [depB, _resolveB, rejectB] = asyncSelector();
const deps = [depA, depB];
get(waitForNone(deps));
declare class Error2 extends Error {} // Previous tests covered the initial values and resolving the initial value
// But, test that waitForAll resolves to the second error.
resolveA(0);
const allTest = expect(get(waitForAll(deps))).rejects.toBeInstanceOf(Error2);
rejectB(new Error2());
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps))[0].contents).toBe(0);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Error2);
expect(getValue(waitForAny(deps))[0].contents).toBe(0);
expect(getValue(waitForAny(deps))[1].contents).toBeInstanceOf(Error2);
expect(get(waitForAll(deps))).toBeInstanceOf(Error2);
await allTest;
}); // TRUTH TABLE
// Dependencies waitForNone waitForAny waitForAll
// [loading, loading] [Promise, Promise] Promise Promise
// [error, loading] [Error, Promise] Promise Error
// [error, value] [Error, value] [Error, value] Error
testRecoil('waitFor - reject then resolve', async () => {
const [depA, _resolveA, rejectA] = asyncSelector();
const [depB, resolveB] = asyncSelector();
const deps = [depA, depB];
get(waitForNone(deps));
declare class Error1 extends Error {}
const anyTest0 = expect(getPromise(waitForAny(deps))).resolves.toEqual([loadableWithError(new Error1()), loadableWithValue(1)]);
await flushPromisesAndTimers(); // Previous tests covered the initial values and the first rejection. But,
// test that the waitForAny provides the next state with the error and value
rejectA(new Error1());
expect(get(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest1 = expect(getPromise(waitForAny(deps))).resolves.toEqual([loadableWithError(new Error1()), loadableWithValue(1)]);
const allTest = expect(getPromise(waitForAll(deps))).rejects.toBeInstanceOf(Error1);
await flushPromisesAndTimers();
resolveB(1);
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForNone(deps))[1].contents).toBe(1);
expect(getValue(waitForAny(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForAny(deps))[1].contents).toBe(1);
expect(get(waitForAll(deps))).toBeInstanceOf(Error1);
await anyTest0;
await anyTest1;
await allTest;
}); // Similar as the first test that resolves both dependencies, but with named dependencies.
testRecoil('waitFor - named dependency version', async () => {
const [depA, resolveA] = asyncSelector();
const [depB, resolveB] = asyncSelector();
const deps = {
a: depA,
b: depB
};
expect(getValue(waitForNone(deps)).a.promiseMaybe()).toBeInstanceOf(Promise);
expect(getValue(waitForNone(deps)).b.promiseMaybe()).toBeInstanceOf(Promise);
const depTest0 = expect(getValue(waitForNone(deps)).a.promiseMaybe()).resolves.toBe(0);
const depTest1 = expect(getValue(waitForNone(deps)).b.promiseMaybe()).resolves.toBe(1);
expect(get(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest0 = expect(getPromise(waitForAny(deps)).then(value => {
expect(value.a.valueMaybe()).toEqual(0);
return value.a.valueMaybe();
})).resolves.toEqual(0);
const anyTest1 = expect(getPromise(waitForAny(deps)).then(value => {
expect(value.b.promiseMaybe()).toBeInstanceOf(Promise);
return value.b.promiseMaybe();
})).resolves.toBe(1);
expect(get(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest0 = expect(getPromise(waitForAll(deps))).resolves.toEqual({
a: 0,
b: 1
});
resolveA(0);
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps)).a.contents).toBe(0);
expect(getValue(waitForNone(deps)).b.contents).toBeInstanceOf(Promise);
expect(getValue(waitForAny(deps)).a.contents).toBe(0);
expect(getValue(waitForAny(deps)).b.contents).toBeInstanceOf(Promise);
expect(get(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest1 = expect(getPromise(waitForAll(deps))).resolves.toEqual({
a: 0,
b: 1
});
resolveB(1);
await flushPromisesAndTimers();
expect(getValue(waitForNone(deps)).a.contents).toBe(0);
expect(getValue(waitForNone(deps)).b.contents).toBe(1);
expect(getValue(waitForAny(deps)).a.contents).toBe(0);
expect(getValue(waitForAny(deps)).b.contents).toBe(1);
expect(getValue(waitForAll(deps)).a).toBe(0);
expect(getValue(waitForAll(deps)).b).toBe(1);
await depTest0;
await depTest1;
await anyTest0;
await anyTest1;
await allTest0;
await allTest1;
});
testRecoil('waitForAll - Evaluated concurrently', async () => {
const [depA, resolveA, _rejectA, evaluatedA] = asyncSelector();
const [depB, _resolveB, _rejectB, evaluatedB] = asyncSelector();
const deps = [depA, depB];
expect(evaluatedA()).toBe(false);
expect(evaluatedB()).toBe(false);
getPromise(waitForAll(deps));
await flushPromisesAndTimers(); // Confirm dependencies were evaluated in parallel
expect(evaluatedA()).toBe(true);
expect(evaluatedB()).toBe(true);
resolveA(0);
getPromise(waitForAll(deps));
await flushPromisesAndTimers();
expect(evaluatedA()).toBe(true);
expect(evaluatedB()).toBe(true);
});
testRecoil('waitForAll - mixed sync and async deps', async () => {
const [depA, resolveA] = asyncSelector();
const depB = selector({
key: 'mydepkeyB',
get: () => 1
});
const deps = [depA, depB];
const allTest = expect(getPromise(waitForAll(deps))).resolves.toEqual([0, 1]);
resolveA(0);
await flushPromisesAndTimers();
expect(getValue(waitForAll(deps))).toEqual([0, 1]);
await allTest;
});
/* eslint-enable jest/valid-expect */