Version 1.0.2 mit node_modules Verzeichnis

This commit is contained in:
ISA
2024-10-02 07:58:24 +02:00
parent f353a06b1b
commit 62b6e55a0a
68228 changed files with 4548477 additions and 651 deletions

View File

@@ -0,0 +1,639 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {RecoilValue} from '../../core/Recoil_RecoilValue';
const {
flushPromisesAndTimers,
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let getRecoilValueAsLoadable,
noWait,
waitForAll,
waitForAllSettled,
waitForAny,
waitForNone,
store,
selector,
invariant;
const testRecoil = getRecoilTestFn(() => {
const {
makeStore,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
invariant = require('recoil-shared/util/Recoil_invariant');
({
getRecoilValueAsLoadable,
} = require('../../core/Recoil_RecoilValueInterface'));
selector = require('../Recoil_selector');
({
noWait,
waitForAll,
waitForAllSettled,
waitForAny,
waitForNone,
} = require('../Recoil_WaitFor'));
store = makeStore();
});
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
function getLoadable(atom) {
return getRecoilValueAsLoadable(store, atom).contents;
}
function getState<T>(
recoilValue: RecoilValue<T>,
): 'loading' | 'hasValue' | 'hasError' {
return getRecoilValueAsLoadable(store, recoilValue).state;
}
function getValue<T>(recoilValue: RecoilValue<T>): T {
const loadable = getRecoilValueAsLoadable(store, recoilValue);
if (loadable.state !== 'hasValue') {
throw new Error(`expected atom "${recoilValue.key}" to have a value`);
}
return loadable.contents;
}
function getPromise<T>(recoilValue: RecoilValue<T>): Promise<T> {
const loadable = getRecoilValueAsLoadable(store, recoilValue);
if (loadable.state !== 'loading') {
throw new Error(`expected atom "${recoilValue.key}" to be a promise`);
}
return loadable.toPromise();
}
let id = 0;
function asyncSelector<T, S>(
dep?: RecoilValue<S>,
): [RecoilValue<T>, (T) => void, (Error) => void, () => boolean] {
let resolve: T => void = () => invariant(false, 'bug in test code'); // make flow happy with initialization
let reject: mixed => void = () => invariant(false, 'bug in test code');
let evaluated = false;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// $FlowFixMe[incompatible-call]
const sel = selector({
key: `AsyncSelector${id++}`,
get: ({get}) => {
evaluated = true;
if (dep != null) {
get(dep);
}
return promise;
},
});
return [sel, resolve, reject, () => evaluated];
}
/* eslint-disable jest/valid-expect */
testRecoil('noWait - resolve', async () => {
const [dep, resolve] = asyncSelector<number, _>();
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<$FlowFixMe, _>();
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 waitForAllSettled
// [loading, loading] [Promise, Promise] Promise Promise Promise
// [value, loading] [value, Promise] [value, Promise] Promise Promise
// [value, value] [value, value] [value, value] [value, value] [value, value]
testRecoil('waitFor - resolve to values', async () => {
const [depA, resolveA] = asyncSelector<$FlowFixMe | number, _>();
const [depB, resolveB] = asyncSelector<$FlowFixMe | number, _>();
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(getLoadable(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(getLoadable(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(getLoadable(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 waitForAllSettled
// [loading, loading] [Promise, Promise] Promise Promise Promise
// [error, loading] [Error, Promise] [Error, Promise] Error Promise
// [error, error] [Error, Error] [Error, Error] Error [Error, Error]
testRecoil('waitFor - rejected', async () => {
const [depA, _resolveA, rejectA] = asyncSelector<$FlowFixMe, _>();
const [depB, _resolveB, rejectB] = asyncSelector<$FlowFixMe, _>();
const deps = [depA, depB];
class Error1 extends Error {}
class Error2 extends Error {}
// All deps Loading Tests
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Promise);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAny(deps))).toEqual('loading');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest0 = expect(
getPromise(waitForAny(deps)).then(res => {
expect(res[0].contents).toBeInstanceOf(Error1);
expect(res[1].contents).toBeInstanceOf(Promise);
return 'success';
}),
).resolves.toEqual('success');
expect(getState(waitForAll(deps))).toEqual('loading');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest0 = expect(
getPromise(waitForAll(deps)).catch(err => {
expect(err).toBeInstanceOf(Error1);
return 'failure';
}),
).resolves.toEqual('failure');
expect(getState(waitForAllSettled(deps))).toEqual('loading');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Promise);
const allSettledTest0 = expect(
getPromise(waitForAllSettled(deps)).then(res => {
expect(res[0].contents).toBeInstanceOf(Error1);
expect(res[1].contents).toBeInstanceOf(Error2);
return 'success';
}),
).resolves.toEqual('success');
// depA Rejected tests
rejectA(new Error1());
await flushPromisesAndTimers();
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAny(deps))).toEqual('hasValue');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAny(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForAny(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAll(deps))).toEqual('hasError');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Error1);
expect(getState(waitForAllSettled(deps))).toEqual('loading');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Promise);
const allSettledTest1 = expect(
getPromise(waitForAllSettled(deps)).then(res => {
expect(res[0].contents).toBeInstanceOf(Error1);
expect(res[1].contents).toBeInstanceOf(Error2);
return 'success';
}),
).resolves.toEqual('success');
// depB Rejected tests
rejectB(new Error2());
await flushPromisesAndTimers();
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Error2);
expect(getState(waitForAny(deps))).toEqual('hasValue');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAny(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForAny(deps))[1].contents).toBeInstanceOf(Error2);
expect(getState(waitForAll(deps))).toEqual('hasError');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Error1);
expect(getState(waitForAllSettled(deps))).toEqual('hasValue');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAllSettled(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForAllSettled(deps))[1].contents).toBeInstanceOf(Error2);
await anyTest0;
await allTest0;
await allSettledTest0;
await allSettledTest1;
});
// TRUTH TABLE
// Dependencies waitForNone waitForAny waitForAll waitForAllSettled
// [loading, loading] [Promise, Promise] Promise Promise Promise
// [value, loading] [value, Promise] [value, Promise] Promise Promise
// [value, error] [value, Error] [value, Error] Error [value, Error]
testRecoil('waitFor - resolve then reject', async () => {
const [depA, resolveA, _rejectA] = asyncSelector<$FlowFixMe | number, _>();
const [depB, _resolveB, rejectB] = asyncSelector<$FlowFixMe | number, _>();
const deps = [depA, depB];
class Error2 extends Error {}
// All deps Loading Tests
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Promise);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAny(deps))).toEqual('loading');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest0 = expect(
getPromise(waitForAny(deps)).then(res => {
expect(res[0].contents).toEqual(1);
expect(res[1].contents).toBeInstanceOf(Promise);
return 'success';
}),
).resolves.toEqual('success');
expect(getState(waitForAll(deps))).toEqual('loading');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest0 = expect(
getPromise(waitForAll(deps)).catch(err => {
expect(err).toBeInstanceOf(Error2);
return 'failure';
}),
).resolves.toEqual('failure');
expect(getState(waitForAllSettled(deps))).toEqual('loading');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Promise);
const allSettledTest0 = expect(
getPromise(waitForAllSettled(deps)).then(res => {
expect(res[0].contents).toEqual(1);
expect(res[1].contents).toBeInstanceOf(Error2);
return 'success';
}),
).resolves.toEqual('success');
// depA Resolves tests
resolveA(1);
await flushPromisesAndTimers();
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toEqual(1);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAny(deps))).toEqual('hasValue');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAny(deps))[0].contents).toEqual(1);
expect(getValue(waitForAny(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAll(deps))).toEqual('loading');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest1 = expect(getPromise(waitForAll(deps))).rejects.toBeInstanceOf(
Error2,
);
expect(getState(waitForAllSettled(deps))).toEqual('loading');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Promise);
const allSettledTest1 = expect(
getPromise(waitForAllSettled(deps)).then(res => {
expect(res[0].contents).toEqual(1);
expect(res[1].contents).toBeInstanceOf(Error2);
return 'success';
}),
).resolves.toEqual('success');
// depB Rejected tests
rejectB(new Error2());
await flushPromisesAndTimers();
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toEqual(1);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Error2);
expect(getState(waitForAny(deps))).toEqual('hasValue');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAny(deps))[0].contents).toEqual(1);
expect(getValue(waitForAny(deps))[1].contents).toBeInstanceOf(Error2);
expect(getState(waitForAll(deps))).toEqual('hasError');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Error2);
expect(getState(waitForAllSettled(deps))).toEqual('hasValue');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAllSettled(deps))[0].contents).toEqual(1);
expect(getValue(waitForAllSettled(deps))[1].contents).toBeInstanceOf(Error2);
await anyTest0;
await allTest0;
await allTest1;
await allSettledTest0;
await allSettledTest1;
});
// TRUTH TABLE
// Dependencies waitForNone waitForAny waitForAll waitForAllSettled
// [loading, loading] [Promise, Promise] Promise Promise Promise
// [error, loading] [Error, Promise] [Error, Promsie] Error Promise
// [error, value] [Error, value] [Error, value] Error [Error, value]
testRecoil('waitFor - reject then resolve', async () => {
const [depA, _resolveA, rejectA] = asyncSelector<$FlowFixMe | number, _>();
const [depB, resolveB, _rejectB] = asyncSelector<$FlowFixMe | number, _>();
const deps = [depA, depB];
class Error1 extends Error {}
// All deps Loading Tests
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Promise);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAny(deps))).toEqual('loading');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Promise);
const anyTest0 = expect(
getPromise(waitForAny(deps)).then(res => {
expect(res[0].contents).toBeInstanceOf(Error1);
expect(res[1].contents).toBeInstanceOf(Promise);
return 'success';
}),
).resolves.toEqual('success');
expect(getState(waitForAll(deps))).toEqual('loading');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Promise);
const allTest0 = expect(
getPromise(waitForAll(deps)).catch(err => {
expect(err).toBeInstanceOf(Error1);
return 'failure';
}),
).resolves.toEqual('failure');
expect(getState(waitForAllSettled(deps))).toEqual('loading');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Promise);
const allSettledTest0 = expect(
getPromise(waitForAllSettled(deps)).then(res => {
expect(res[0].contents).toBeInstanceOf(Error1);
expect(res[1].contents).toEqual(1);
return 'success';
}),
).resolves.toEqual('success');
// depA Rejects tests
rejectA(new Error1());
await flushPromisesAndTimers();
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForNone(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAny(deps))).toEqual('hasValue');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAny(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForAny(deps))[1].contents).toBeInstanceOf(Promise);
expect(getState(waitForAll(deps))).toEqual('hasError');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Error1);
expect(getState(waitForAllSettled(deps))).toEqual('loading');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Promise);
const allSettledTest1 = expect(
getPromise(waitForAllSettled(deps)).then(res => {
expect(res[0].contents).toBeInstanceOf(Error1);
expect(res[1].contents).toEqual(1);
return 'success';
}),
).resolves.toEqual('success');
// depB Resolves tests
resolveB(1);
await flushPromisesAndTimers();
expect(getState(waitForNone(deps))).toEqual('hasValue');
expect(getLoadable(waitForNone(deps))).toBeInstanceOf(Array);
expect(getValue(waitForNone(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForNone(deps))[1].contents).toEqual(1);
expect(getState(waitForAny(deps))).toEqual('hasValue');
expect(getLoadable(waitForAny(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAny(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForAny(deps))[1].contents).toEqual(1);
expect(getState(waitForAll(deps))).toEqual('hasError');
expect(getLoadable(waitForAll(deps))).toBeInstanceOf(Error1);
expect(getState(waitForAllSettled(deps))).toEqual('hasValue');
expect(getLoadable(waitForAllSettled(deps))).toBeInstanceOf(Array);
expect(getValue(waitForAllSettled(deps))[0].contents).toBeInstanceOf(Error1);
expect(getValue(waitForAllSettled(deps))[1].contents).toEqual(1);
await anyTest0;
await allTest0;
await allSettledTest0;
await allSettledTest1;
});
// Similar as the first test that resolves both dependencies, but with named dependencies.
testRecoil('waitFor - named dependency version', async () => {
const [depA, resolveA] = asyncSelector<$FlowFixMe | number, _>();
const [depB, resolveB] = asyncSelector<$FlowFixMe | number, _>();
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(getLoadable(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(getLoadable(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(getLoadable(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<
$FlowFixMe | number,
_,
>();
const [depB, _resolveB, _rejectB, evaluatedB] = asyncSelector<
$FlowFixMe,
_,
>();
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<$FlowFixMe | number, _>();
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 */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,762 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {Store} from '../../core/Recoil_State';
import type {Parameter} from 'Recoil_atomFamily';
import type {NodeKey, StoreID as StoreIDType} from 'Recoil_Keys';
import type {RecoilState} from 'Recoil_RecoilValue';
import type {Node} from 'react';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let store: Store,
React,
Profiler,
useState,
act,
RecoilRoot,
getRecoilValueAsLoadable,
setRecoilValue,
useRecoilState,
useRecoilValue,
useSetRecoilState,
useSetUnvalidatedAtomValues,
useRecoilStoreID,
ReadsAtom,
componentThatReadsAndWritesAtom,
flushPromisesAndTimers,
renderElements,
reactMode,
stableStringify,
atom,
atomFamily,
selectorFamily,
RecoilLoadable,
pAtom;
const testRecoil = getRecoilTestFn(() => {
const {
makeStore,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
React = require('react');
({Profiler, useState} = require('react'));
({act} = require('ReactTestUtils'));
({RecoilRoot, useRecoilStoreID} = require('../../core/Recoil_RecoilRoot'));
({
getRecoilValueAsLoadable,
setRecoilValue,
} = require('../../core/Recoil_RecoilValueInterface'));
({
useRecoilState,
useRecoilValue,
useSetRecoilState,
useSetUnvalidatedAtomValues,
} = require('../../hooks/Recoil_Hooks'));
({
ReadsAtom,
componentThatReadsAndWritesAtom,
flushPromisesAndTimers,
renderElements,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils'));
({reactMode} = require('../../core/Recoil_ReactMode'));
stableStringify = require('recoil-shared/util/Recoil_stableStringify');
atom = require('../Recoil_atom');
atomFamily = require('../Recoil_atomFamily');
selectorFamily = require('../Recoil_selectorFamily');
({RecoilLoadable} = require('../../adt/Recoil_Loadable'));
store = makeStore();
pAtom = atomFamily<_, {k: string} | {x: string} | {y: string}>({
key: 'pAtom',
default: 'fallback',
});
});
let fbOnlyTest = test.skip;
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-type]
// @fb-only: fbOnlyTest = testRecoil;
let id = 0;
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
function get(recoilValue) {
return getRecoilValueAsLoadable(store, recoilValue).contents;
}
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
function getLoadable(recoilValue) {
return getRecoilValueAsLoadable(store, recoilValue);
}
function set(
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
recoilValue,
value:
| void
| number
| $TEMPORARY$string<'VALUE'>
| $TEMPORARY$string<'bar'>
| $TEMPORARY$string<'eggs'>
| $TEMPORARY$string<'spam'>
| $TEMPORARY$string<'xValue'>
| $TEMPORARY$string<'xValue1'>
| $TEMPORARY$string<'xValue2'>
| $TEMPORARY$string<'xValue3'>
| $TEMPORARY$string<'xValue4'>
| $TEMPORARY$string<'yValue'>
| $TEMPORARY$string<'yValue1'>
| $TEMPORARY$string<'yValue2'>
| $TEMPORARY$string<'yValue3'>
| $TEMPORARY$string<'yValue4'>,
) {
setRecoilValue(store, recoilValue, value);
}
testRecoil('Read fallback by default', () => {
expect(get(pAtom({k: 'x'}))).toBe('fallback');
});
testRecoil('Uses value for parameter', () => {
set(pAtom({k: 'x'}), 'xValue');
set(pAtom({k: 'y'}), 'yValue');
expect(get(pAtom({k: 'x'}))).toBe('xValue');
expect(get(pAtom({k: 'y'}))).toBe('yValue');
expect(get(pAtom({k: 'z'}))).toBe('fallback');
});
testRecoil('Works with non-overlapping sets', () => {
set(pAtom({x: 'x'}), 'xValue');
set(pAtom({y: 'y'}), 'yValue');
expect(get(pAtom({x: 'x'}))).toBe('xValue');
expect(get(pAtom({y: 'y'}))).toBe('yValue');
});
describe('Default', () => {
testRecoil('default is optional', () => {
const myAtom = atom<$FlowFixMe>({key: 'atom without default'});
expect(getLoadable(myAtom).state).toBe('loading');
act(() => set(myAtom, 'VALUE'));
expect(get(myAtom)).toBe('VALUE');
});
testRecoil('Works with atom default', () => {
const fallbackAtom = atom({key: 'fallback', default: 0});
const hasFallback = atomFamily<_, {k: string}>({
key: 'hasFallback',
default: fallbackAtom,
});
expect(get(hasFallback({k: 'x'}))).toBe(0);
set(fallbackAtom, 1);
expect(get(hasFallback({k: 'x'}))).toBe(1);
set(hasFallback({k: 'x'}), 2);
expect(get(hasFallback({k: 'x'}))).toBe(2);
expect(get(hasFallback({k: 'y'}))).toBe(1);
});
testRecoil('Works with parameterized default', () => {
const paramDefaultAtom = atomFamily<_, {num: number}>({
key: 'parameterized default',
// $FlowFixMe[missing-local-annot]
default: ({num}) => num,
});
expect(get(paramDefaultAtom({num: 1}))).toBe(1);
expect(get(paramDefaultAtom({num: 2}))).toBe(2);
set(paramDefaultAtom({num: 1}), 3);
expect(get(paramDefaultAtom({num: 1}))).toBe(3);
expect(get(paramDefaultAtom({num: 2}))).toBe(2);
});
testRecoil('Parameterized async default', async () => {
const paramDefaultAtom = atomFamily<_, {num: number}>({
key: 'parameterized async default',
// $FlowFixMe[missing-local-annot]
default: ({num}) =>
num === 1 ? Promise.reject(num) : Promise.resolve(num),
});
await expect(get(paramDefaultAtom({num: 1}))).rejects.toBe(1);
await expect(get(paramDefaultAtom({num: 2}))).resolves.toBe(2);
set(paramDefaultAtom({num: 1}), 3);
expect(get(paramDefaultAtom({num: 1}))).toBe(3);
expect(get(paramDefaultAtom({num: 2}))).toBe(2);
});
testRecoil('Parameterized loadable default', async () => {
const paramDefaultAtom = atomFamily<_, {num: number}>({
key: 'parameterized loadable default',
// $FlowFixMe[missing-local-annot]
default: ({num}) =>
// $FlowFixMe[underconstrained-implicit-instantiation]
num === 1 ? RecoilLoadable.error(num) : RecoilLoadable.of(num),
});
expect(getLoadable(paramDefaultAtom({num: 1})).state).toBe('hasError');
expect(getLoadable(paramDefaultAtom({num: 1})).contents).toBe(1);
expect(getLoadable(paramDefaultAtom({num: 2})).state).toBe('hasValue');
expect(getLoadable(paramDefaultAtom({num: 2})).contents).toBe(2);
set(paramDefaultAtom({num: 1}), 3);
expect(getLoadable(paramDefaultAtom({num: 1})).state).toBe('hasValue');
expect(getLoadable(paramDefaultAtom({num: 1})).contents).toBe(3);
expect(getLoadable(paramDefaultAtom({num: 2})).state).toBe('hasValue');
expect(getLoadable(paramDefaultAtom({num: 2})).contents).toBe(2);
});
});
testRecoil('Works with date as parameter', () => {
const dateAtomFamily = atomFamily<_, Date>({
key: 'dateFamily',
// $FlowFixMe[missing-local-annot]
default: _date => 0,
});
expect(get(dateAtomFamily(new Date(2021, 2, 25)))).toBe(0);
expect(get(dateAtomFamily(new Date(2021, 2, 26)))).toBe(0);
set(dateAtomFamily(new Date(2021, 2, 25)), 1);
expect(get(dateAtomFamily(new Date(2021, 2, 25)))).toBe(1);
expect(get(dateAtomFamily(new Date(2021, 2, 26)))).toBe(0);
});
testRecoil('Works with parameterized fallback', () => {
const fallbackAtom = atomFamily<_, $FlowFixMe | {num: number}>({
key: 'parameterized fallback default',
// $FlowFixMe[missing-local-annot]
default: ({num}) => num * 10,
});
const paramFallbackAtom = atomFamily<_, {num: number}>({
key: 'parameterized fallback',
default: fallbackAtom,
});
expect(get(paramFallbackAtom({num: 1}))).toBe(10);
expect(get(paramFallbackAtom({num: 2}))).toBe(20);
set(paramFallbackAtom({num: 1}), 3);
expect(get(paramFallbackAtom({num: 1}))).toBe(3);
expect(get(paramFallbackAtom({num: 2}))).toBe(20);
set(fallbackAtom({num: 2}), 200);
expect(get(paramFallbackAtom({num: 2}))).toBe(200);
set(fallbackAtom({num: 1}), 100);
expect(get(paramFallbackAtom({num: 1}))).toBe(3);
expect(get(paramFallbackAtom({num: 2}))).toBe(200);
});
testRecoil('atomFamily async fallback', async () => {
const paramFallback = atomFamily<_, {}>({
key: 'paramaterizedAtom async Fallback',
default: Promise.resolve(42),
});
const container = renderElements(<ReadsAtom atom={paramFallback({})} />);
expect(container.textContent).toEqual('loading');
act(() => jest.runAllTimers());
await flushPromisesAndTimers();
expect(container.textContent).toEqual('42');
});
testRecoil('Parameterized fallback with atom and async', async () => {
const paramFallback = atomFamily<_, {param: string}>({
key: 'parameterized async Fallback',
// $FlowFixMe[missing-local-annot]
default: ({param}) =>
({
value: 'value',
atom: atom({key: `param async fallback atom ${id++}`, default: 'atom'}),
async: Promise.resolve('async'),
}[param]),
});
const valueCont = renderElements(
<ReadsAtom atom={paramFallback({param: 'value'})} />,
);
expect(valueCont.textContent).toEqual('"value"');
const atomCont = renderElements(
<ReadsAtom atom={paramFallback({param: 'atom'})} />,
);
expect(atomCont.textContent).toEqual('"atom"');
const asyncCont = renderElements(
<ReadsAtom atom={paramFallback({param: 'async'})} />,
);
expect(asyncCont.textContent).toEqual('loading');
act(() => jest.runAllTimers());
await flushPromisesAndTimers();
expect(asyncCont.textContent).toEqual('"async"');
});
fbOnlyTest('atomFamily with scope', () => {
const scopeForParamAtom = atom<string>({
key: 'scope atom for atomFamily',
default: 'foo',
});
const paramAtomWithScope = atomFamily<string, {k: string}>({
key: 'parameterized atom with scope',
default: 'default',
scopeRules_APPEND_ONLY_READ_THE_DOCS: [[scopeForParamAtom]],
});
expect(get(paramAtomWithScope({k: 'x'}))).toBe('default');
expect(get(paramAtomWithScope({k: 'y'}))).toBe('default');
set(paramAtomWithScope({k: 'x'}), 'xValue1');
expect(get(paramAtomWithScope({k: 'x'}))).toBe('xValue1');
expect(get(paramAtomWithScope({k: 'y'}))).toBe('default');
set(paramAtomWithScope({k: 'y'}), 'yValue1');
expect(get(paramAtomWithScope({k: 'x'}))).toBe('xValue1');
expect(get(paramAtomWithScope({k: 'y'}))).toBe('yValue1');
set(scopeForParamAtom, 'bar');
expect(get(paramAtomWithScope({k: 'x'}))).toBe('default');
expect(get(paramAtomWithScope({k: 'y'}))).toBe('default');
set(paramAtomWithScope({k: 'x'}), 'xValue2');
expect(get(paramAtomWithScope({k: 'x'}))).toBe('xValue2');
expect(get(paramAtomWithScope({k: 'y'}))).toBe('default');
set(paramAtomWithScope({k: 'y'}), 'yValue2');
expect(get(paramAtomWithScope({k: 'x'}))).toBe('xValue2');
expect(get(paramAtomWithScope({k: 'y'}))).toBe('yValue2');
});
fbOnlyTest('atomFamily with parameterized scope', () => {
const paramScopeForParamAtom = atomFamily<string, {namespace: string}>({
key: 'scope atom for atomFamily with parameterized scope',
default: ({namespace}) => namespace,
});
const paramAtomWithParamScope = atomFamily<string, {k: string, n: string}>({
key: 'parameterized atom with parameterized scope',
default: 'default',
scopeRules_APPEND_ONLY_READ_THE_DOCS: [
[({n}) => paramScopeForParamAtom({namespace: n})],
],
});
expect(get(paramScopeForParamAtom({namespace: 'foo'}))).toBe('foo');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'x'}))).toBe('default');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'foo', k: 'x'}), 'xValue1');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'x'}))).toBe('xValue1');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'foo', k: 'y'}), 'yValue1');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'x'}))).toBe('xValue1');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'y'}))).toBe('yValue1');
set(paramScopeForParamAtom({namespace: 'foo'}), 'eggs');
expect(get(paramScopeForParamAtom({namespace: 'foo'}))).toBe('eggs');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'x'}))).toBe('default');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'foo', k: 'x'}), 'xValue2');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'x'}))).toBe('xValue2');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'foo', k: 'y'}), 'yValue2');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'x'}))).toBe('xValue2');
expect(get(paramAtomWithParamScope({n: 'foo', k: 'y'}))).toBe('yValue2');
expect(get(paramScopeForParamAtom({namespace: 'bar'}))).toBe('bar');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'x'}))).toBe('default');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'bar', k: 'x'}), 'xValue3');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'x'}))).toBe('xValue3');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'bar', k: 'y'}), 'yValue3');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'x'}))).toBe('xValue3');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'y'}))).toBe('yValue3');
set(paramScopeForParamAtom({namespace: 'bar'}), 'spam');
expect(get(paramScopeForParamAtom({namespace: 'bar'}))).toBe('spam');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'x'}))).toBe('default');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'bar', k: 'x'}), 'xValue4');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'x'}))).toBe('xValue4');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'y'}))).toBe('default');
set(paramAtomWithParamScope({n: 'bar', k: 'y'}), 'yValue4');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'x'}))).toBe('xValue4');
expect(get(paramAtomWithParamScope({n: 'bar', k: 'y'}))).toBe('yValue4');
});
testRecoil('Returns the fallback for parameterized atoms', () => {
let theAtom = null;
let setUnvalidatedAtomValues;
let setAtomParam;
let setAtomValue;
function SetsUnvalidatedAtomValues() {
setUnvalidatedAtomValues = useSetUnvalidatedAtomValues();
return null;
}
let setVisible;
function Switch({children}: $TEMPORARY$object<{children: Node}>) {
const [visible, mySetVisible] = useState(false);
setVisible = mySetVisible;
return visible ? children : null;
}
function MyReadsAtom({
getAtom,
}: $TEMPORARY$object<{
getAtom: () => null | (Parameter => RecoilState<number>),
}>) {
const [param, setParam] = useState({num: 1});
setAtomParam = setParam;
// flowlint-next-line unclear-type:off
const myAtom: any = getAtom();
const [value, setValue] = useRecoilState(myAtom(param));
setAtomValue = setValue;
return value;
}
const container = renderElements(
<>
<SetsUnvalidatedAtomValues />
<Switch>
<MyReadsAtom getAtom={() => theAtom} />
</Switch>
</>,
);
act(() => {
setUnvalidatedAtomValues(
new Map<NodeKey, mixed>().set('notDefinedYetAtomFamilyWithFallback', 123),
);
});
const fallback = atom<number>({
key: 'fallback for atomFamily',
default: 222,
});
theAtom = atomFamily<_, Parameter>({
key: 'notDefinedYetAtomFamilyWithFallback',
default: fallback,
persistence_UNSTABLE: {
type: 'url',
validator: (_, returnFallback) => returnFallback,
},
});
act(() => {
setVisible(true);
});
expect(container.textContent).toBe('222');
act(() => {
setAtomValue(111);
});
expect(container.textContent).toBe('111');
act(() => {
setAtomParam({num: 2});
});
expect(container.textContent).toBe('222');
});
testRecoil(
'Returns the fallback for parameterized atoms with a selector as the fallback',
() => {
let theAtom = null;
let setUnvalidatedAtomValues;
let setAtomParam;
let setAtomValue;
function SetsUnvalidatedAtomValues() {
setUnvalidatedAtomValues = useSetUnvalidatedAtomValues();
return null;
}
let setVisible;
function Switch({children}: $TEMPORARY$object<{children: Node}>) {
const [visible, mySetVisible] = useState(false);
setVisible = mySetVisible;
return visible ? children : null;
}
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by
* Flow's LTI update could not be added via codemod */
function MyReadsAtom({getAtom}) {
const [param, setParam] = useState({num: 10});
setAtomParam = setParam;
// flowlint-next-line unclear-type:off
const myAtom: any = getAtom();
const [value, setValue] = useRecoilState(myAtom(param));
setAtomValue = setValue;
return value;
}
const container = renderElements(
<>
<SetsUnvalidatedAtomValues />
<Switch>
<MyReadsAtom getAtom={() => theAtom} />
</Switch>
</>,
);
act(() => {
setUnvalidatedAtomValues(
new Map<NodeKey, mixed>().set(
'notDefinedYetAtomFamilyFallbackSel',
123,
),
);
});
theAtom = atomFamily<_, $FlowFixMe>({
key: 'notDefinedYetAtomFamilyFallbackSel',
default: selectorFamily({
key: 'notDefinedYetAtomFamilyFallbackSelFallback',
get:
(
// $FlowFixMe[missing-local-annot]
{num},
) =>
() =>
num === 1 ? 456 : 789,
}),
persistence_UNSTABLE: {
type: 'url',
validator: (_, notValid) => notValid,
},
});
act(() => {
setVisible(true);
});
expect(container.textContent).toBe('789');
act(() => {
setAtomValue(111);
});
expect(container.textContent).toBe('111');
act(() => {
setAtomParam({num: 1});
});
expect(container.textContent).toBe('456');
},
);
testRecoil('Independent atom subscriptions', ({gks}) => {
const BASE_CALLS =
reactMode().mode === 'LEGACY' &&
!gks.includes('recoil_suppress_rerender_in_callback')
? 1
: 0;
const myAtom = atomFamily<_, string>({
key: 'atomFamily/independent subscriptions',
default: 'DEFAULT',
});
const TrackingComponent = (
param: $TEMPORARY$string<'A'> | $TEMPORARY$string<'B'>,
) => {
let numUpdates = 0;
let setValue;
const Component = () => {
setValue = useSetRecoilState(myAtom(param));
return (
<Profiler
id="test"
onRender={() => {
numUpdates++;
}}>
{stableStringify(useRecoilValue(myAtom(param)))}
</Profiler>
);
};
// $FlowFixMe[incompatible-call]
return [Component, (value: number) => setValue(value), () => numUpdates];
};
const [ComponentA, setValueA, getNumUpdatesA] = TrackingComponent('A');
const [ComponentB, setValueB, getNumUpdatesB] = TrackingComponent('B');
const container = renderElements(
<>
<ComponentA />
<ComponentB />
</>,
);
// Initial:
expect(container.textContent).toBe('"DEFAULT""DEFAULT"');
expect(getNumUpdatesA()).toBe(BASE_CALLS + 1);
expect(getNumUpdatesB()).toBe(BASE_CALLS + 1);
// After setting at parameter A, component A should update:
act(() => setValueA(1));
expect(container.textContent).toBe('1"DEFAULT"');
expect(getNumUpdatesA()).toBe(BASE_CALLS + 2);
expect(getNumUpdatesB()).toBe(BASE_CALLS + 1);
// After setting at parameter B, component B should update:
act(() => setValueB(2));
expect(container.textContent).toBe('12');
expect(getNumUpdatesA()).toBe(BASE_CALLS + 2);
expect(getNumUpdatesB()).toBe(BASE_CALLS + 2);
});
describe('Effects', () => {
testRecoil('Initialization', () => {
let inited = 0;
const myFamily = atomFamily<string, number>({
key: 'atomFamily effect init',
default: 'DEFAULT',
effects: [
({setSelf}) => {
inited++;
setSelf('INIT');
},
],
});
expect(inited).toEqual(0);
expect(get(myFamily(1))).toEqual('INIT');
expect(inited).toEqual(1);
set(myFamily(2));
expect(inited).toEqual(2);
const [ReadsWritesAtom, _, reset] = componentThatReadsAndWritesAtom(
myFamily(1),
);
const c = renderElements(<ReadsWritesAtom />);
expect(c.textContent).toEqual('"INIT"');
act(reset);
expect(c.textContent).toEqual('"DEFAULT"');
});
testRecoil('Parameterized Initialization', () => {
const myFamily = atomFamily({
key: 'atomFamily effect parameterized init',
default: 'DEFAULT',
// $FlowFixMe[missing-local-annot]
effects: param => [({setSelf}) => setSelf(param)],
});
expect(get(myFamily(1))).toEqual(1);
expect(get(myFamily(2))).toEqual(2);
});
testRecoil('Cleanup Handlers - when root unmounted', () => {
const refCounts: {[string]: number} = {A: 0, B: 0};
const atoms = atomFamily({
key: 'atomFamily effect cleanup',
// $FlowFixMe[missing-local-annot]
default: p => p,
// $FlowFixMe[missing-local-annot]
effects: p => [
() => {
refCounts[p]++;
return () => {
refCounts[p]--;
};
},
],
});
let setNumRoots;
function App() {
const [numRoots, _setNumRoots] = useState(0);
setNumRoots = _setNumRoots;
return (
<div>
{Array(numRoots)
.fill(null)
.map((_, idx) => (
<RecoilRoot key={idx}>
<ReadsAtom atom={atoms('A')} />
<ReadsAtom atom={atoms('B')} />
</RecoilRoot>
))}
</div>
);
}
const c = renderElements(<App />);
expect(c.textContent).toBe('');
expect(refCounts).toEqual({A: 0, B: 0});
act(() => setNumRoots(1));
expect(c.textContent).toBe('"A""B"');
expect(refCounts).toEqual({A: 1, B: 1});
act(() => setNumRoots(2));
expect(c.textContent).toBe('"A""B""A""B"');
expect(refCounts).toEqual({A: 2, B: 2});
act(() => setNumRoots(1));
expect(c.textContent).toBe('"A""B"');
expect(refCounts).toEqual({A: 1, B: 1});
act(() => setNumRoots(0));
expect(c.textContent).toBe('');
expect(refCounts).toEqual({A: 0, B: 0});
});
testRecoil('storeID matches <RecoilRoot>', async () => {
const atoms = atomFamily({
key: 'atomFamily effect - storeID',
default: 'DEFAULT',
// $FlowFixMe[missing-local-annot]
effects: rootKey => [
({storeID, setSelf}) => {
expect(storeID).toEqual(storeIDs[rootKey]);
setSelf(rootKey);
},
],
});
const storeIDs: {[string]: StoreIDType} = {};
function StoreID({
rootKey,
}:
| $TEMPORARY$object<{rootKey: $TEMPORARY$string<'A'>}>
| $TEMPORARY$object<{rootKey: $TEMPORARY$string<'A1'>}>
| $TEMPORARY$object<{rootKey: $TEMPORARY$string<'A2'>}>
| $TEMPORARY$object<{rootKey: $TEMPORARY$string<'B'>}>) {
const storeID = useRecoilStoreID();
storeIDs[rootKey] = storeID;
return null;
}
function MyApp() {
return (
<div>
<RecoilRoot>
<StoreID rootKey="A" />
<ReadsAtom atom={atoms('A')} />
<RecoilRoot>
<StoreID rootKey="A1" />
<ReadsAtom atom={atoms('A1')} />
</RecoilRoot>
<RecoilRoot override={false}>
<StoreID rootKey="A2" />
<ReadsAtom atom={atoms('A2')} />
</RecoilRoot>
</RecoilRoot>
<RecoilRoot>
<StoreID rootKey="B" />
<ReadsAtom atom={atoms('B')} />
</RecoilRoot>
</div>
);
}
const c = renderElements(<MyApp />);
expect(c.textContent).toEqual('"A""A1""A2""B"');
expect('A' in storeIDs).toEqual(true);
expect('A1' in storeIDs).toEqual(true);
expect('A2' in storeIDs).toEqual(true);
expect('B' in storeIDs).toEqual(true);
expect(storeIDs.A).not.toEqual(storeIDs.B);
expect(storeIDs.A).not.toEqual(storeIDs.A1);
expect(storeIDs.A).toEqual(storeIDs.A2);
expect(storeIDs.B).not.toEqual(storeIDs.A1);
expect(storeIDs.B).not.toEqual(storeIDs.A2);
});
});

