Files
react-native/Libraries/Utilities/__tests__/useRefEffect-test.js
T
Tim Yung cb25638a0e RN: Create useRefEffect Utility
Summary:
Creates `useRefEffect` which will be used by components in React Native.

Changelog:
[Internal]

Reviewed By: lunaleaps

Differential Revision: D28862440

fbshipit-source-id: 50e0099c1a3e0a0f506bf82e68984fc5a032f101
2021-06-06 17:08:55 -07:00

243 lines
5.7 KiB
JavaScript

/**
* 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+react_native
* @flow strict-local
* @format
*/
import useRefEffect from '../useRefEffect';
import * as React from 'react';
import {View} from 'react-native';
import {act, create} from 'react-test-renderer';
/**
* TestView provide a component execution environment to test hooks.
*/
function TestView({childKey = null, effect}) {
const ref = useRefEffect(effect);
return <View key={childKey} ref={ref} testID={childKey} />;
}
/**
* TestEffect represents an effect invocation.
*/
class TestEffect {
name: string;
key: ?string;
constructor(name: string, key: ?string) {
this.name = name;
this.key = key;
}
static called(name: string, key: ?string) {
// $FlowIssue[prop-missing] - Flow does not support type augmentation.
return expect.effect(name, key);
}
}
/**
* TestEffectCleanup represents an effect cleanup invocation.
*/
class TestEffectCleanup {
name: string;
key: ?string;
constructor(name: string, key: ?string) {
this.name = name;
this.key = key;
}
static called(name: string, key: ?string) {
// $FlowIssue[prop-missing] - Flow does not support type augmentation.
return expect.effectCleanup(name, key);
}
}
/**
* extend.effect and expect.extendCleanup make it easier to assert expected
* values. But use TestEffect.called and TestEffectCleanup.called instead of
* extend.effect and expect.extendCleanup because of Flow.
*/
expect.extend({
effect(received, name, key) {
const pass =
received instanceof TestEffect &&
received.name === name &&
received.key === key;
return {pass};
},
effectCleanup(received, name, key) {
const pass =
received instanceof TestEffectCleanup &&
received.name === name &&
received.key === key;
return {pass};
},
});
function mockEffectRegistry(): {
mockEffect: string => () => () => void,
mockEffectWithoutCleanup: string => () => void,
registry: $ReadOnlyArray<TestEffect | TestEffectCleanup>,
} {
const registry = [];
return {
mockEffect(name: string): () => () => void {
return instance => {
const key = instance?.props?.testID;
registry.push(new TestEffect(name, key));
return () => {
registry.push(new TestEffectCleanup(name, key));
};
};
},
mockEffectWithoutCleanup(name: string): () => void {
return instance => {
const key = instance?.props?.testID;
registry.push(new TestEffect(name, key));
};
},
registry,
};
}
test('calls effect without cleanup', () => {
let root;
const {mockEffectWithoutCleanup, registry} = mockEffectRegistry();
const effectA = mockEffectWithoutCleanup('A');
act(() => {
root = create(<TestView childKey="foo" effect={effectA} />);
});
expect(registry).toEqual([TestEffect.called('A', 'foo')]);
act(() => {
root.unmount();
});
expect(registry).toEqual([TestEffect.called('A', 'foo')]);
});
test('calls effect and cleanup', () => {
let root;
const {mockEffect, registry} = mockEffectRegistry();
const effectA = mockEffect('A');
act(() => {
root = create(<TestView childKey="foo" effect={effectA} />);
});
expect(registry).toEqual([TestEffect.called('A', 'foo')]);
act(() => {
root.unmount();
});
expect(registry).toEqual([
TestEffect.called('A', 'foo'),
TestEffectCleanup.called('A', 'foo'),
]);
});
test('cleans up old effect before calling new effect', () => {
let root;
const {mockEffect, registry} = mockEffectRegistry();
const effectA = mockEffect('A');
const effectB = mockEffect('B');
act(() => {
root = create(<TestView childKey="foo" effect={effectA} />);
});
act(() => {
root.update(<TestView childKey="foo" effect={effectB} />);
});
expect(registry).toEqual([
TestEffect.called('A', 'foo'),
TestEffectCleanup.called('A', 'foo'),
TestEffect.called('B', 'foo'),
]);
act(() => {
root.unmount();
});
expect(registry).toEqual([
TestEffect.called('A', 'foo'),
TestEffectCleanup.called('A', 'foo'),
TestEffect.called('B', 'foo'),
TestEffectCleanup.called('B', 'foo'),
]);
});
test('calls cleanup and effect on new instance', () => {
let root;
const {mockEffect, registry} = mockEffectRegistry();
const effectA = mockEffect('A');
act(() => {
root = create(<TestView childKey="foo" effect={effectA} />);
});
act(() => {
root.update(<TestView childKey="bar" effect={effectA} />);
});
expect(registry).toEqual([
TestEffect.called('A', 'foo'),
TestEffectCleanup.called('A', 'foo'),
TestEffect.called('A', 'bar'),
]);
act(() => {
root.unmount();
});
expect(registry).toEqual([
TestEffect.called('A', 'foo'),
TestEffectCleanup.called('A', 'foo'),
TestEffect.called('A', 'bar'),
TestEffectCleanup.called('A', 'bar'),
]);
});
test('cleans up old effect before calling new effect with new instance', () => {
let root;
const {mockEffect, registry} = mockEffectRegistry();
const effectA = mockEffect('A');
const effectB = mockEffect('B');
act(() => {
root = create(<TestView childKey="foo" effect={effectA} />);
});
act(() => {
root.update(<TestView childKey="bar" effect={effectB} />);
});
expect(registry).toEqual([
TestEffect.called('A', 'foo'),
TestEffectCleanup.called('A', 'foo'),
TestEffect.called('B', 'bar'),
]);
act(() => {
root.unmount();
});
expect(registry).toEqual([
TestEffect.called('A', 'foo'),
TestEffectCleanup.called('A', 'foo'),
TestEffect.called('B', 'bar'),
TestEffectCleanup.called('B', 'bar'),
]);
});