Files
Rubén NorteandFacebook GitHub Bot 849c139a4c Initial implementation of Jest test runner for RN integration tests (#47558)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/47558

Changelog: [internal]

Reviewed By: sammy-SC

Differential Revision: D65661701

fbshipit-source-id: 0f0227debc769d0cebebc1989cbcfbbdd44dfc34
2024-11-14 06:20:47 -08:00

181 lines
3.9 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import nullthrows from 'nullthrows';
export type TestCaseResult = {
ancestorTitles: Array<string>,
title: string,
fullName: string,
status: 'passed' | 'failed' | 'pending',
duration: number,
failureMessages: Array<string>,
numPassingAsserts: number,
// location: string,
};
export type TestSuiteResult =
| {
testResults: Array<TestCaseResult>,
}
| {
error: {
message: string,
stack: string,
},
};
const tests: Array<{
title: string,
ancestorTitles: Array<string>,
implementation: () => mixed,
result?: TestCaseResult,
}> = [];
const ancestorTitles: Array<string> = [];
global.describe = (title: string, implementation: () => mixed) => {
ancestorTitles.push(title);
implementation();
ancestorTitles.pop();
};
global.it = (title: string, implementation: () => mixed) =>
tests.push({
title,
implementation,
ancestorTitles: ancestorTitles.slice(),
});
// flowlint unsafe-getters-setters:off
class Expect {
#received: mixed;
#isNot: boolean = false;
constructor(received: mixed) {
this.#received = received;
}
get not(): this {
this.#isNot = !this.#isNot;
return this;
}
toBe(expected: mixed): void {
const pass = this.#received !== expected;
if (this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(expected)} but received ${String(this.#received)}.`,
);
}
}
toBeInstanceOf(expected: Class<mixed>): void {
const pass = this.#received instanceof expected;
if (!pass) {
throw new Error(
`expected ${String(this.#received)} to be an instance of ${String(expected)}`,
);
}
}
toBeCloseTo(expected: number, precision: number = 2): void {
const pass =
Math.abs(expected - Number(this.#received)) < Math.pow(10, -precision);
if (!pass) {
throw new Error(
`expected ${String(this.#received)} to be close to ${expected}`,
);
}
}
toThrow(error: mixed): void {
if (error != null) {
throw new Error('toThrow() implementation does not accept arguments.');
}
let pass = false;
try {
// $FlowExpectedError[not-a-function]
this.#received();
} catch {
pass = true;
}
if (!pass) {
throw new Error(`expected ${String(this.#received)} to throw`);
}
}
#isExpectedResult(pass: boolean): boolean {
return this.#isNot ? !pass : pass;
}
}
global.expect = (received: mixed) => new Expect(received);
function runWithGuard(fn: () => void) {
try {
fn();
} catch (error) {
let reportedError =
error instanceof Error ? error : new Error(String(error));
reportTestSuiteResult({
error: {
message: reportedError.message,
stack: reportedError.stack,
},
});
}
}
function executeTests() {
for (const test of tests) {
let status;
let error;
const start = Date.now();
try {
test.implementation();
status = 'passed';
} catch (e) {
error = e;
status = 'failed';
}
test.result = {
title: test.title,
fullName: [...test.ancestorTitles, test.title].join(' '),
ancestorTitles: test.ancestorTitles,
status,
duration: Date.now() - start,
failureMessages: status === 'failed' && error ? [error.message] : [],
numPassingAsserts: 0,
};
}
reportTestSuiteResult({
testResults: tests.map(test => nullthrows(test.result)),
});
}
function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
console.log(JSON.stringify(testSuiteResult));
}
export function registerTest(setUpTest: () => void) {
runWithGuard(() => {
setUpTest();
executeTests();
});
}