From 7094ff24340b926b25dadb7089f7990bacc1f544 Mon Sep 17 00:00:00 2001 From: JoseLion Date: Sat, 9 Mar 2024 21:28:45 -0500 Subject: [PATCH] feat(core): Add Animated mocks --- package.json | 2 +- src/helpers/commons.ts | 20 ++++--- src/lib/Animated/AnimatedMock.ts | 10 ++++ src/lib/Animated/AnimatedValueMock.ts | 11 ++++ src/lib/Animated/AnimatedValueXY.ts | 16 ++++++ src/lib/Core/NativeModules.ts | 2 +- src/lib/coreMocks.ts | 6 +- src/register.ts | 8 ++- test/unit/register.test.tsx | 80 ++++++++++++++++++++------- 9 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 src/lib/Animated/AnimatedMock.ts create mode 100644 src/lib/Animated/AnimatedValueMock.ts create mode 100644 src/lib/Animated/AnimatedValueXY.ts diff --git a/package.json b/package.json index 5a01b91..6d15ce7 100644 --- a/package.json +++ b/package.json @@ -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": "yarn@4.1.1", "dependencies": { diff --git a/src/helpers/commons.ts b/src/helpers/commons.ts index c75180c..1fa2adf 100644 --- a/src/helpers/commons.ts +++ b/src/helpers/commons.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ import path from "path"; /** @@ -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(modulePath: string, other: T | string): void { - const exports = typeof other === "string" - ? require(other) as T - : other; +export function replace(modulePath: string, exports: T): void { const id = resolveId(modulePath); require.cache[id] = { @@ -34,6 +30,16 @@ export function replace(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(modulePath: string, defaultExport: T): void { + replace(modulePath, { __esModule: true, default: defaultExport }); +} + function resolveId(modulePath: string): string { try { return require.resolve(modulePath); diff --git a/src/lib/Animated/AnimatedMock.ts b/src/lib/Animated/AnimatedMock.ts new file mode 100644 index 0000000..31e20d2 --- /dev/null +++ b/src/lib/Animated/AnimatedMock.ts @@ -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, +}; diff --git a/src/lib/Animated/AnimatedValueMock.ts b/src/lib/Animated/AnimatedValueMock.ts new file mode 100644 index 0000000..89e0a5e --- /dev/null +++ b/src/lib/Animated/AnimatedValueMock.ts @@ -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 }); + } +} diff --git a/src/lib/Animated/AnimatedValueXY.ts b/src/lib/Animated/AnimatedValueXY.ts new file mode 100644 index 0000000..248a239 --- /dev/null +++ b/src/lib/Animated/AnimatedValueXY.ts @@ -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 }); + } +} diff --git a/src/lib/Core/NativeModules.ts b/src/lib/Core/NativeModules.ts index a3a19d0..4855387 100644 --- a/src/lib/Core/NativeModules.ts +++ b/src/lib/Core/NativeModules.ts @@ -60,7 +60,7 @@ export const NativeModulesMock = { }, PlatformConstants: { getConstants() { - return {}; + return { isTesting: true }; }, }, PushNotificationManager: { diff --git a/src/lib/coreMocks.ts b/src/lib/coreMocks.ts index e26c206..abb906f 100644 --- a/src/lib/coreMocks.ts +++ b/src/lib/coreMocks.ts @@ -1,4 +1,4 @@ -import { replace } from "../helpers/commons"; +import { noop, replace, replaceEsm } from "../helpers/commons"; import { mockNativeComponent } from "../helpers/mockNativeComponent"; import { NativeComponentRegistryMock } from "./Core/NativeComponentRegistry"; @@ -6,12 +6,14 @@ 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", diff --git a/src/register.ts b/src/register.ts index 334404d..a302dba 100644 --- a/src/register.ts +++ b/src/register.ts @@ -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"; @@ -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); diff --git a/test/unit/register.test.tsx b/test/unit/register.test.tsx index 9c437f6..a574bd0 100644 --- a/test/unit/register.test.tsx +++ b/test/unit/register.test.tsx @@ -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 ( + + + + {"Hello world!"} + + Profile picture + + {"I'm on a modal"} + + + {"foo"} + + +