/** * 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 { act } = require('ReactTestUtils'); const { getRecoilTestFn } = require('recoil-shared/__test_utils__/Recoil_TestingUtils'); let React, useState, useEffect, atom, useRecoilValue, useRecoilState, useRecoilTransaction, useRecoilSnapshot, renderElements, flushPromisesAndTimers, ReadsAtom; const testRecoil = getRecoilTestFn(() => { React = require('react'); ({ useState, useEffect } = React); ({ atom, useRecoilValue, useRecoilState, useRecoilTransaction_UNSTABLE: useRecoilTransaction, useRecoilSnapshot } = require('../../Recoil_index')); ({ renderElements, flushPromisesAndTimers, ReadsAtom } = require('recoil-shared/__test_utils__/Recoil_TestingUtils')); }); describe('Atoms', () => { testRecoil('Get with transaction', () => { const myAtom = atom({ key: 'useRecoilTransaction atom get', default: 'DEFAULT' }); let readAtom; let ranTransaction = false; declare function Component(): any; renderElements(); expect(ranTransaction).toBe(false); act(readAtom); expect(ranTransaction).toBe(true); }); testRecoil('Set with transaction', () => { const myAtom = atom({ key: 'useRecoilTransaction atom set', default: 'DEFAULT' }); declare function Component(): any; const c = renderElements(<> ); expect(c.textContent).toEqual('"TRANSACT"'); }); testRecoil('Dirty atoms', async () => { const beforeAtom = atom({ key: 'useRecoilTransaction dirty before', default: 'DEFAULT' }); const duringAtomA = atom({ key: 'useRecoilTransaction dirty during A', default: 'DEFAULT' }); const duringAtomB = atom({ key: 'useRecoilTransaction dirty during B', default: 'DEFAULT' }); const afterAtom = atom({ key: 'useRecoilTransaction dirty after', default: 'DEFAULT' }); let snapshot; let firstEffect = true; declare function Component(): any; const c = renderElements(); expect(c.textContent).toBe('INITIAL,DEFAULT,DEFAULT,DEFAULT,DEFAULT,INITIAL'); expect(Array.from(snapshot?.getNodes_UNSTABLE({ isModified: true }) ?? [])).toEqual([]); await flushPromisesAndTimers(); expect(c.textContent).toBe('BEFORE,BEFORE,DURING_A,DURING_B,AFTER,AFTER'); expect(Array.from(snapshot?.getNodes_UNSTABLE({ isModified: true }) ?? []).map(({ key }) => key)).toEqual(['useRecoilTransaction dirty before', 'useRecoilTransaction dirty during A', 'useRecoilTransaction dirty during B', 'useRecoilTransaction dirty after']); }); }); describe('Atom Effects', () => { testRecoil('Atom effects are run when first get from a transaction', async () => { let numTimesEffectInit = 0; const atomWithEffect = atom({ key: 'atom effect first get transaction', default: 'DEFAULT', effects: [({ trigger }) => { expect(trigger).toEqual('get'); numTimesEffectInit++; }] }); let getAtomWithTransaction; let ranTransaction = false; declare var Component: () => any; renderElements(); act(() => getAtomWithTransaction()); expect(ranTransaction).toBe(true); expect(numTimesEffectInit).toBe(1); }); testRecoil('Atom effects are run when first set with a transaction', async ({ strictMode, concurrentMode }) => { let numTimesEffectInit = 0; const atomWithEffect = atom({ key: 'atom effect first set transaction', default: 'DEFAULT', effects: [({ trigger }) => { expect(trigger).toEqual('set'); numTimesEffectInit++; }] }); let setAtomWithTransaction; declare var Component: () => any; renderElements(); expect(numTimesEffectInit).toBe(strictMode && concurrentMode ? 2 : 1); }); testRecoil('Atom effects can initialize for a transaction', async () => { let numTimesEffectInit = 0; const atomWithEffect = atom({ key: 'atom effect init transaction', default: 'DEFAULT', effects: [({ setSelf }) => { setSelf('INIT'); numTimesEffectInit++; }] }); let initAtomWithTransaction; let ranTransaction = false; declare var Component: () => any; renderElements(); act(() => initAtomWithTransaction()); expect(ranTransaction).toBe(true); expect(numTimesEffectInit).toBe(1); }); testRecoil('Atom effects are initialized once if first seen on transaction and then on root store', ({ strictMode, concurrentMode }) => { const sm = strictMode && concurrentMode ? 2 : 1; let numTimesEffectInit = 0; const atomWithEffect = atom({ key: 'useRecoilTransaction effect first get transaction', default: 0, effects: [() => { numTimesEffectInit++; }] }); declare var Component: () => any; const c = renderElements(); expect(c.textContent).toBe('RENDERED'); expect(numTimesEffectInit).toBe(1 * sm); }); testRecoil('Atom effects are initialized once if first seen on root store and then on snapshot', ({ strictMode, concurrentMode }) => { const sm = strictMode && concurrentMode ? 2 : 1; let numTimesEffectInit = 0; const atomWithEffect = atom({ key: 'atom effect first get root', default: 0, effects: [() => { numTimesEffectInit++; }] }); declare var Component: () => any; const c = renderElements(); expect(c.textContent).toBe('RENDERED'); expect(numTimesEffectInit).toBe(1 * sm); }); });