Files
react-native/packages/rn-tester/js/examples/Experimental/PlatformTest/RNTesterPlatformTestResultView.js
T
Sam Zhou b56d709e6e Annotate useCallback in xplat (11/n)
Summary:
Add explicit annotations to useCallback as required for Flow's Local Type Inference project. This codemod prepares the codebase to match Flow's new typechecking algorithm. The new algorithm will make Flow more reliable and predictable.

Codemod command: `flow codemod annotate-use-callback`

drop-conflicts
bypass-lint

Changelog: [Internal]

Reviewed By: evanyeung

Differential Revision: D40079418

fbshipit-source-id: 59750a5d07b2ac1f440927794a7523682f048a5e
2022-10-04 18:51:59 -07:00

447 lines
11 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 {RenderItemProps} from 'react-native/Libraries/Lists/VirtualizedList';
import type {
ViewStyleProp,
TextStyle,
} from 'react-native/Libraries/StyleSheet/StyleSheet';
import type {
PlatformTestResult,
PlatformTestResultStatus,
} from './RNTesterPlatformTestTypes';
import RNTesterPlatformTestMinimizedResultView from './RNTesterPlatformTestMinimizedResultView';
import RNTesterPlatformTestResultsText from './RNTesterPlatformTestResultsText';
import * as React from 'react';
import {useMemo, useState, useCallback} from 'react';
import {
Button,
View,
Text,
StyleSheet,
FlatList,
Modal,
SafeAreaView,
TextInput,
KeyboardAvoidingView,
Platform,
TouchableOpacity,
} from 'react-native';
const DISPLAY_STATUS_MAPPING: {[PlatformTestResultStatus]: string} = {
PASS: 'Pass',
FAIL: 'Fail',
ERROR: 'Error',
SKIPPED: 'Skipped',
};
type FilterModalProps = $ReadOnly<{
filterText: string,
setFilterText: (newFilterText: string) => void,
}>;
function FilterModalButton(props: FilterModalProps) {
const {filterText, setFilterText} = props;
const [modalVisible, setModalVisible] = useState(false);
const [pendingFilterText, setPendingFilterText] = useState(filterText);
const onFilterButtonPress = useCallback(() => {
setPendingFilterText(filterText);
setModalVisible(true);
}, [filterText]);
const onFilterSubmit = useCallback(() => {
setFilterText(pendingFilterText);
setModalVisible(false);
}, [pendingFilterText, setFilterText]);
const onFilterCancel = useCallback(() => {
setModalVisible(false);
}, []);
const onPendingTextChange = useCallback((newText: string) => {
setPendingFilterText(newText);
}, []);
return (
<>
<Button title="Filter" onPress={onFilterButtonPress} />
<Modal
visible={modalVisible}
animationType="fade"
presentationStyle="overFullScreen"
transparent={true}>
<SafeAreaView style={styles.filterModalRoot}>
<KeyboardAvoidingView
style={styles.filterModalKeyboardAvoidingRoot}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<View style={styles.filterModalContainer}>
<View style={styles.filterModalContentContainer}>
<View style={styles.filterModalPromptContainer}>
<Text style={styles.filterModalPromptText}>
Enter a test name filter
</Text>
</View>
<TextInput
autoCapitalize="none"
autoCorrect={false}
autoFocus={true}
style={styles.filterModalPendingTextInput}
value={pendingFilterText}
onChangeText={onPendingTextChange}
onSubmitEditing={onFilterSubmit}
/>
</View>
<View style={styles.filterModalActionsContainer}>
<Button title="Cancel" onPress={onFilterCancel} />
<Button title="Submit" onPress={onFilterSubmit} />
</View>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
</Modal>
</>
);
}
function TableHeader() {
return (
<View style={styles.tableRow}>
<View style={[styles.tableHeaderColumn, styles.tableResultColumn]}>
<Text style={styles.tableHeader}>Result</Text>
</View>
<View style={[styles.tableHeaderColumn, styles.tableTestNameColumn]}>
<Text style={styles.tableHeader}>Test Name</Text>
</View>
<View style={[styles.tableHeaderColumn, styles.tableMessageColumn]}>
<Text style={styles.tableHeader}>Message</Text>
</View>
</View>
);
}
const TableRow = React.memo(
({testResult}: {testResult: PlatformTestResult}) => {
return (
<View style={styles.tableRow}>
<View style={styles.tableResultColumn}>
<Text style={STATUS_TEXT_STYLE_MAPPING[testResult.status]}>
{DISPLAY_STATUS_MAPPING[testResult.status]}
</Text>
</View>
<View style={styles.tableTestNameColumn}>
<Text>{testResult.name}</Text>
</View>
<View style={styles.tableMessageColumn}>
{testResult.assertions.map((assertion, assertionIdx) => {
if (assertion.passing) {
return null;
}
return (
<Text key={assertionIdx}>
{assertion.name}: {assertion.description}{' '}
{assertion.failureMessage}
</Text>
);
})}
</View>
</View>
);
},
);
function renderTableRow({item}: RenderItemProps<PlatformTestResult>) {
return <TableRow testResult={item} />;
}
type Props = $ReadOnly<{|
numPending: number,
reset: () => void,
results: $ReadOnlyArray<PlatformTestResult>,
style?: ?ViewStyleProp,
|}>;
export default function RNTesterPlatformTestResultView(
props: Props,
): React.MixedElement {
const {numPending, reset, results, style} = props;
const [filterText, setFilterText] = useState('');
const filteredResults = useMemo(() => {
if (filterText === '') {
return results;
}
return results.filter(result =>
result.name.toLowerCase().includes(filterText.toLowerCase()),
);
}, [filterText, results]);
const {numPass, numFail, numError, numSkipped} = useMemo(
() =>
filteredResults.reduce(
(acc, result) => {
switch (result.status) {
case 'PASS':
return {...acc, numPass: acc.numPass + 1};
case 'FAIL':
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],
);
const [resultsExpanded, setResultsExpanded] = useState(false);
const handleReset = useCallback(() => {
setFilterText('');
reset();
setResultsExpanded(false);
}, [reset]);
const handleMinimizedPress = useCallback(() => {
setResultsExpanded(true);
}, []);
const handleMaximizedPress = useCallback(() => {
setResultsExpanded(false);
}, []);
return (
<>
<RNTesterPlatformTestMinimizedResultView
numFail={numFail}
numError={numError}
numPass={numPass}
numPending={numPending}
numSkipped={numSkipped}
onPress={handleMinimizedPress}
style={style}
/>
<Modal
animationType="slide"
onRequestClose={handleMaximizedPress}
visible={resultsExpanded}>
<SafeAreaView
style={{
width: '100%',
height: '100%',
flexDirection: 'column',
}}>
<View style={styles.resultsHeader}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Results</Text>
{filterText !== '' ? (
<Text style={styles.filteredText}>
(Filtered: '{filterText}')
</Text>
) : null}
<Text style={styles.summaryContainer}>
<RNTesterPlatformTestResultsText
numError={numError}
numFail={numFail}
numPass={numPass}
numPending={numPending}
numSkipped={numSkipped}
/>
</Text>
</View>
<View style={styles.actionsContainer}>
<FilterModalButton
filterText={filterText}
setFilterText={setFilterText}
/>
<View style={styles.buttonSpacer} />
<Button title="Reset" onPress={handleReset} />
</View>
<TouchableOpacity
hitSlop={{bottom: 10, left: 10, right: 10, top: 10}}
onPress={handleMaximizedPress}
style={styles.closeButton}>
<Text style={styles.closeButtonIcon}>✕</Text>
</TouchableOpacity>
</View>
<View style={styles.table}>
<TableHeader />
<FlatList data={filteredResults} renderItem={renderTableRow} />
</View>
</SafeAreaView>
</Modal>
</>
);
}
const styles = StyleSheet.create({
actionsContainer: {
flexDirection: 'row',
},
buttonSpacer: {
width: 8,
},
closeButton: {
position: 'absolute',
top: 0,
right: 16,
backgroundColor: 'lightgray',
width: 30,
height: 30,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
},
closeButtonIcon: {
fontSize: 18,
fontWeight: 'bold',
opacity: 0.5,
},
errorText: {
color: 'orange',
},
failText: {
color: 'red',
},
filteredText: {
fontSize: 18,
lineHeight: 18,
fontWeight: 'normal',
opacity: 0.5,
},
filterModalActionButton: {
flex: 1,
},
filterModalActionsContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
minHeight: 50,
},
filterModalContainer: {
minWidth: 250,
backgroundColor: 'white',
borderRadius: 20,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
filterModalContentContainer: {
paddingHorizontal: 12,
},
filterModalPromptContainer: {
alignItems: 'center',
justifyContent: 'center',
},
filterModalRoot: {
flex: 1,
justifyContent: 'center',
alignItems: 'stretch',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
filterModalPromptText: {
paddingVertical: 12,
fontSize: 16,
},
filterModalPendingTextInput: {
// height: 40,
padding: 6,
backgroundColor: 'white',
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'rgb(171, 171, 171)',
borderRadius: 8,
},
filterModalKeyboardAvoidingRoot: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
passText: {
color: 'green',
},
pendingText: {
color: 'gray',
},
resultsHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-end',
paddingHorizontal: 8,
paddingTop: 8,
flex: 0,
},
skippedText: {
color: 'blue',
},
table: {
flex: 1,
},
tableHeader: {
fontSize: 16,
fontWeight: '700',
},
tableHeaderColumn: {
alignItems: 'center',
},
tableMessageColumn: {
flex: 2.5,
paddingLeft: 8,
justifyContent: 'center',
},
tableRow: {
flexDirection: 'row',
borderBottomWidth: StyleSheet.hairlineWidth,
paddingVertical: 8,
},
tableResultColumn: {
flex: 0.5,
minWidth: 40,
paddingLeft: 8,
justifyContent: 'center',
},
tableTestNameColumn: {
flex: 2,
justifyContent: 'center',
},
summaryContainer: {
flexDirection: 'row',
paddingBottom: 8,
},
title: {
fontSize: 32,
fontWeight: '700',
},
titleContainer: {
flexDirection: 'column',
},
});
const STATUS_TEXT_STYLE_MAPPING: {[PlatformTestResultStatus]: TextStyle} = {
PASS: styles.passText,
FAIL: styles.failText,
ERROR: styles.errorText,
SKIPPED: styles.skippedText,
};