Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComparisonMode, TestValue } from './types';
import { TestValue } from './types';

export const RUNTIME_TEST_ERRORS = {
UNDEFINED_TEST_SUITE: 'Undefined test suite context',
Expand Down Expand Up @@ -26,14 +26,3 @@ export function color(

return `${COLOR_CODES[color]}${value}\x1b[0m`;
}

export function defaultTestErrorLog(
expected: TestValue,
received: TestValue,
mode: ComparisonMode
) {
const coloredExpected = color(expected, 'green');
const coloredReceived = color(received, 'red');
const coloredMode = color(mode, 'yellow');
return `Expected ${coloredExpected} received ${coloredReceived}, mode: ${coloredMode}`;
}
163 changes: 114 additions & 49 deletions app/src/examples/RuntimeTests/ReanimatedRuntimeTestsRunner/Matchers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getComparator } from './Comparators';
import { color, defaultTestErrorLog } from './LogMessageUtils';
import { color } from './LogMessageUtils';
import {
ComparisonMode,
OperationUpdate,
Expand All @@ -8,10 +8,20 @@ import {
TrackerCallCount,
} from './types';

type MatcherFunction = (
currentValue: TestValue,
expectedValue: TestValue,
...additionalArgs: Array<unknown>
) => {
pass: boolean;
message: string;
};

export class Matchers {
constructor(private currentValue: TestValue, private testCase: TestCase) {}
private _negation = false;
constructor(private _currentValue: TestValue, private _testCase: TestCase) {}

private assertValueIsCallTracker(
private static _assertValueIsCallTracker(
value: TrackerCallCount | TestValue
): asserts value is TrackerCallCount {
if (
Expand All @@ -22,64 +32,119 @@ export class Matchers {
}
}

public toBe(expectedValue: TestValue, comparisonMode = ComparisonMode.AUTO) {
private _toBeMatcher: MatcherFunction = (
currentValue: TestValue,
expectedValue: TestValue,
comparisonModeUnknown: unknown
) => {
const comparisonMode: ComparisonMode =
typeof comparisonModeUnknown === 'string' &&
comparisonModeUnknown in ComparisonMode
? (comparisonModeUnknown as ComparisonMode)
: ComparisonMode.AUTO;

const isEqual = getComparator(comparisonMode);
if (!isEqual(expectedValue, this.currentValue)) {
this.testCase.errors.push(
defaultTestErrorLog(expectedValue, this.currentValue, comparisonMode)
);
}
}

public toBeCalled(times = 1) {
this.assertValueIsCallTracker(this.currentValue);
const callsCount = this.currentValue.onUI + this.currentValue.onJS;
if (callsCount !== times) {
const name = color(this.currentValue.name, 'green');
const expected = color(times, 'green');
const received = color(callsCount, 'red');
this.testCase.errors.push(
`Expected ${name} to be called ${expected} times, but was called ${received} times`
);
}
}
const coloredExpected = color(expectedValue, 'green');
const coloredReceived = color(currentValue, 'red');
const coloredMode = color(comparisonMode, 'yellow');

return {
pass: isEqual(expectedValue, currentValue),
message: `Expected${
this._negation ? ' NOT' : ''
} ${coloredExpected} received ${coloredReceived}, mode: ${coloredMode}`,
};
};

private _toBeCalledMatcher: MatcherFunction = (
currentValue: TestValue,
times = 1
) => {
Matchers._assertValueIsCallTracker(currentValue);
const callsCount = currentValue.onUI + currentValue.onJS;
const name = color(currentValue.name, 'green');
const expected = color(times, 'green');
const received = color(callsCount, 'red');
return {
pass: callsCount === times,
message: `Expected ${name}${
this._negation ? ' NOT' : ''
} to be called ${expected} times, but was called ${received} times`,
};
};

public toBeCalledUI(times = 1) {
this.assertValueIsCallTracker(this.currentValue);
if (this.currentValue.onUI !== times) {
const name = color(this.currentValue.name, 'green');
const threadName = color('UI thread', 'cyan');
const expected = color(times, 'green');
const received = color(this.currentValue.onUI, 'red');
this.testCase.errors.push(
`Expected ${name} to be called ${expected} times on ${threadName}, but was called ${received} times`
private _toBeCalledUIMatcher: MatcherFunction = (
currentValue: TestValue,
times = 1
) => {
Matchers._assertValueIsCallTracker(currentValue);
const callsCount = currentValue.onUI;
const name = color(currentValue.name, 'green');
const threadName = color('UI thread', 'cyan');
const expected = color(times, 'green');
const received = color(callsCount, 'red');

return {
pass: callsCount === times,
message: `Expected ${name}${
this._negation ? ' NOT' : ''
} to be called ${expected} times on ${threadName}, but was called ${received} times`,
};
};

private _toBeCalledJSMatcher: MatcherFunction = (
currentValue: TestValue,
times = 1
) => {
Matchers._assertValueIsCallTracker(currentValue);
const callsCount = currentValue.onJS;
const name = color(currentValue.name, 'green');
const threadName = color('JS thread', 'cyan');
const expected = color(times, 'green');
const received = color(callsCount, 'red');

return {
pass: callsCount === times,
message: `Expected ${name}${
this._negation ? ' NOT' : ''
} to be called ${expected} times on ${threadName}, but was called ${received} times`,
};
};

private decorateMatcher(matcher: MatcherFunction) {
return (expectedValue: TestValue, ...args: Array<unknown>) => {
const { pass, message } = matcher(
this._currentValue,
expectedValue,
...args
);
}
if ((!pass && !this._negation) || (pass && this._negation)) {
this._testCase.errors.push(message);
}
};
}

public toBeCalledJS(times = 1) {
this.assertValueIsCallTracker(this.currentValue);
if (this.currentValue.onJS !== times) {
const name = color(this.currentValue.name, 'green');
const threadName = color('UI thread', 'cyan');
const expected = color(times, 'green');
const received = color(this.currentValue.onUI, 'red');
this.testCase.errors.push(
`Expected ${name} to be called ${expected} times on ${threadName}, but was called ${received} times`
);
}
public toBe = this.decorateMatcher(this._toBeMatcher);
public toBeCalled = this.decorateMatcher(this._toBeCalledMatcher);
public toBeCalledUI = this.decorateMatcher(this._toBeCalledUIMatcher);
public toBeCalledJS = this.decorateMatcher(this._toBeCalledJSMatcher);

get not() {
this._negation = true;
return this;
}

public toMatchSnapshot(expectedSnapshots: Array<Record<string, unknown>>) {
const capturedSnapshots = this.currentValue as Array<
public toMatchSnapshots(expectedSnapshots: Array<Record<string, unknown>>) {
const capturedSnapshots = this._currentValue as Array<
Record<string, unknown>
>;
if (capturedSnapshots.length !== expectedSnapshots.length) {
const errorMessage = this.formatMismatchLengthErrorMessage(
expectedSnapshots.length,
capturedSnapshots.length
);
this.testCase.errors.push(errorMessage);
this._testCase.errors.push(errorMessage);
}
let errorString = '';
expectedSnapshots.forEach(
Expand All @@ -97,7 +162,7 @@ export class Matchers {
}
);
if (errorString !== '') {
this.testCase.errors.push('Snapshot mismatch: \n' + errorString);
this._testCase.errors.push('Snapshot mismatch: \n' + errorString);
}
}

Expand All @@ -109,7 +174,7 @@ export class Matchers {
Updates applied through `_updateProps` are not synchronously applied to the native side. Instead, they are batched and applied at the end of each frame. Therefore, it is not allowed to take a native snapshot immediately after the `_updateProps` call. To address this issue, we need to wait for the next frame before capturing the native snapshot. That's why native snapshots are one frame behind JS snapshots. To account for this delay, one additional native snapshot is taken during the execution of the `getNativeSnapshots` function.
*/
let errorString = '';
const jsUpdates = this.currentValue as Array<OperationUpdate>;
const jsUpdates = this._currentValue as Array<OperationUpdate>;
for (let i = 0; i < jsUpdates.length; i++) {
errorString += this.compareJsAndNativeSnapshot(
jsUpdates,
Expand All @@ -123,7 +188,7 @@ export class Matchers {
} snapshots\n`;
}
if (errorString !== '') {
this.testCase.errors.push('Native snapshot mismatch: \n' + errorString);
this._testCase.errors.push('Native snapshot mismatch: \n' + errorString);
}
}

Expand Down
20 changes: 18 additions & 2 deletions app/src/examples/RuntimeTests/tests/Animations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ describe('Tests of animations', () => {
expect(await component.getAnimatedStyle('width')).toBe('123');
});

test('withTiming - not - expect error', async () => {
await render(<AnimatedComponent />);
const component = getTestComponent('AnimatedComponent');
await wait(600);
expect(await component.getAnimatedStyle('width')).not.toBe('100');
});

test('withTiming - with not', async () => {
await render(<AnimatedComponent />);
const component = getTestComponent('AnimatedComponent');
await wait(600);
expect(await component.getAnimatedStyle('width')).not.toBe('123');
});

test('withTiming - expect pass', async () => {
await render(<AnimatedComponent />);
const component = getTestComponent('AnimatedComponent');
Expand Down Expand Up @@ -200,7 +214,9 @@ describe('Tests of animations', () => {
const updatesContainer = await recordAnimationUpdates();
await render(<AnimatedComponent />);
await wait(1000);
expect(updatesContainer.getUpdates()).toMatchSnapshot(Snapshots.animation3);
expect(updatesContainer.getUpdates()).toMatchSnapshots(
Snapshots.animation3
);
expect(updatesContainer.getUpdates()).toMatchNativeSnapshots(
await updatesContainer.getNativeSnapshots()
);
Expand All @@ -211,7 +227,7 @@ describe('Tests of animations', () => {
const updatesContainer = await recordAnimationUpdates();
await render(<LayoutAnimation />);
await wait(600);
expect(updatesContainer.getUpdates()).toMatchSnapshot(
expect(updatesContainer.getUpdates()).toMatchSnapshots(
Snapshots.layoutAnimation
);
});
Expand Down