mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/48686 Changelog: [Internal] - Removed redundant `{||}` syntax Reviewed By: javache Differential Revision: D68205038 fbshipit-source-id: f7d3271142b6443a5859c3b668b7aebd3ce3ef3f
335 lines
8.9 KiB
JavaScript
335 lines
8.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.
|
|
*
|
|
* @format
|
|
* @flow
|
|
*/
|
|
|
|
import type {
|
|
PlatformTestAssertionResult,
|
|
PlatformTestCase,
|
|
PlatformTestContext,
|
|
PlatformTestHarness,
|
|
PlatformTestResult,
|
|
SyncTestOptions,
|
|
} from './RNTesterPlatformTestTypes';
|
|
|
|
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
|
|
type AsyncTestStatus = 'NOT_RAN' | 'COMPLETED' | 'TIMED_OUT';
|
|
|
|
function didAllAssertionsPass(
|
|
assertions: Array<PlatformTestAssertionResult>,
|
|
): boolean {
|
|
const hasFailingAssertion = assertions.some(assertion => !assertion.passing);
|
|
return !hasFailingAssertion;
|
|
}
|
|
|
|
function constructAsyncTestHook(
|
|
addTestResult: (newResult: PlatformTestResult) => void,
|
|
updateAsyncTestStatuses: (
|
|
(
|
|
$ReadOnly<{[string]: AsyncTestStatus}>,
|
|
) => $ReadOnly<{[string]: AsyncTestStatus}>,
|
|
) => void,
|
|
runTestCase: (
|
|
testCase: PlatformTestCase,
|
|
) => Array<PlatformTestAssertionResult>,
|
|
) {
|
|
return (description: string, timeoutMs?: number = 10000) => {
|
|
const assertionsRef = useRef<Array<PlatformTestAssertionResult>>([]);
|
|
|
|
const timeoutIDRef = useRef<TimeoutID | null>(null);
|
|
|
|
const timeoutHandler = useCallback(() => {
|
|
timeoutIDRef.current = null;
|
|
addTestResult({
|
|
name: description,
|
|
assertions: [
|
|
...assertionsRef.current,
|
|
{
|
|
passing: false,
|
|
name: 'async_timeout',
|
|
description: `async test should be completed in ${timeoutMs}ms`,
|
|
failureMessage: `expected to complete async test in ${timeoutMs}ms`,
|
|
},
|
|
],
|
|
status: 'FAIL',
|
|
error: null,
|
|
});
|
|
updateAsyncTestStatuses(prev => ({
|
|
...prev,
|
|
[description]: 'TIMED_OUT',
|
|
}));
|
|
}, [description, timeoutMs]);
|
|
|
|
// timeout management
|
|
useEffect(() => {
|
|
timeoutIDRef.current = setTimeout(timeoutHandler, timeoutMs);
|
|
return () => {
|
|
if (timeoutIDRef.current != null) {
|
|
clearTimeout(timeoutIDRef.current);
|
|
}
|
|
};
|
|
}, [timeoutHandler, timeoutMs]);
|
|
|
|
const completionHandler = useCallback(() => {
|
|
const timeoutID = timeoutIDRef.current;
|
|
if (timeoutID != null) {
|
|
clearTimeout(timeoutID);
|
|
timeoutIDRef.current = null;
|
|
}
|
|
|
|
updateAsyncTestStatuses(prev => {
|
|
if (prev[description] === 'NOT_RAN') {
|
|
addTestResult({
|
|
name: description,
|
|
assertions: [
|
|
...assertionsRef.current,
|
|
{
|
|
passing: true,
|
|
name: 'async_test',
|
|
description: 'async test should be completed',
|
|
},
|
|
],
|
|
status: didAllAssertionsPass(assertionsRef.current)
|
|
? 'PASS'
|
|
: 'FAIL',
|
|
error: null,
|
|
});
|
|
return {...prev, [description]: 'COMPLETED'};
|
|
}
|
|
return prev;
|
|
});
|
|
}, [description]);
|
|
|
|
const stepHandler = useCallback((testCase: PlatformTestCase) => {
|
|
const stepAssertions = runTestCase(testCase);
|
|
assertionsRef.current.push(...stepAssertions);
|
|
}, []);
|
|
|
|
// test registration
|
|
useEffect(() => {
|
|
updateAsyncTestStatuses(prev => {
|
|
if (!prev.hasOwnProperty(description)) {
|
|
return {...prev, [description]: 'NOT_RAN'};
|
|
}
|
|
return prev;
|
|
});
|
|
}, [description]);
|
|
|
|
return useMemo(
|
|
() => ({
|
|
done: completionHandler,
|
|
step: stepHandler,
|
|
}),
|
|
[completionHandler, stepHandler],
|
|
);
|
|
};
|
|
}
|
|
|
|
export type PlatformTestHarnessHookResult = $ReadOnly<{
|
|
testKey: number,
|
|
harness: PlatformTestHarness,
|
|
numPending: number,
|
|
reset: () => void,
|
|
results: $ReadOnlyArray<PlatformTestResult>,
|
|
}>;
|
|
|
|
export default function usePlatformTestHarness(): PlatformTestHarnessHookResult {
|
|
const [testResults, updateTestResults] = useState<
|
|
$ReadOnlyArray<PlatformTestResult>,
|
|
>([]);
|
|
|
|
// Since updating the test results array can get expensive at larger sizes
|
|
// we use a basic debouncing logic to minimize the number of re-renders
|
|
// caused by adding test results
|
|
const resultQueueRef = useRef<Array<PlatformTestResult>>([]);
|
|
const schedulerTimeoutIdRef = useRef<null | TimeoutID>(null);
|
|
|
|
const commitResults = useCallback(() => {
|
|
const queuedResults = resultQueueRef.current;
|
|
if (queuedResults.length > 0) {
|
|
updateTestResults(prev => [...prev, ...queuedResults]);
|
|
resultQueueRef.current = [];
|
|
}
|
|
}, []);
|
|
|
|
const scheduleResultsCommit = useCallback(() => {
|
|
const schedulerTimeoutId = schedulerTimeoutIdRef.current;
|
|
if (schedulerTimeoutId != null) {
|
|
clearTimeout(schedulerTimeoutId);
|
|
}
|
|
schedulerTimeoutIdRef.current = setTimeout(() => commitResults(), 500);
|
|
}, [commitResults]);
|
|
|
|
const addTestResult = useCallback(
|
|
(newResult: PlatformTestResult) => {
|
|
resultQueueRef.current.push(newResult);
|
|
scheduleResultsCommit();
|
|
},
|
|
[scheduleResultsCommit],
|
|
);
|
|
|
|
// When resetting the test results we should also re-mount the
|
|
// so we apply a key to that component which we can increment
|
|
// to ensure it re-mounts
|
|
const [testElementKey, setTestElementKey] = useState<number>(0);
|
|
|
|
const [asyncTestStatuses, updateAsyncTestStatuses] = useState<
|
|
$ReadOnly<{[string]: AsyncTestStatus}>,
|
|
>({});
|
|
|
|
const reset = useCallback(() => {
|
|
updateTestResults([]);
|
|
updateAsyncTestStatuses({});
|
|
setTestElementKey(k => k + 1);
|
|
}, []);
|
|
|
|
const runTestCase = useCallback((testCase: PlatformTestCase) => {
|
|
const assertionResults: Array<PlatformTestAssertionResult> = [];
|
|
|
|
const baseAssert = (
|
|
assertionName: string,
|
|
testConditionResult: boolean,
|
|
description: string,
|
|
failureMessage: string,
|
|
) => {
|
|
if (testConditionResult) {
|
|
assertionResults.push({
|
|
passing: true,
|
|
name: assertionName,
|
|
description,
|
|
});
|
|
} else {
|
|
assertionResults.push({
|
|
passing: false,
|
|
name: assertionName,
|
|
description,
|
|
failureMessage,
|
|
});
|
|
}
|
|
};
|
|
|
|
const context: PlatformTestContext = {
|
|
assert_true: (cond: boolean, desc: string) =>
|
|
baseAssert(
|
|
'assert_true',
|
|
cond,
|
|
desc,
|
|
"expected 'true' but received 'false'",
|
|
),
|
|
assert_equals: (a: any, b: any, desc: string) =>
|
|
baseAssert(
|
|
'assert_equal',
|
|
a === b,
|
|
desc,
|
|
`expected ${a} to equal ${b}`,
|
|
),
|
|
assert_not_equals: (a: any, b: any, desc: string) =>
|
|
baseAssert(
|
|
'assert_not_equals',
|
|
a !== b,
|
|
desc,
|
|
`expected ${a} not to equal ${b}`,
|
|
),
|
|
assert_greater_than_equal: (a: number, b: number, desc: string) =>
|
|
baseAssert(
|
|
'assert_greater_than_equal',
|
|
a >= b,
|
|
desc,
|
|
`expected ${a} to be greater than or equal to ${b}`,
|
|
),
|
|
assert_less_than_equal: (a: number, b: number, desc: string) =>
|
|
baseAssert(
|
|
'assert_less_than_equal',
|
|
a <= b,
|
|
desc,
|
|
`expected ${a} to be less than or equal to ${b}`,
|
|
),
|
|
};
|
|
|
|
testCase(context);
|
|
|
|
return assertionResults;
|
|
}, []);
|
|
|
|
const testFunction: PlatformTestHarness['test'] = useCallback(
|
|
(
|
|
testCase: PlatformTestCase,
|
|
name: string,
|
|
options?: SyncTestOptions,
|
|
): void => {
|
|
const {skip = false} = options ?? {};
|
|
|
|
if (skip) {
|
|
addTestResult({
|
|
name,
|
|
status: 'SKIPPED',
|
|
assertions: [],
|
|
error: null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const assertionResults = runTestCase(testCase);
|
|
addTestResult({
|
|
name,
|
|
status: didAllAssertionsPass(assertionResults) ? 'PASS' : 'FAIL',
|
|
assertions: assertionResults,
|
|
error: null,
|
|
});
|
|
} catch (error) {
|
|
addTestResult({
|
|
name,
|
|
status: 'ERROR',
|
|
assertions: [],
|
|
error,
|
|
});
|
|
}
|
|
},
|
|
[addTestResult, runTestCase],
|
|
);
|
|
|
|
const asyncTestHook: PlatformTestHarness['useAsyncTest'] = useMemo(
|
|
() =>
|
|
constructAsyncTestHook(
|
|
addTestResult,
|
|
updateAsyncTestStatuses,
|
|
runTestCase,
|
|
),
|
|
[addTestResult, runTestCase],
|
|
);
|
|
|
|
const numPendingAsyncTests = useMemo(() => {
|
|
let numPending = 0;
|
|
for (const asyncTestName in asyncTestStatuses) {
|
|
const asyncTestStatus = asyncTestStatuses[asyncTestName];
|
|
if (asyncTestStatus === 'NOT_RAN') {
|
|
numPending++;
|
|
}
|
|
}
|
|
return numPending;
|
|
}, [asyncTestStatuses]);
|
|
|
|
const harness: PlatformTestHarness = useMemo(
|
|
() => ({
|
|
test: testFunction,
|
|
useAsyncTest: asyncTestHook,
|
|
}),
|
|
[asyncTestHook, testFunction],
|
|
);
|
|
|
|
return {
|
|
harness,
|
|
numPending: numPendingAsyncTests,
|
|
reset,
|
|
results: testResults,
|
|
testKey: testElementKey,
|
|
};
|
|
}
|