Skip to content

Commit e330913

Browse files
authored
feat(core): Add Animated mocks (#18)
1 parent 0651156 commit e330913

File tree

9 files changed

+122
-33
lines changed

9 files changed

+122
-33
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"compile": "tsc",
3030
"lint": "eslint . --report-unused-disable-directives",
3131
"release": "semantic-release",
32-
"test": "NODE_ENV=test mocha"
32+
"test": "RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true NODE_ENV=test mocha"
3333
},
3434
"packageManager": "yarn@4.1.1",
3535
"dependencies": {

src/helpers/commons.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable @typescript-eslint/no-var-requires */
21
import path from "path";
32

43
/**
@@ -9,15 +8,12 @@ export function noop(): void {
98
}
109

1110
/**
12-
* Replaces a module with a given `exports` value or another module path.
11+
* Replaces a Module with a given `exports` value or another module path.
1312
*
1413
* @param modulePath the path to the module
15-
* @param other the exports to replace or another module path
14+
* @param exports the exports to replace
1615
*/
17-
export function replace<T>(modulePath: string, other: T | string): void {
18-
const exports = typeof other === "string"
19-
? require(other) as T
20-
: other;
16+
export function replace<T>(modulePath: string, exports: T): void {
2117
const id = resolveId(modulePath);
2218

2319
require.cache[id] = {
@@ -34,6 +30,16 @@ export function replace<T>(modulePath: string, other: T | string): void {
3430
};
3531
}
3632

33+
/**
34+
* Replaces am ESModule with a given `exports` value or another module path.
35+
*
36+
* @param modulePath the path to the ESModule
37+
* @param defaultExport the default export to replace
38+
*/
39+
export function replaceEsm<T>(modulePath: string, defaultExport: T): void {
40+
replace(modulePath, { __esModule: true, default: defaultExport });
41+
}
42+
3743
function resolveId(modulePath: string): string {
3844
try {
3945
return require.resolve(modulePath);

src/lib/Animated/AnimatedMock.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Animated } from "react-native";
2+
3+
import { AnimatedValueMock } from "./AnimatedValueMock";
4+
import { AnimatedValueXYMock } from "./AnimatedValueXY";
5+
6+
export const AnimatedMock: typeof Animated = {
7+
...Animated,
8+
Value: AnimatedValueMock,
9+
ValueXY: AnimatedValueXYMock,
10+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Animated } from "react-native";
2+
3+
export class AnimatedValueMock extends Animated.Value {
4+
5+
public constructor(
6+
value: number,
7+
config: Animated.AnimatedConfig = { useNativeDriver: false },
8+
) {
9+
super(value, { ...config, useNativeDriver: false });
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Animated } from "react-native";
2+
3+
interface ValueXY {
4+
x: number | Animated.AnimatedValue;
5+
y: number | Animated.AnimatedValue;
6+
}
7+
8+
export class AnimatedValueXYMock extends Animated.ValueXY {
9+
10+
public constructor (
11+
value: ValueXY,
12+
config: Animated.AnimatedConfig = { useNativeDriver: false },
13+
) {
14+
super(value, { ...config, useNativeDriver: false });
15+
}
16+
}

src/lib/Core/NativeModules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const NativeModulesMock = {
6060
},
6161
PlatformConstants: {
6262
getConstants() {
63-
return {};
63+
return { isTesting: true };
6464
},
6565
},
6666
PushNotificationManager: {

src/lib/coreMocks.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
import { replace } from "../helpers/commons";
1+
import { noop, replace, replaceEsm } from "../helpers/commons";
22
import { mockNativeComponent } from "../helpers/mockNativeComponent";
33

44
import { NativeComponentRegistryMock } from "./Core/NativeComponentRegistry";
55
import { NativeModulesMock } from "./Core/NativeModules";
66
import { UIManagerMock } from "./Core/UIManager";
77
import { verifyComponentAttrEqMock } from "./Core/verifyComponentAttributeEquivalence";
88

9+
Object.assign(global, { jest: { fn: () => noop } });
10+
911
replace("react-native/Libraries/Core/InitializeCore", { });
1012
replace("react-native/Libraries/Core/NativeExceptionsManager", { });
1113
replace("react-native/Libraries/ReactNative/UIManager", UIManagerMock);
1214
replace("react-native/Libraries/BatchedBridge/NativeModules", NativeModulesMock);
1315
replace("react-native/Libraries/NativeComponent/NativeComponentRegistry", NativeComponentRegistryMock);
14-
replace("react-native/Libraries/ReactNative/requireNativeComponent", { default: mockNativeComponent });
16+
replaceEsm("react-native/Libraries/ReactNative/requireNativeComponent", mockNativeComponent);
1517
replace("react-native/Libraries/Utilities/verifyComponentAttributeEquivalence", verifyComponentAttrEqMock);
1618
replace(
1719
"react-native/Libraries/ReactNative/RendererProxy",

src/register.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import "./lib/babelRegister";
22
import "./lib/polyfills";
33
import "./lib/coreMocks";
44

5-
import { replace } from "./helpers/commons";
5+
import { replace, replaceEsm } from "./helpers/commons";
6+
import { AnimatedMock } from "./lib/Animated/AnimatedMock";
67
import { AccessibilityInfoMock } from "./lib/Components/AccessibilityInfo";
78
import { ActivityIndicatorMock } from "./lib/Components/ActivityIndicator";
89
import { AppStateMock } from "./lib/Components/AppState";
@@ -22,13 +23,14 @@ replace("react-native/Libraries/Image/Image", ImageMock);
2223
replace("react-native/Libraries/Text/Text", TextMock);
2324
replace("react-native/Libraries/Components/TextInput/TextInput", TextInputMock);
2425
replace("react-native/Libraries/Modal/Modal", ModalMock);
25-
replace("react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo", { default: AccessibilityInfoMock });
26+
replaceEsm("react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo", AccessibilityInfoMock);
2627
replace("react-native/Libraries/Components/Clipboard/Clipboard", ClipboardMock);
2728
replace("react-native/Libraries/Components/RefreshControl/RefreshControl", RefreshControlMock);
2829
replace("react-native/Libraries/Components/ScrollView/ScrollView", ScrollViewMock);
29-
replace("react-native/Libraries/Components/ActivityIndicator/ActivityIndicator", { default: ActivityIndicatorMock });
30+
replaceEsm("react-native/Libraries/Components/ActivityIndicator/ActivityIndicator", ActivityIndicatorMock);
3031
replace("react-native/Libraries/AppState/AppState", AppStateMock);
3132
replace("react-native/Libraries/Linking/Linking", LinkingMock);
3233
replace("react-native/Libraries/Vibration/Vibration", VibrationMock);
3334
replace("react-native/Libraries/Components/View/View", ViewMock);
3435
replace("react-native/Libraries/Components/View/ViewNativeComponent", ViewNativeComponentMock);
36+
replaceEsm("react-native/Libraries/Animated/Animated", AnimatedMock);

test/unit/register.test.tsx

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,68 @@
11
import "../../src/register";
22

33
import { expect } from "@assertive-ts/core";
4-
import { render } from "@testing-library/react-native";
5-
import { ActivityIndicator, Image, Modal, ScrollView, Text, TextInput, View } from "react-native";
4+
import { render, waitFor, userEvent } from "@testing-library/react-native";
5+
import { ReactElement, useCallback, useRef, useState } from "react";
6+
import { ActivityIndicator, Animated, Button, Image, Modal, ScrollView, Text, TextInput, View } from "react-native";
7+
8+
function TestScreen(): ReactElement {
9+
10+
const [animated, setAnimated] = useState(false);
11+
12+
const enterLeft = useRef(new Animated.Value(100, { useNativeDriver: true })).current;
13+
const movePoint = useRef(new Animated.ValueXY({ x: 0, y: 0 }, { useNativeDriver: true })).current;
14+
15+
const animateView = useCallback(() => {
16+
const enterAnim = Animated.timing(enterLeft, {
17+
duration: 100,
18+
toValue: 0,
19+
useNativeDriver: true,
20+
});
21+
const moveAnim = Animated.spring(movePoint, {
22+
damping: 45,
23+
mass: 4,
24+
stiffness: 350,
25+
toValue: { x: 50, y: 10 },
26+
useNativeDriver: true,
27+
});
28+
29+
Animated.parallel([enterAnim, moveAnim]).start(({ finished }) => {
30+
setAnimated(finished);
31+
});
32+
}, []);
33+
34+
return (
35+
<ScrollView>
36+
<View>
37+
<ActivityIndicator aria-label="Loading" animating={true} />
38+
<Text>{"Hello world!"}</Text>
39+
<TextInput placeholder="Say hello here..." value="Hello :)" />
40+
<Image alt="Profile picture" />
41+
<Modal visible={true}>
42+
<Text>{"I'm on a modal"}</Text>
43+
</Modal>
44+
<Modal visible={false}>
45+
<Text>{"foo"}</Text>
46+
</Modal>
47+
</View>
48+
<Button title="Click Me!" onPress={animateView} />
49+
<Animated.View style={{ marginLeft: enterLeft }}>
50+
<Text>{`Animated view: ${animated}`}</Text>
51+
</Animated.View>
52+
</ScrollView>
53+
);
54+
}
655

756
describe("[Unit] register.test.ts", () => {
857
context("when main is called", () => {
9-
it("mocks react native so it can render on Node.js", () => {
58+
it("mocks react native so it can render on Node.js", async () => {
1059
const {
1160
getByText,
1261
getByPlaceholderText,
1362
getByDisplayValue,
1463
getByLabelText,
15-
} = render(
16-
<ScrollView>
17-
<View>
18-
<ActivityIndicator aria-label="Loading" animating={true} />
19-
<Text>{"Hello world!"}</Text>
20-
<TextInput placeholder="Say hello here..." value="Hello :)" />
21-
<Image alt="Profile picture" />
22-
<Modal visible={true}>
23-
<Text>{"I'm on a modal"}</Text>
24-
</Modal>
25-
<Modal visible={false}>
26-
<Text>{"foo"}</Text>
27-
</Modal>
28-
</View>
29-
</ScrollView>,
30-
);
64+
findByText,
65+
} = render(<TestScreen />);
3166

3267
expect(getByLabelText("Loading")).toBePresent();
3368
expect(getByText("Hello world!")).toBePresent();
@@ -36,6 +71,13 @@ describe("[Unit] register.test.ts", () => {
3671
expect(getByLabelText("Profile picture")).toBePresent();
3772
expect(getByText("I'm on a modal")).toBePresent();
3873
expect(() => getByText("foo")).toThrowError();
74+
expect(getByText("Animated view: false")).toBePresent();
75+
76+
const clickMeButton = await findByText("Click Me!");
77+
78+
await userEvent.press(clickMeButton);
79+
80+
await waitFor(() => getByText("Animated view: true"));
3981
});
4082
});
4183
});

0 commit comments

Comments
 (0)