View File

@@ -0,0 +1,260 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {RecoilValue} from '../../core/Recoil_RecoilValue';
import type {Store} from '../../core/Recoil_State';
import type {NodeKey} from 'Recoil_Keys';
import type {RecoilState} from 'Recoil_RecoilValue';
import type {Node} from 'react';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let React,
useState,
act,
getRecoilValueAsLoadable,
setRecoilValue,
subscribeToRecoilValue,
useRecoilState,
useSetUnvalidatedAtomValues,
componentThatReadsAndWritesAtom,
renderElements,
atom,
constSelector,
store: Store;
let fallbackAtom: RecoilValue<number>, hasFallbackAtom: RecoilValue<number>;
let id = 0;
const testRecoil = getRecoilTestFn(() => {
const {
makeStore,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
React = require('react');
({useState} = require('react'));
({act} = require('ReactTestUtils'));
({
getRecoilValueAsLoadable,
setRecoilValue,
subscribeToRecoilValue,
} = require('../../core/Recoil_RecoilValueInterface'));
({
useRecoilState,
useSetUnvalidatedAtomValues,
} = require('../../hooks/Recoil_Hooks'));
({
componentThatReadsAndWritesAtom,
renderElements,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils'));
atom = require('../Recoil_atom');
constSelector = require('../Recoil_constSelector');
store = makeStore();
fallbackAtom = atom<number>({key: `fallback${id}`, default: 1});
hasFallbackAtom = atom<number>({
key: `hasFallback${id++}`,
default: fallbackAtom,
});
subscribeToRecoilValue(store, hasFallbackAtom, () => undefined);
});
function get(
recoilValue: RecoilState<string> | RecoilState<?string> | RecoilValue<number>,
) {
return getRecoilValueAsLoadable(store, recoilValue).contents;
}
function set(
recoilValue: RecoilState<?string> | RecoilValue<number>,
value: ?(number | $TEMPORARY$string<'VALUE'>),
) {
setRecoilValue(store, recoilValue, value);
}
testRecoil('atomWithFallback', () => {
expect(get(hasFallbackAtom)).toBe(1);
set(fallbackAtom, 2);
expect(get(hasFallbackAtom)).toBe(2);
set(hasFallbackAtom, 3);
expect(get(hasFallbackAtom)).toBe(3);
});
describe('ReturnDefaultOrFallback', () => {
testRecoil('Returns the default', () => {
let theAtom = null;
let setUnvalidatedAtomValues;
function SetsUnvalidatedAtomValues() {
setUnvalidatedAtomValues = useSetUnvalidatedAtomValues();
return null;
}
let setVisible;
function Switch({children}: $TEMPORARY$object<{children: Node}>) {
const [visible, mySetVisible] = useState(false);
setVisible = mySetVisible;
return visible ? children : null;
}
function MyReadsAtom({
getAtom,
}: $TEMPORARY$object<{getAtom: () => null | RecoilState<number>}>) {
// flowlint-next-line unclear-type:off
const [value] = useRecoilState((getAtom(): any));
return value;
}
const container = renderElements(
<>
<SetsUnvalidatedAtomValues />
<Switch>
<MyReadsAtom getAtom={() => theAtom} />
</Switch>
</>,
);
act(() => {
setUnvalidatedAtomValues(
new Map<NodeKey, mixed>().set('notDefinedYetAtomValidator', 123),
);
});
theAtom = atom({
key: 'notDefinedYetAtomValidator',
default: 456,
persistence_UNSTABLE: {
type: 'url',
validator: (_, returnFallback) => returnFallback,
},
});
act(() => {
setVisible(true);
});
expect(container.textContent).toBe('456');
});
testRecoil('Returns the fallback', () => {
let theAtom = null;
let setUnvalidatedAtomValues;
function SetsUnvalidatedAtomValues() {
setUnvalidatedAtomValues = useSetUnvalidatedAtomValues();
return null;
}
let setVisible;
function Switch({children}: $TEMPORARY$object<{children: Node}>) {
const [visible, mySetVisible] = useState(false);
setVisible = mySetVisible;
return visible ? children : null;
}
function MyReadsAtom({
getAtom,
}: $TEMPORARY$object<{getAtom: () => null | RecoilState<number>}>) {
// flowlint-next-line unclear-type:off
const [value] = useRecoilState((getAtom(): any));
return value;
}
const container = renderElements(
<>
<SetsUnvalidatedAtomValues />
<Switch>
<MyReadsAtom getAtom={() => theAtom} />
</Switch>
</>,
);
act(() => {
setUnvalidatedAtomValues(
new Map<NodeKey, mixed>().set('notDefinedYetAtomWithFallback', 123),
);
});
const fallback = atom<number>({
key: 'notDefinedYetAtomFallback',
default: 222,
});
theAtom = atom({
key: 'notDefinedYetAtomWithFallback',
default: fallback,
persistence_UNSTABLE: {
type: 'url',
validator: (_, returnFallback) => returnFallback,
},
});
act(() => {
setVisible(true);
});
expect(container.textContent).toBe('222');
});
});
testRecoil('Atom with atom fallback can store null and undefined', () => {
const myFallbackAtom = atom<?string>({
key: 'fallback for null undefined',
default: 'FALLBACK',
});
const myAtom = atom<?string>({
key: 'fallback atom with undefined',
default: myFallbackAtom,
});
expect(get(myAtom)).toBe('FALLBACK');
act(() => set(myAtom, 'VALUE'));
expect(get(myAtom)).toBe('VALUE');
act(() => set(myAtom, null));
expect(get(myAtom)).toBe(null);
act(() => set(myAtom, undefined));
expect(get(myAtom)).toBe(undefined);
act(() => set(myAtom, 'VALUE'));
expect(get(myAtom)).toBe('VALUE');
});
testRecoil('Atom with selector fallback can store null and undefined', () => {
const fallbackSelector = constSelector('FALLBACK');
const myAtom = atom<?string>({
key: 'fallback selector with undefined',
default: fallbackSelector,
});
expect(get(myAtom)).toBe('FALLBACK');
act(() => set(myAtom, 'VALUE'));
expect(get(myAtom)).toBe('VALUE');
act(() => set(myAtom, null));
expect(get(myAtom)).toBe(null);
act(() => set(myAtom, undefined));
expect(get(myAtom)).toBe(undefined);
act(() => set(myAtom, 'VALUE'));
expect(get(myAtom)).toBe('VALUE');
});
testRecoil('Effects', () => {
let inited = false;
const myFallbackAtom = atom({
key: 'atom with fallback effects init fallback',
default: 'FALLBACK',
});
const myAtom = atom<string>({
key: 'atom with fallback effects init',
default: myFallbackAtom,
effects: [
({setSelf}) => {
inited = true;
setSelf('INIT');
},
],
});
expect(get(myAtom)).toEqual('INIT');
expect(inited).toEqual(true);
const [ReadsWritesAtom, _, reset] = componentThatReadsAndWritesAtom(myAtom);
const c = renderElements(<ReadsWritesAtom />);
expect(c.textContent).toEqual('"INIT"');
act(reset);
expect(c.textContent).toEqual('"FALLBACK"');
});

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {RecoilValue} from '../../core/Recoil_RecoilValue';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let getRecoilValueAsLoadable, store, constSelector;
const testRecoil = getRecoilTestFn(() => {
const {
makeStore,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
({
getRecoilValueAsLoadable,
} = require('../../core/Recoil_RecoilValueInterface'));
constSelector = require('../Recoil_constSelector');
store = makeStore();
});
function get<T>(recoilValue: RecoilValue<T>): T {
return getRecoilValueAsLoadable<T>(store, recoilValue).valueOrThrow();
}
testRecoil('constSelector - string', () => {
const mySelector = constSelector('HELLO');
expect(get(mySelector)).toEqual('HELLO');
expect(get(mySelector)).toBe('HELLO');
});
testRecoil('constSelector - number', () => {
const mySelector = constSelector(42);
expect(get(mySelector)).toEqual(42);
expect(get(mySelector)).toBe(42);
});
testRecoil('constSelector - null', () => {
const mySelector = constSelector(null);
expect(get(mySelector)).toEqual(null);
expect(get(mySelector)).toBe(null);
});
testRecoil('constSelector - boolean', () => {
const mySelector = constSelector(true);
expect(get(mySelector)).toEqual(true);
expect(get(mySelector)).toBe(true);
});
testRecoil('constSelector - array', () => {
const emptyArraySelector = constSelector(([]: Array<$FlowFixMe>));
expect(get(emptyArraySelector)).toEqual([]);
const numberArray = [1, 2, 3];
const numberArraySelector = constSelector(numberArray);
expect(get(numberArraySelector)).toEqual([1, 2, 3]);
expect(get(numberArraySelector)).toBe(numberArray);
});
testRecoil('constSelector - object', () => {
const emptyObjSelector = constSelector({});
expect(get(emptyObjSelector)).toEqual({});
const obj = {foo: 'bar'};
const objSelector = constSelector(obj);
expect(get(objSelector)).toEqual({foo: 'bar'});
expect(get(objSelector)).toBe(obj);
// Calling a second time with same object provides the same selector
const objSelector2 = constSelector(obj);
expect(objSelector2).toBe(objSelector);
expect(get(objSelector2)).toEqual({foo: 'bar'});
expect(get(objSelector2)).toBe(obj);
// Calling a third time with similar but different object provides
// a new selector for the new reference.
const newObj = {foo: 'bar'};
const objSelector3 = constSelector(newObj);
expect(get(objSelector3)).toEqual({foo: 'bar'});
expect(get(objSelector3)).toBe(newObj);
});
testRecoil('constSelector - function', () => {
const foo = () => 'FOO';
const bar = () => 'BAR';
const fooSelector = constSelector(foo);
const barSelector = constSelector(bar);
expect(get(fooSelector)()).toEqual('FOO');
expect(get(barSelector)()).toEqual('BAR');
expect(constSelector(foo)).toEqual(fooSelector);
});

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {RecoilValueReadOnly} from 'Recoil_RecoilValue';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let store, getRecoilValueAsLoadable, errorSelector;
const testRecoil = getRecoilTestFn(() => {
const {
makeStore,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
({
getRecoilValueAsLoadable,
} = require('../../core/Recoil_RecoilValueInterface'));
errorSelector = require('../Recoil_errorSelector');
store = makeStore();
});
function getError(recoilValue: RecoilValueReadOnly<mixed>): Error {
const error = getRecoilValueAsLoadable(store, recoilValue).errorOrThrow();
if (!(error instanceof Error)) {
throw new Error('Expected error to be an instance of Error');
}
return error;
}
testRecoil('errorSelector - string', () => {
const mySelector = errorSelector<mixed>('My Error');
expect(getError(mySelector).message).toEqual(
expect.stringContaining('My Error'),
);
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,358 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {RecoilValue} from '../../core/Recoil_RecoilValue';
import type {RecoilState} from 'Recoil_RecoilValue';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let atom,
DefaultValue,
selectorFamily,
getRecoilValueAsLoadable,
setRecoilValue,
store,
myAtom;
const testRecoil = getRecoilTestFn(() => {
const {
makeStore,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
atom = require('../Recoil_atom');
({DefaultValue} = require('../../core/Recoil_Node'));
selectorFamily = require('../Recoil_selectorFamily');
({
getRecoilValueAsLoadable,
setRecoilValue,
} = require('../../core/Recoil_RecoilValueInterface'));
store = makeStore();
myAtom = atom({
key: 'atom',
default: 0,
});
});
function getValue<T>(recoilValue: RecoilValue<T>): T {
return getRecoilValueAsLoadable<T>(store, recoilValue).valueOrThrow();
}
function set(recoilValue: RecoilState<number>, value: number) {
setRecoilValue(store, recoilValue, value);
}
testRecoil('selectorFamily - number parameter', () => {
const mySelector = selectorFamily({
key: 'selectorFamily/number',
get:
(
// $FlowFixMe[missing-local-annot]
multiplier,
) =>
// $FlowFixMe[missing-local-annot]
({get}) =>
get(myAtom) * multiplier,
});
set(myAtom, 1);
expect(getValue(mySelector(10))).toBe(10);
expect(getValue(mySelector(100))).toBe(100);
set(myAtom, 2);
expect(getValue(mySelector(10))).toBe(20);
expect(getValue(mySelector(100))).toBe(200);
});
testRecoil('selectorFamily - array parameter', () => {
const mySelector = selectorFamily({
key: 'selectorFamily/array',
// $FlowFixMe[missing-local-annot]
get: numbers => () => numbers.reduce((x, y) => x + y, 0),
});
expect(getValue(mySelector([]))).toBe(0);
expect(getValue(mySelector([1, 2, 3]))).toBe(6);
expect(getValue(mySelector([0, 1, 1, 2, 3, 5]))).toBe(12);
});
testRecoil('selectorFamily - object parameter', () => {
const mySelector = selectorFamily({
key: 'selectorFamily/object',
get:
(
// $FlowFixMe[missing-local-annot]
{multiplier},
) =>
// $FlowFixMe[missing-local-annot]
({get}) =>
get(myAtom) * multiplier,
});
set(myAtom, 1);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(getValue(mySelector({multiplier: 100}))).toBe(100);
set(myAtom, 2);
expect(getValue(mySelector({multiplier: 10}))).toBe(20);
expect(getValue(mySelector({multiplier: 100}))).toBe(200);
});
testRecoil('selectorFamily - date parameter', () => {
const mySelector = selectorFamily({
key: 'selectorFamily/date',
get:
(
// $FlowFixMe[missing-local-annot]
date,
) =>
// $FlowFixMe[missing-local-annot]
({get}) => {
const daysToAdd = get(myAtom);
const returnDate = new Date(date);
returnDate.setDate(returnDate.getDate() + daysToAdd);
return returnDate;
},
});
set(myAtom, 1);
expect(getValue(mySelector(new Date(2021, 2, 25))).getDate()).toBe(26);
set(myAtom, 2);
expect(getValue(mySelector(new Date(2021, 2, 25))).getDate()).toBe(27);
});
testRecoil('Works with supersets', () => {
const mySelector = selectorFamily({
key: 'selectorFamily/supersets',
get:
(
// $FlowFixMe[missing-local-annot]
{multiplier},
) =>
// $FlowFixMe[missing-local-annot]
({get}) =>
get(myAtom) * multiplier,
});
set(myAtom, 1);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(getValue(mySelector({multiplier: 100}))).toBe(100);
expect(getValue(mySelector({multiplier: 100, extra: 'foo'}))).toBe(100);
});
testRecoil('selectorFamily - writable', () => {
const mySelector = selectorFamily({
key: 'selectorFamily/writable',
get:
(
// $FlowFixMe[missing-local-annot]
{multiplier},
) =>
// $FlowFixMe[missing-local-annot]
({get}) =>
get(myAtom) * multiplier,
set:
(
// $FlowFixMe[missing-local-annot]
{multiplier},
) =>
// $FlowFixMe[missing-local-annot]
({set}, num) =>
set(myAtom, num instanceof DefaultValue ? num : num / multiplier),
});
set(myAtom, 1);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
set(mySelector({multiplier: 10}), 20);
expect(getValue(myAtom)).toBe(2);
set(mySelector({multiplier: 10}), 30);
expect(getValue(myAtom)).toBe(3);
set(mySelector({multiplier: 100}), 400);
expect(getValue(myAtom)).toBe(4);
});
testRecoil('selectorFamily - value caching', () => {
let evals = 0;
const mySelector = selectorFamily({
key: 'selectorFamily/value caching',
get:
(
// $FlowFixMe[missing-local-annot]
{multiplier},
) =>
// $FlowFixMe[missing-local-annot]
({get}) => {
evals++;
return get(myAtom) * multiplier;
},
});
expect(evals).toBe(0);
set(myAtom, 1);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(evals).toBe(1);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(evals).toBe(1);
expect(getValue(mySelector({multiplier: 100}))).toBe(100);
expect(evals).toBe(2);
expect(getValue(mySelector({multiplier: 100}))).toBe(100);
expect(evals).toBe(2);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(evals).toBe(2);
set(myAtom, 2);
expect(getValue(mySelector({multiplier: 10}))).toBe(20);
expect(evals).toBe(3);
expect(getValue(mySelector({multiplier: 10}))).toBe(20);
expect(evals).toBe(3);
expect(getValue(mySelector({multiplier: 100}))).toBe(200);
expect(evals).toBe(4);
expect(getValue(mySelector({multiplier: 100}))).toBe(200);
expect(evals).toBe(4);
});
testRecoil('selectorFamily - reference caching', () => {
let evals = 0;
const mySelector = selectorFamily({
key: 'selectorFamily/reference caching',
get:
(
// $FlowFixMe[missing-local-annot]
{multiplier},
) =>
// $FlowFixMe[missing-local-annot]
({get}) => {
evals++;
return get(myAtom) * multiplier;
},
cachePolicyForParams_UNSTABLE: {
equality: 'reference',
},
});
expect(evals).toBe(0);
set(myAtom, 1);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(evals).toBe(1);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(evals).toBe(2);
expect(getValue(mySelector({multiplier: 100}))).toBe(100);
expect(evals).toBe(3);
expect(getValue(mySelector({multiplier: 100}))).toBe(100);
expect(evals).toBe(4);
expect(getValue(mySelector({multiplier: 10}))).toBe(10);
expect(evals).toBe(5);
set(myAtom, 2);
expect(getValue(mySelector({multiplier: 10}))).toBe(20);
expect(evals).toBe(6);
expect(getValue(mySelector({multiplier: 10}))).toBe(20);
expect(evals).toBe(7);
expect(getValue(mySelector({multiplier: 100}))).toBe(200);
expect(evals).toBe(8);
expect(getValue(mySelector({multiplier: 100}))).toBe(200);
expect(evals).toBe(9);
const multiply10 = {multiplier: 10};
const multiply100 = {multiplier: 100};
set(myAtom, 1);
expect(getValue(mySelector(multiply10))).toBe(10);
expect(evals).toBe(10);
expect(getValue(mySelector(multiply10))).toBe(10);
expect(evals).toBe(10);
expect(getValue(mySelector(multiply100))).toBe(100);
expect(evals).toBe(11);
expect(getValue(mySelector(multiply100))).toBe(100);
expect(evals).toBe(11);
expect(getValue(mySelector(multiply10))).toBe(10);
expect(evals).toBe(11);
set(myAtom, 2);
expect(getValue(mySelector(multiply10))).toBe(20);
expect(evals).toBe(12);
expect(getValue(mySelector(multiply10))).toBe(20);
expect(evals).toBe(12);
expect(getValue(mySelector(multiply100))).toBe(200);
expect(evals).toBe(13);
expect(getValue(mySelector(multiply100))).toBe(200);
expect(evals).toBe(13);
});
// Parameterized selector results should be frozen unless
// dangerouslyAllowMutability is set
testRecoil('selectorFamily - mutability', () => {
const myImmutableSelector = selectorFamily({
key: 'selectorFamily/immutable',
get:
(
// $FlowFixMe[missing-local-annot]
{key},
) =>
// $FlowFixMe[missing-local-annot]
({get}) => ({[key]: get(myAtom)}),
});
set(myAtom, 42);
const immutableResult: {[string]: number, ...} = getValue(
myImmutableSelector({key: 'foo'}),
);
expect(immutableResult).toEqual({foo: 42});
expect(() => {
immutableResult.foo = 2600;
}).toThrow();
const myMutableSelector = selectorFamily({
key: 'selectorFamily/mutable',
get:
(
// $FlowFixMe[missing-local-annot]
{key},
) =>
// $FlowFixMe[missing-local-annot]
({get}) => ({[key]: get(myAtom)}),
dangerouslyAllowMutability: true,
});
set(myAtom, 42);
const mutableResult: {[string]: number, ...} = getValue(
myMutableSelector({key: 'foo'}),
);
expect(mutableResult).toEqual({foo: 42});
mutableResult.foo = 2600;
expect(mutableResult).toEqual({foo: 2600});
});
testRecoil('selectorFamily - evaluate to RecoilValue', () => {
const atomA = atom({key: 'selectorFamily/const atom A', default: 'A'});
const atomB = atom({key: 'selectorFamily/const atom B', default: 'B'});
const mySelector = selectorFamily<string, string>({
key: 'selectorFamily/',
get: param => () => param === 'a' ? atomA : atomB,
});
expect(getValue(mySelector('a'))).toEqual('A');
expect(getValue(mySelector('b'))).toEqual('B');
});
testRecoil('selectorFamily - invalid parameter error message', () => {
const mySelector = selectorFamily<_, {foo: () => void}>({
key: 'function in parameter',
get: () => () => {},
});
expect(() => getValue(mySelector({foo: () => {}}))).toThrow(
'function in parameter',
);
});

File diff suppressed because it is too large Load Diff