Skip to content
Closed
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
4 changes: 2 additions & 2 deletions RNReanimated.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ Pod::Spec.new do |s|
"Common/cpp/hidden_headers/**"
]

gcc_debug_definitions = "$(inherited)"
if $config[:react_native_minor_version] >= 73 || !is_release
gcc_debug_definitions = "$(inherited)"
if config[:react_native_minor_version] >= 73 || !is_release
gcc_debug_definitions << " HERMES_ENABLE_DEBUGGER=1"
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import { makeMutable, runOnJS, runOnUI } from "react-native-reanimated";
import { TestRunner } from "./TestRunner";
import { TestComponent } from "./TestComponent";

const testRunner = new TestRunner();

export function describe(name: string, buildSuite: () => void) {
testRunner.describe(name, buildSuite);
};

export function beforeAll(job: () => void) {
testRunner.beforeAll(job);
}

export function beforeEach(job: () => void) {
testRunner.beforeEach(job);
}

export function afterEach(job: () => void) {
testRunner.afterEach(job);
}

export function afterAll(job: () => void) {
testRunner.afterAll(job);
}

export function test(name: string, testCase: () => void) {
testRunner.test(name, testCase);
};

export async function render(component: any) {
return testRunner.render(component);
}

function waitForPropertyValueChange(targetObject, targetProperty, initialValue = true) {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (targetObject[targetProperty] != initialValue) {
clearInterval(interval);
resolve(targetObject[targetProperty]);
}
}, 10);
});
}

export function useTestRef(name: string): React.MutableRefObject<any> {
return testRunner.useTestRef(name);
}

export function getTestComponent(name: string): TestComponent {
return testRunner.getTestComponent(name);
}

export async function runTests() {
await testRunner.runTests();
}

export async function wait(delay: number) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}

export function expect(value: any) {
return testRunner.expect(value);
};

export function configure(config: any) {
return testRunner.configure(config);
}

const lockObject = {
lock: false
}
function unlock() {
lockObject.lock = false;
}
async function runOnUiSync(worklet: () => void) {
lockObject.lock = true;
runOnUI(() => {
"worklet";
worklet();
runOnJS(unlock)();
})();
await waitForPropertyValueChange(lockObject, "lock", true);
}

export async function mockAnimationTimer() {
await runOnUiSync(() => {
"worklet";
global.mockedAnimationTimestamp = 0;
global.originalGetAnimationTimestamp = global._getAnimationTimestamp;
global._getAnimationTimestamp = () => {
const currentTimestamp = global.mockedAnimationTimestamp;
global.__frameTimestamp = currentTimestamp;
global.mockedAnimationTimestamp += 16;
return currentTimestamp;
};
let originalRequestAnimationFrame = global.requestAnimationFrame;
global.originalRequestAnimationFrame = originalRequestAnimationFrame;
(global as any).requestAnimationFrame = (callback) => {
originalRequestAnimationFrame(() => {
callback(global._getAnimationTimestamp());
});
}
})
}

export async function unmockAnimationTimer() {
await runOnUiSync(() => {
"worklet";
if (global.originalGetAnimationTimestamp) {
global._getAnimationTimestamp = global.originalGetAnimationTimestamp;
global.originalGetAnimationTimestamp = undefined;
}
if (global.originalRequestAnimationFrame) {
global.requestAnimationFrame = global.originalRequestAnimationFrame;
global.originalRequestAnimationFrame = undefined;
}
if (global.mockedAnimationTimestamp) {
global.mockedAnimationTimestamp = undefined;
}
});
}

export async function setAnimationTimestamp(timestamp: number) {
await runOnUiSync(() => {
"worklet";
global.mockedAnimationTimestamp = timestamp;
})
}

export async function advanceAnimationByTime(time: number) {
await runOnUiSync(() => {
"worklet";
global.mockedAnimationTimestamp += time;
})
}

export async function advanceAnimationByFrames(frameCount: number) {
await runOnUiSync(() => {
"worklet";
global.mockedAnimationTimestamp += frameCount * 16;
})
}

export async function recordAnimationUpdates(mergeOperations = true) {
const updates = makeMutable<any>(null);
await runOnUiSync(() => {
"worklet";
const originalUpdateProps = global._IS_FABRIC ? global._updatePropsFabric : global._updatePropsPaper;
global.originalUpdateProps = originalUpdateProps;
const mockedUpdateProps = mergeOperations
? (operations) => {
if (updates.value == null) {
updates.value = [];
}
const newUpdates: any = [];
for (const operation of operations) {
newUpdates.push(operation.updates);
}
for (const newUpdate of newUpdates) {
updates.value.push(newUpdate);
}
updates.value = [...updates.value];
originalUpdateProps(operations);
}
: (operations) => {
if (updates.value == null) {
updates.value = {};
}
for (const operation of operations) {
if (updates.value[operation.tag] == undefined) {
updates.value[operation.tag] = [];
}
updates.value[operation.tag].push(updates.value[operation.tag]);
}
updates.value = operations;
originalUpdateProps(operations);
};

if (global._IS_FABRIC) {
global._updatePropsFabric = mockedUpdateProps;
} else {
global._updatePropsPaper = mockedUpdateProps;
}

const originalNotifyAboutProgress = global._notifyAboutProgress;
global.originalNotifyAboutProgress = originalNotifyAboutProgress;
global._notifyAboutProgress = mergeOperations
? (tag, value, isSharedTransition) => {
if (updates.value == null) {
updates.value = [];
}
updates.value.push({... value});
updates.value = [...updates.value];
originalNotifyAboutProgress(tag, value, isSharedTransition);
}
: (tag, value, isSharedTransition) => {
if (updates.value == null) {
updates.value = {};
}
if (updates.value[tag] == undefined) {
updates.value[tag] = [];
}
updates.value[tag].push(value);
updates.value = { ...updates.value };
originalNotifyAboutProgress(tag, value, isSharedTransition);
}
});
return updates;
}

export async function stopRecordingAnimationUpdates() {
await runOnUiSync(() => {
"worklet";
if (global.originalUpdateProps) {
if (global._IS_FABRIC) {
global._updatePropsFabric = global.originalUpdateProps;
} else {
global._updatePropsPaper = global.originalUpdateProps;
}
global.originalUpdateProps = undefined;
}
if (global.originalNotifyAboutProgress) {
global._notifyAboutProgress = global.originalNotifyAboutProgress;
global.originalNotifyAboutProgress = undefined;
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { View, Button, StyleSheet, Text } from 'react-native';
import React, { useEffect, useState } from 'react';
import { runTests, configure } from './RuntimeTestsApi';

let renderLock: { lock: boolean } = { lock: false };
export default function RuntimeTestsRunner() {
const [component, renderComponent] = useState(false);
useEffect(() => {
if (renderLock) {
renderLock.lock = false;
}
}, [component]);
return (
<View style={styles.container}>
<Button title="Run tests" onPress={async () => {
renderLock = configure({ render: renderComponent });
await runTests();
}} />
{ component }
{ !component && <Text> Press "Run tests" button to start tests </Text> }
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { findNodeHandle } from "react-native";
import { getViewProp } from "react-native-reanimated";

export class TestComponent {
private ref: React.MutableRefObject<any>;
constructor(ref: React.MutableRefObject<any>) {
this.ref = ref;
}
public getStyle(propName) {
return this.ref.current.props.style[propName];
}
public async getAnimatedStyle(propName) {
const tag = findNodeHandle(this.ref.current) ?? -1;
return getViewProp(tag, propName);
}
public getTag() {
return findNodeHandle(this.ref.current) ?? -1;
}
};
Loading