Add the ability for tests to be marked as 'skipped'

Summary:
Changelog: [RNTester][Internal] - Add the ability for platform tests to be marked as skipped

There are some properties/tests in the web platform tests that we don't want to especially priorities (especially if they aren't especially relevant to the specification and just legacy browser compat) so this adds the ability for us to mark these tests as skipped. This is so that we can ensure that our platform tests are "complete" while decreasing the noise and distraction from failing tests that don't really apply to RN.

Reviewed By: lunaleaps

Differential Revision: D37889470

fbshipit-source-id: 6516ac90c242d662518a95cb9ba8ce643f6bb09c
This commit is contained in:
Vincent Riemer
2022-07-18 14:12:25 -07:00
committed by Facebook GitHub Bot
parent 966f800b7c
commit 663dd9177e
7 changed files with 184 additions and 70 deletions
@@ -24,7 +24,7 @@ function ExampleTestCase ({ harness }) { /* ... */ }
As of writting this README there are 2 different types of tests that the `harness` prop provides:
### `test(testcase: (TestContext) => void, testName: string)`
### `test(testcase: (TestContext) => void, testName: string, options?: TestOptions)`
This is a method to create "regular" test reminicent of other frameworks such as Jest. These are meant to be run imperatively, and while that means that they technically could work in a `useEffect` hook as a way to run the test "on mount" — it is instead recommended to try and keep these tests in callbacks instead. A good alternative to running the test on mount would be to instead put the test in a callback and render a "Start Test" button which executes the callback.
@@ -35,6 +35,10 @@ The first argument is the closure in which you will run your test and make asser
* `assert_greater_than_equal(a: number, b: number, description: string): void`
* `assert_less_than_equal(a: number, b: number, description: string): void`
An optional third argument can be used for specifying additional options to the test — that object currently has the following properties (all of which are optional themselves):
* `skip: boolean`: In cases where we want the test to be registered but we don't want it to contribute to the pass/fail count.
Here's what a basic/contrived example which verifies the layout of a basic view:
```js
@@ -10,6 +10,8 @@
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
import RNTesterPlatformTestResultsText from './RNTesterPlatformTestResultsText';
import * as React from 'react';
import {View, Text, StyleSheet, TouchableHighlight} from 'react-native';
@@ -18,6 +20,7 @@ type Props = $ReadOnly<{|
numError: number,
numPass: number,
numPending: number,
numSkipped: number,
onPress?: () => void,
style?: ?ViewStyleProp,
|}>;
@@ -26,26 +29,22 @@ export default function RNTesterPlatformTestMinimizedResultView({
numError,
numPass,
numPending,
numSkipped,
onPress,
style,
}: Props): React.MixedElement {
return (
<TouchableHighlight onPress={onPress} style={[styles.root, style]}>
<View style={styles.innerContainer}>
<View style={styles.statsContainer}>
<Text style={styles.summaryText}>
{numPass} <Text style={styles.passText}>Pass</Text>
</Text>
<Text style={styles.summaryText}>
{numFail} <Text style={styles.failText}>Fail</Text>
</Text>
<Text style={styles.summaryText}>
{numError} <Text style={styles.errorText}>Error</Text>
</Text>
<Text style={styles.summaryText}>
{numPending} <Text style={styles.pendingText}>Pending</Text>
</Text>
</View>
<Text style={styles.statsContainer}>
<RNTesterPlatformTestResultsText
numError={numError}
numFail={numFail}
numPass={numPass}
numPending={numPending}
numSkipped={numSkipped}
/>
</Text>
<Text style={styles.caret}></Text>
</View>
</TouchableHighlight>
@@ -59,12 +58,6 @@ const styles = StyleSheet.create({
marginEnd: 8,
opacity: 0.5,
},
errorText: {
color: 'orange',
},
failText: {
color: 'red',
},
innerContainer: {
width: '100%',
height: '100%',
@@ -74,12 +67,6 @@ const styles = StyleSheet.create({
paddingHorizontal: 8,
backgroundColor: 'white',
},
passText: {
color: 'green',
},
pendingText: {
color: 'gray',
},
root: {
borderTopColor: 'rgb(171, 171, 171)',
borderTopWidth: StyleSheet.hairlineWidth,
@@ -89,8 +76,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
},
summaryText: {
marginStart: 8,
},
});
@@ -19,6 +19,7 @@ import type {
} from './RNTesterPlatformTestTypes';
import RNTesterPlatformTestMinimizedResultView from './RNTesterPlatformTestMinimizedResultView';
import RNTesterPlatformTestResultsText from './RNTesterPlatformTestResultsText';
import * as React from 'react';
import {useMemo, useState, useCallback} from 'react';
@@ -40,6 +41,7 @@ const DISPLAY_STATUS_MAPPING: {[PlatformTestResultStatus]: string} = {
PASS: 'Pass',
FAIL: 'Fail',
ERROR: 'Error',
SKIPPED: 'Skipped',
};
type FilterModalProps = $ReadOnly<{
@@ -183,7 +185,7 @@ export default function RNTesterPlatformTestResultView(
);
}, [filterText, results]);
const {numPass, numFail, numError} = useMemo(
const {numPass, numFail, numError, numSkipped} = useMemo(
() =>
filteredResults.reduce(
(acc, result) => {
@@ -194,12 +196,15 @@ export default function RNTesterPlatformTestResultView(
return {...acc, numFail: acc.numFail + 1};
case 'ERROR':
return {...acc, numError: acc.numError + 1};
case 'SKIPPED':
return {...acc, numSkipped: acc.numSkipped + 1};
}
},
{
numPass: 0,
numFail: 0,
numError: 0,
numSkipped: 0,
},
),
[filteredResults],
@@ -228,6 +233,7 @@ export default function RNTesterPlatformTestResultView(
numError={numError}
numPass={numPass}
numPending={numPending}
numSkipped={numSkipped}
onPress={handleMinimizedPress}
style={style}
/>
@@ -250,26 +256,13 @@ export default function RNTesterPlatformTestResultView(
</Text>
) : null}
<Text style={styles.summaryContainer}>
<Text>
{numPass} <Text style={styles.passText}>Pass</Text>
</Text>
{' '}
<Text>
{numFail} <Text style={styles.failText}>Fail</Text>
</Text>
{' '}
<Text>
{numError} <Text style={styles.errorText}>Error</Text>
</Text>
{numPending > 0 ? (
<>
{' '}
<Text>
{numPending}{' '}
<Text style={styles.pendingText}>Pending</Text>
</Text>
</>
) : null}
<RNTesterPlatformTestResultsText
numError={numError}
numFail={numFail}
numPass={numPass}
numPending={numPending}
numSkipped={numSkipped}
/>
</Text>
</View>
<View style={styles.actionsContainer}>
@@ -399,6 +392,9 @@ const styles = StyleSheet.create({
paddingTop: 8,
flex: 0,
},
skippedText: {
color: 'blue',
},
table: {
flex: 1,
},
@@ -446,4 +442,5 @@ const STATUS_TEXT_STYLE_MAPPING: {[PlatformTestResultStatus]: TextStyle} = {
PASS: styles.passText,
FAIL: styles.failText,
ERROR: styles.errorText,
SKIPPED: styles.skippedText,
};
@@ -0,0 +1,78 @@
/**
* 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 {Text, StyleSheet} from 'react-native';
import * as React from 'react';
type Props = $ReadOnly<{
numPass: number,
numFail: number,
numError: number,
numPending: number,
numSkipped: number,
}>;
export default function RNTesterPlatformTestResultsText(
props: Props,
): React.MixedElement {
const {numPass, numFail, numError, numPending, numSkipped} = props;
return (
<>
<Text>
{numPass} <Text style={styles.passText}>Pass</Text>
</Text>
{' '}
<Text>
{numFail} <Text style={styles.failText}>Fail</Text>
</Text>
{numSkipped > 0 ? (
<>
{' '}
<Text>
{numSkipped} <Text style={styles.skippedText}>Skipped</Text>
</Text>
</>
) : null}
{numError > 0 ? (
<>
{' '}
<Text>
{numError} <Text style={styles.errorText}>Error</Text>
</Text>
</>
) : null}
{numPending > 0 ? (
<>
{' '}
<Text>
{numPending} <Text style={styles.pendingText}>Pending</Text>
</Text>
</>
) : null}
</>
);
}
const styles = StyleSheet.create({
errorText: {
color: 'orange',
},
failText: {
color: 'red',
},
passText: {
color: 'green',
},
pendingText: {
color: 'gray',
},
skippedText: {
color: 'blue',
},
});
@@ -28,7 +28,7 @@ export type PlatformTestAssertionResult =
| PassingPlatformTestAssertionResult
| FailingPlatformTestAssertionResult;
export type PlatformTestResultStatus = 'PASS' | 'FAIL' | 'ERROR';
export type PlatformTestResultStatus = 'PASS' | 'FAIL' | 'ERROR' | 'SKIPPED';
export type PlatformTestResult = $ReadOnly<{|
name: string,
@@ -50,8 +50,16 @@ export type AsyncPlatformTest = $ReadOnly<{|
done(): void,
|}>;
export type SyncTestOptions = $ReadOnly<{|
skip?: boolean,
|}>;
export type PlatformTestHarness = $ReadOnly<{|
test(testcase: PlatformTestCase, name: string): void,
test(
testcase: PlatformTestCase,
name: string,
options?: SyncTestOptions,
): void,
useAsyncTest(description: string, timeout?: number): AsyncPlatformTest,
|}>;
@@ -16,6 +16,7 @@ import type {
PlatformTestCase,
PlatformTestAssertionResult,
PlatformTestContext,
SyncTestOptions,
} from './RNTesterPlatformTestTypes';
type AsyncTestStatus = 'NOT_RAN' | 'COMPLETED' | 'TIMED_OUT';
@@ -174,7 +175,23 @@ export default function usePlatformTestHarness(): PlatformTestHarnessHookResult
}, []);
const testFunction: PlatformTestHarness['test'] = useCallback(
(testCase: PlatformTestCase, name: string): void => {
(
testCase: PlatformTestCase,
name: string,
options?: SyncTestOptions,
): void => {
const {skip = false} = options ?? {};
if (skip) {
addTestResult({
name,
status: 'SKIPPED',
assertions: [],
error: null,
});
return;
}
const assertionResults: Array<PlatformTestAssertionResult> = [];
const baseAssert = (
@@ -14,6 +14,10 @@ import type {PointerEvent} from 'react-native/Libraries/Types/CoreEventTypes';
import {useMemo} from 'react';
// These props are not in the specification but are present in the WPT so we keep them
// but marked as skipped so we don't prioritize them
const SKIPPED_PROPS = ['fromElement', 'toElement'];
// Check for conformance to PointerEvent interface
// TA: 1.1, 1.2, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11, 1.12, 1.13
// Adapted from https://github.com/web-platform-tests/wpt/blob/6c26371ea1c144dd612864a278e88b6ba2f3d883/pointerevents/pointerevent_support.js#L15
@@ -95,33 +99,54 @@ export function check_PointerEvent(
const name = attr[2];
const value = attr[3];
const skip = SKIPPED_PROPS.includes(name);
// existence check
harness.test(({assert_true}) => {
assert_true(
name in nativeEvent,
name + ' attribute in ' + eventType + ' event',
);
}, pointerTestName + '.' + name + ' attribute exists');
harness.test(
({assert_true}) => {
assert_true(
name in nativeEvent,
name + ' attribute in ' + eventType + ' event',
);
},
pointerTestName + '.' + name + ' attribute exists',
{skip},
);
// readonly check
// TODO
// type check
harness.test(({assert_true}) => {
assert_true(
harness.test(
({assert_true}) => {
assert_true(
// $FlowFixMe
idl_type_check[type](nativeEvent[name]),
name + ' attribute of type ' + type,
);
},
pointerTestName +
'.' +
name +
' IDL type ' +
type +
' (JS type was ' +
// $FlowFixMe
idl_type_check[type](nativeEvent[name]),
name + ' attribute of type ' + type,
);
// $FlowFixMe
}, pointerTestName + '.' + name + ' IDL type ' + type + ' (JS type was ' + typeof nativeEvent[name] + ')');
typeof nativeEvent[name] +
')',
{skip},
);
// value check if defined
if (value !== undefined) {
harness.test(({assert_equals}) => {
// $FlowFixMe
assert_equals(nativeEvent[name], value, name + ' attribute value');
}, pointerTestName + '.' + name + ' value is ' + String(value) + '.');
harness.test(
({assert_equals}) => {
// $FlowFixMe
assert_equals(nativeEvent[name], value, name + ' attribute value');
},
pointerTestName + '.' + name + ' value is ' + String(value) + '.',
{skip},
);
}
});