Skip to content

Commit

Permalink
Exract async code to separate classes (#6258)
Browse files Browse the repository at this point in the history
## Summary
Previously to use function `runOnUIBlocking` we need a reference to
TestRunner.
I wanted to use `runOnUIBlocking` while implementing some other feature 
(`expect(()=>{}).toThrow()`) and found it a bit troublesome. Therefore
I've decided to extract this code into separate class first.
## Test plan
  • Loading branch information
Latropos authored Jul 15, 2024
1 parent 7194196 commit d7de282
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { View, Button, StyleSheet, Text } from 'react-native';
import type { ReactNode } from 'react';
import React, { useEffect, useState } from 'react';
import { runTests, configure } from './RuntimeTestsApi';
import type { LockObject } from './types';
import { RenderLock } from './SyncUIRunner';

let renderLock: LockObject = { lock: false };
let renderLock: RenderLock = new RenderLock();
export class ErrorBoundary extends React.Component<
{ children: React.JSX.Element | Array<React.JSX.Element> },
{ hasError: boolean }
Expand Down Expand Up @@ -32,7 +32,7 @@ export default function RuntimeTestsRunner() {
const [component, setComponent] = useState<ReactNode | null>(null);
useEffect(() => {
if (renderLock) {
renderLock.lock = false;
renderLock.unlock();
}
}, [component]);
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { runOnJS, runOnUI } from 'react-native-reanimated';
import type { LockObject } from './types';

class WaitForUnlock {
private _lock: LockObject = {
lock: false,
};

_setLock(value: boolean) {
this._lock = { lock: value };
}

_waitForUnlock(maxWaitTime?: number) {
return new Promise(resolve => {
const startTime = performance.now();
const interval = setInterval(() => {
const currentTime = performance.now();
const waitTimeExceeded = maxWaitTime && maxWaitTime < currentTime - startTime;
if (this._lock.lock !== true || waitTimeExceeded) {
clearInterval(interval);
resolve(this._lock.lock);
}
}, 10);
});
}
}

export class SyncUIRunner extends WaitForUnlock {
public async runOnUIBlocking(worklet: () => void, maxWaitTime?: number) {
const unlock = () => this._setLock(false);
this._setLock(true);
runOnUI(() => {
'worklet';
worklet();
runOnJS(unlock)();
})();
await this._waitForUnlock(maxWaitTime);
}
}

export class RenderLock extends WaitForUnlock {
public lock() {
this._setLock(true);
}

public unlock() {
this._setLock(false);
}

public async waitForUnlock(maxWaitTime?: number) {
await this._waitForUnlock(maxWaitTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useRef } from 'react';
import type {
BuildFunction,
NullableTestValue,
LockObject,
Operation,
SharedValueSnapshot,
TestCase,
Expand All @@ -17,10 +16,11 @@ import { DescribeDecorator, TestDecorator } from './types';
import { TestComponent } from './TestComponent';
import { EMPTY_LOG_PLACEHOLDER, applyMarkdown, color, formatString, indentNestingLevel } from './stringFormatUtils';
import type { SharedValue } from 'react-native-reanimated';
import { makeMutable, runOnUI, runOnJS } from 'react-native-reanimated';
import { makeMutable, runOnJS } from 'react-native-reanimated';
import { Matchers, nullableMatch } from './matchers/Matchers';
import { assertMockedAnimationTimestamp, assertTestCase, assertTestSuite } from './Asserts';
import { createUpdatesContainer } from './UpdatesContainer';
import { RenderLock, SyncUIRunner } from './SyncUIRunner';

let callTrackerRegistryJS: Record<string, number> = {};
const callTrackerRegistryUI = makeMutable<Record<string, number>>({});
Expand All @@ -41,10 +41,11 @@ export class TestRunner {
private _currentTestSuite: TestSuite | null = null;
private _currentTestCase: TestCase | null = null;
private _renderHook: (component: ReactElement<Component> | null) => void = () => {};
private _renderLock: LockObject = { lock: false };
private _valueRegistry: Record<string, SharedValue> = {};
private _wasRenderedNull: boolean = false;
private _includesOnly: boolean = false;
private _syncUIRunner: SyncUIRunner = new SyncUIRunner();
private _renderLock: RenderLock = new RenderLock();
private _summary: TestSummary = {
passed: 0,
failed: 0,
Expand All @@ -54,10 +55,6 @@ export class TestRunner {
endTime: 0,
};

private _threadLock: LockObject = {
lock: false,
};

public notify(name: string) {
'worklet';
if (_WORKLET) {
Expand Down Expand Up @@ -88,13 +85,14 @@ export class TestRunner {
return;
}
this._wasRenderedNull = !component;
this._renderLock.lock = true;
this._renderLock.lock();

try {
this._renderHook(component);
} catch (e) {
console.log(e);
}
return this.waitForPropertyValueChange(this._renderLock, 'lock');
return this._renderLock.waitForUnlock();
}

public async clearRenderOutput() {
Expand Down Expand Up @@ -207,7 +205,7 @@ export class TestRunner {
const jsValue = this._valueRegistry[name].value;
const sharedValue = this._valueRegistry[name];
const valueContainer = makeMutable<unknown>(null);
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
valueContainer.value = sharedValue.value;
}, 1000);
Expand Down Expand Up @@ -379,42 +377,12 @@ export class TestRunner {
this._currentTestSuite.afterEach = job;
}

private waitForPropertyValueChange(
targetObject: LockObject,
targetProperty: 'lock',
initialValue = true,
maxWaitTime?: number,
) {
return new Promise(resolve => {
const startTime = performance.now();
const interval = setInterval(() => {
const currentTime = performance.now();
const waitTimeExceeded = maxWaitTime && maxWaitTime < currentTime - startTime;
if (targetObject[targetProperty] !== initialValue || waitTimeExceeded) {
clearInterval(interval);
resolve(targetObject[targetProperty]);
}
}, 10);
});
}

public async runOnUIBlocking(worklet: () => void, maxWaitTime?: number) {
const unlock = () => (this._threadLock.lock = false);
this._threadLock.lock = true;
runOnUI(() => {
'worklet';
worklet();
runOnJS(unlock)();
})();
await this.waitForPropertyValueChange(this._threadLock, 'lock', true, maxWaitTime);
}

public async recordAnimationUpdates() {
const updatesContainer = createUpdatesContainer(this);
const updatesContainer = createUpdatesContainer();
const recordAnimationUpdates = updatesContainer.pushAnimationUpdates;
const recordLayoutAnimationUpdates = updatesContainer.pushLayoutAnimationUpdates;

await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
const originalUpdateProps = global._IS_FABRIC ? global._updatePropsFabric : global._updatePropsPaper;
global.originalUpdateProps = originalUpdateProps;
Expand All @@ -441,7 +409,7 @@ export class TestRunner {
}

public async stopRecordingAnimationUpdates() {
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
if (global.originalUpdateProps) {
if (global._IS_FABRIC) {
Expand All @@ -459,7 +427,7 @@ export class TestRunner {
}

public async mockAnimationTimer() {
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
global.mockedAnimationTimestamp = 0;
global.originalGetAnimationTimestamp = global._getAnimationTimestamp;
Expand Down Expand Up @@ -489,31 +457,31 @@ export class TestRunner {
}

public async setAnimationTimestamp(timestamp: number) {
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
assertMockedAnimationTimestamp(global.mockedAnimationTimestamp);
global.mockedAnimationTimestamp = timestamp;
});
}

public async advanceAnimationByTime(time: number) {
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
assertMockedAnimationTimestamp(global.mockedAnimationTimestamp);
global.mockedAnimationTimestamp += time;
});
}

public async advanceAnimationByFrames(frameCount: number) {
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
assertMockedAnimationTimestamp(global.mockedAnimationTimestamp);
global.mockedAnimationTimestamp += frameCount * 16;
});
}

public async unmockAnimationTimer() {
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
if (global.originalGetAnimationTimestamp) {
global._getAnimationTimestamp = global.originalGetAnimationTimestamp;
Expand Down Expand Up @@ -583,7 +551,7 @@ export class TestRunner {
};
console.error = mockedConsoleFunction;
console.warn = mockedConsoleFunction;
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
console.error = mockedConsoleFunction;
console.warn = mockedConsoleFunction;
Expand All @@ -592,7 +560,7 @@ export class TestRunner {
const restoreConsole = async () => {
console.error = originalError;
console.warn = originalWarning;
await this.runOnUIBlocking(() => {
await this._syncUIRunner.runOnUIBlocking(() => {
'worklet';
console.error = originalError;
console.warn = originalWarning;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { makeMutable } from 'react-native-reanimated';
import type { Operation, OperationUpdate } from './types';
import { isValidPropName } from './types';
import type { TestRunner } from './TestRunner';
import type { MultiViewSnapshot, SingleViewSnapshot } from './matchers/snapshotMatchers';
import { convertDecimalColor } from './util';
import type { TestComponent } from './TestComponent';
import { SyncUIRunner } from './SyncUIRunner';

type JsUpdate = {
tag: number;
Expand All @@ -18,7 +18,7 @@ type NativeUpdate = {
jsUpdateIndex: number;
};

export function createUpdatesContainer(testRunner: TestRunner) {
export function createUpdatesContainer() {
const jsUpdates = makeMutable<Array<JsUpdate>>([]);
const nativeSnapshots = makeMutable<Array<NativeUpdate>>([]);

Expand Down Expand Up @@ -151,7 +151,7 @@ export function createUpdatesContainer(testRunner: TestRunner) {
const nativeSnapshotsCount = nativeSnapshots.value.length;
const jsUpdatesCount = jsUpdates.value.length;
if (jsUpdatesCount === nativeSnapshotsCount) {
await testRunner.runOnUIBlocking(() => {
await new SyncUIRunner().runOnUIBlocking(() => {
'worklet';
const lastSnapshot = nativeSnapshots.value[nativeSnapshotsCount - 1];
if (lastSnapshot) {
Expand Down

0 comments on commit d7de282

Please sign in to comment.