Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Add Animated mocks #18

Merged
merged 1 commit into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"compile": "tsc",
"lint": "eslint . --report-unused-disable-directives",
"release": "semantic-release",
"test": "NODE_ENV=test mocha"
"test": "RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true NODE_ENV=test mocha"
},
"packageManager": "[email protected]",
"dependencies": {
Expand Down
20 changes: 13 additions & 7 deletions src/helpers/commons.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import path from "path";

/**
Expand All @@ -9,15 +8,12 @@ export function noop(): void {
}

/**
* Replaces a module with a given `exports` value or another module path.
* Replaces a Module with a given `exports` value or another module path.
*
* @param modulePath the path to the module
* @param other the exports to replace or another module path
* @param exports the exports to replace
*/
export function replace<T>(modulePath: string, other: T | string): void {
const exports = typeof other === "string"
? require(other) as T
: other;
export function replace<T>(modulePath: string, exports: T): void {
const id = resolveId(modulePath);

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

/**
* Replaces am ESModule with a given `exports` value or another module path.
*
* @param modulePath the path to the ESModule
* @param defaultExport the default export to replace
*/
export function replaceEsm<T>(modulePath: string, defaultExport: T): void {
replace(modulePath, { __esModule: true, default: defaultExport });
}

function resolveId(modulePath: string): string {
try {
return require.resolve(modulePath);
Expand Down
10 changes: 10 additions & 0 deletions src/lib/Animated/AnimatedMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Animated } from "react-native";

import { AnimatedValueMock } from "./AnimatedValueMock";
import { AnimatedValueXYMock } from "./AnimatedValueXY";

export const AnimatedMock: typeof Animated = {
...Animated,
Value: AnimatedValueMock,
ValueXY: AnimatedValueXYMock,
};
11 changes: 11 additions & 0 deletions src/lib/Animated/AnimatedValueMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Animated } from "react-native";

export class AnimatedValueMock extends Animated.Value {

public constructor(
value: number,
config: Animated.AnimatedConfig = { useNativeDriver: false },
) {
super(value, { ...config, useNativeDriver: false });
}
}
16 changes: 16 additions & 0 deletions src/lib/Animated/AnimatedValueXY.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Animated } from "react-native";

interface ValueXY {
x: number | Animated.AnimatedValue;
y: number | Animated.AnimatedValue;
}

export class AnimatedValueXYMock extends Animated.ValueXY {

public constructor (
value: ValueXY,
config: Animated.AnimatedConfig = { useNativeDriver: false },
) {
super(value, { ...config, useNativeDriver: false });
}
}
2 changes: 1 addition & 1 deletion src/lib/Core/NativeModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const NativeModulesMock = {
},
PlatformConstants: {
getConstants() {
return {};
return { isTesting: true };
},
},
PushNotificationManager: {
Expand Down
6 changes: 4 additions & 2 deletions src/lib/coreMocks.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { replace } from "../helpers/commons";
import { noop, replace, replaceEsm } from "../helpers/commons";
import { mockNativeComponent } from "../helpers/mockNativeComponent";

import { NativeComponentRegistryMock } from "./Core/NativeComponentRegistry";
import { NativeModulesMock } from "./Core/NativeModules";
import { UIManagerMock } from "./Core/UIManager";
import { verifyComponentAttrEqMock } from "./Core/verifyComponentAttributeEquivalence";

Object.assign(global, { jest: { fn: () => noop } });

replace("react-native/Libraries/Core/InitializeCore", { });
replace("react-native/Libraries/Core/NativeExceptionsManager", { });
replace("react-native/Libraries/ReactNative/UIManager", UIManagerMock);
replace("react-native/Libraries/BatchedBridge/NativeModules", NativeModulesMock);
replace("react-native/Libraries/NativeComponent/NativeComponentRegistry", NativeComponentRegistryMock);
replace("react-native/Libraries/ReactNative/requireNativeComponent", { default: mockNativeComponent });
replaceEsm("react-native/Libraries/ReactNative/requireNativeComponent", mockNativeComponent);
replace("react-native/Libraries/Utilities/verifyComponentAttributeEquivalence", verifyComponentAttrEqMock);
replace(
"react-native/Libraries/ReactNative/RendererProxy",
Expand Down
8 changes: 5 additions & 3 deletions src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import "./lib/babelRegister";
import "./lib/polyfills";
import "./lib/coreMocks";

import { replace } from "./helpers/commons";
import { replace, replaceEsm } from "./helpers/commons";
import { AnimatedMock } from "./lib/Animated/AnimatedMock";
import { AccessibilityInfoMock } from "./lib/Components/AccessibilityInfo";
import { ActivityIndicatorMock } from "./lib/Components/ActivityIndicator";
import { AppStateMock } from "./lib/Components/AppState";
Expand All @@ -22,13 +23,14 @@ replace("react-native/Libraries/Image/Image", ImageMock);
replace("react-native/Libraries/Text/Text", TextMock);
replace("react-native/Libraries/Components/TextInput/TextInput", TextInputMock);
replace("react-native/Libraries/Modal/Modal", ModalMock);
replace("react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo", { default: AccessibilityInfoMock });
replaceEsm("react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo", AccessibilityInfoMock);
replace("react-native/Libraries/Components/Clipboard/Clipboard", ClipboardMock);
replace("react-native/Libraries/Components/RefreshControl/RefreshControl", RefreshControlMock);
replace("react-native/Libraries/Components/ScrollView/ScrollView", ScrollViewMock);
replace("react-native/Libraries/Components/ActivityIndicator/ActivityIndicator", { default: ActivityIndicatorMock });
replaceEsm("react-native/Libraries/Components/ActivityIndicator/ActivityIndicator", ActivityIndicatorMock);
replace("react-native/Libraries/AppState/AppState", AppStateMock);
replace("react-native/Libraries/Linking/Linking", LinkingMock);
replace("react-native/Libraries/Vibration/Vibration", VibrationMock);
replace("react-native/Libraries/Components/View/View", ViewMock);
replace("react-native/Libraries/Components/View/ViewNativeComponent", ViewNativeComponentMock);
replaceEsm("react-native/Libraries/Animated/Animated", AnimatedMock);
80 changes: 61 additions & 19 deletions test/unit/register.test.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,68 @@
import "../../src/register";

import { expect } from "@assertive-ts/core";
import { render } from "@testing-library/react-native";
import { ActivityIndicator, Image, Modal, ScrollView, Text, TextInput, View } from "react-native";
import { render, waitFor, userEvent } from "@testing-library/react-native";
import { ReactElement, useCallback, useRef, useState } from "react";
import { ActivityIndicator, Animated, Button, Image, Modal, ScrollView, Text, TextInput, View } from "react-native";

function TestScreen(): ReactElement {

const [animated, setAnimated] = useState(false);

const enterLeft = useRef(new Animated.Value(100, { useNativeDriver: true })).current;
const movePoint = useRef(new Animated.ValueXY({ x: 0, y: 0 }, { useNativeDriver: true })).current;

const animateView = useCallback(() => {
const enterAnim = Animated.timing(enterLeft, {
duration: 100,
toValue: 0,
useNativeDriver: true,
});
const moveAnim = Animated.spring(movePoint, {
damping: 45,
mass: 4,
stiffness: 350,
toValue: { x: 50, y: 10 },
useNativeDriver: true,
});

Animated.parallel([enterAnim, moveAnim]).start(({ finished }) => {
setAnimated(finished);
});
}, []);

return (
<ScrollView>
<View>
<ActivityIndicator aria-label="Loading" animating={true} />
<Text>{"Hello world!"}</Text>
<TextInput placeholder="Say hello here..." value="Hello :)" />
<Image alt="Profile picture" />
<Modal visible={true}>
<Text>{"I'm on a modal"}</Text>
</Modal>
<Modal visible={false}>
<Text>{"foo"}</Text>
</Modal>
</View>
<Button title="Click Me!" onPress={animateView} />
<Animated.View style={{ marginLeft: enterLeft }}>
<Text>{`Animated view: ${animated}`}</Text>
</Animated.View>
</ScrollView>
);
}

describe("[Unit] register.test.ts", () => {
context("when main is called", () => {
it("mocks react native so it can render on Node.js", () => {
it("mocks react native so it can render on Node.js", async () => {
const {
getByText,
getByPlaceholderText,
getByDisplayValue,
getByLabelText,
} = render(
<ScrollView>
<View>
<ActivityIndicator aria-label="Loading" animating={true} />
<Text>{"Hello world!"}</Text>
<TextInput placeholder="Say hello here..." value="Hello :)" />
<Image alt="Profile picture" />
<Modal visible={true}>
<Text>{"I'm on a modal"}</Text>
</Modal>
<Modal visible={false}>
<Text>{"foo"}</Text>
</Modal>
</View>
</ScrollView>,
);
findByText,
} = render(<TestScreen />);

expect(getByLabelText("Loading")).toBePresent();
expect(getByText("Hello world!")).toBePresent();
Expand All @@ -36,6 +71,13 @@ describe("[Unit] register.test.ts", () => {
expect(getByLabelText("Profile picture")).toBePresent();
expect(getByText("I'm on a modal")).toBePresent();
expect(() => getByText("foo")).toThrowError();
expect(getByText("Animated view: false")).toBePresent();

const clickMeButton = await findByText("Click Me!");

await userEvent.press(clickMeButton);

await waitFor(() => getByText("Animated view: true"));
});
});
});
Loading