Я пытаюсь проверить следующий компонент Toast:
import React, { Component } from "react"
import PropTypes from "prop-types"
import {
Animated,
Platform,
Text,
ToastAndroid,
TouchableOpacity,
View,
} from "react-native"
import { RkStyleSheet, RkText } from "react-native-ui-kitten"
import IconFe from "react-native-vector-icons/Feather"
import { UIConstants } from "constants/appConstants"
class Toast extends Component {
constructor(props) {
super(props)
this.state = {
fadeAnimation: new Animated.Value(0),
shadowOpacity: new Animated.Value(0),
timeLeftAnimation: new Animated.Value(0),
present: false,
message: "",
dismissTimeout: null,
height: 0,
width: 0,
}
}
/* eslint-disable-next-line */
UNSAFE_componentWillReceiveProps(
{ message, error, duration, warning },
...rest
) {
if (message) {
let dismissTimeout = null
if (duration > 0) {
dismissTimeout = setTimeout(() => {
this.props.hideToast()
}, duration)
}
clearTimeout(this.state.dismissTimeout)
this.show(message, { error, warning, dismissTimeout, duration })
} else {
this.state.dismissTimeout && clearTimeout(this.state.dismissTimeout)
this.hide()
}
}
show(message, { error, warning, dismissTimeout, duration }) {
if (Platform.OS === "android") {
const androidDuration =
duration < 3000 ? ToastAndroid.SHORT : ToastAndroid.LONG
ToastAndroid.showWithGravityAndOffset(
message,
androidDuration,
ToastAndroid.TOP,
0,
UIConstants.HeaderHeight
)
} else {
this.setState(
{
present: true,
fadeAnimation: new Animated.Value(0),
shadowOpacity: new Animated.Value(0),
timeLeftAnimation: new Animated.Value(0),
message,
error,
warning,
dismissTimeout,
},
() => {
Animated.spring(this.state.fadeAnimation, {
toValue: 1,
friction: 4,
tension: 40,
}).start()
Animated.timing(this.state.shadowOpacity, { toValue: 0.5 }).start()
Animated.timing(this.state.timeLeftAnimation, {
duration,
toValue: 1,
}).start()
}
)
}
}
hide() {
if (Platform.OS === "ios") {
Animated.timing(this.state.shadowOpacity, { toValue: 0 }).start()
Animated.spring(this.state.fadeAnimation, { toValue: 0 }).start(() => {
this.setState({
present: false,
message: null,
error: false,
warning: false,
dismissTimeout: null,
})
})
}
}
dispatchHide() {
this.props.hideToast()
}
_renderIOS() {
if (!this.state.present) {
return null
}
const messageStyles = [styles.messageContainer, this.props.containerStyle]
if (this.state.error) {
messageStyles.push(styles.error, this.props.errorStyle)
} else if (this.state.warning) {
messageStyles.push(styles.warning, this.props.warningStyle)
}
return (
<Animated.View
style={[
styles.container,
{
opacity: this.state.fadeAnimation,
transform: [
{
translateY: this.state.fadeAnimation.interpolate({
inputRange: [0, 1],
outputRange: [0, this.state.height], // 0 : 150, 0.5 : 75, 1 : 0
}),
},
],
},
]}
onLayout={evt => this.setState({})}
>
<TouchableOpacity
onPress={this.dispatchHide.bind(this)}
activeOpacity={1}
>
<View style={styles.messageWrapper}>
<View
testID={"toast"}
style={messageStyles}
onLayout={evt => {
this.setState({
width: evt.nativeEvent.layout.width,
height: evt.nativeEvent.layout.height,
})
}}
>
{this.state.dismissTimeout === null ? (
<TouchableOpacity
style={{ alignItems: "flex-end" }}
onPress={this.dispatchHide.bind(this)}
>
<IconFe name={"x"} color={"white"} size={16} />
</TouchableOpacity>
) : null}
{this.props.getMessageComponent(this.state.message, {
error: this.state.error,
warning: this.state.warning,
})}
</View>
</View>
</TouchableOpacity>
</Animated.View>
)
}
render() {
if (Platform.OS === "ios") {
return this._renderIOS()
} else {
return null
}
}
}
const styles = RkStyleSheet.create(theme => {
return {
container: {
zIndex: 10000,
position: "absolute",
left: 0,
right: 0,
top: 10,
},
messageWrapper: {
justifyContent: "center",
alignItems: "center",
},
messageContainer: {
paddingHorizontal: 15,
paddingVertical: 15,
borderRadius: 15,
backgroundColor: "rgba(238,238,238,0.9)",
},
messageStyle: {
color: theme.colors.black,
fontSize: theme.fonts.sizes.small,
},
timeLeft: {
height: 2,
backgroundColor: theme.colors.primary,
top: 2,
zIndex: 10,
},
error: {
backgroundColor: "red",
},
warning: {
backgroundColor: "yellow",
},
}
})
Toast.defaultProps = {
getMessageComponent(message) {
return <RkText style={styles.messageStyle}>{message}</RkText>
},
duration: 5000,
}
Toast.propTypes = {
// containerStyle: View.propTypes.style,
message: PropTypes.string,
messageStyle: Text.propTypes.style, // eslint-disable-line react/no-unused-prop-types
error: PropTypes.bool,
// errorStyle: View.propTypes.style,
warning: PropTypes.bool,
// warningStyle: View.propTypes.style,
duration: PropTypes.number,
getMessageComponent: PropTypes.func,
}
export default Toast
При запуске этого на iOS выводится View с текстовым сообщением. В моем представлении для testID установлено значение "toast". Чтобы показать тост, мы отправляем действие приставки, которое в терминах запускает тост.
У меня есть следующий тест, который не проходит:
it("submit without username should display invalid username", async () => {
await element(by.id("letsGo")).tap()
await expect(element(by.id("toast"))).toBeVisible()
});
Я понимаю, что тест не пройден из-за автоматическогосинхронизация (https://github.com/wix/Detox/blob/master/docs/Troubleshooting.Synchronization.md) детоксикации. Когда мы нажимаем кнопку, мы отправляем действие перевоплощения. Отображается тост и устанавливается setTimeout 4 с. Теперь детокс ждет 4 с, прежде чем он проверяет, является ли элемент «тост» видимым или нетКогда кончено 4, элемент уничтожается из вида, и детокс не может его найти.
Существуют различные обходные пути для этого. Первый - отключить синхронизацию перед нажатием кнопки, а затем включить ее послеотображается тост. это работает, но для завершения теста требуется 4s +. По какой-то причине, даже если синхронизация отключена, мы все еще ждем завершения setTimeout, но на этот раз мы видим элемент.
it("submit without username should display invalid username", async () => {
await device.disableSynchronization();
await element(by.id("letsGo")).tap()
await waitFor(element(by.id("toastWTF"))).toBeVisible().withTimeout(1000)
await device.enableSynchronization();
});
Другой вариант в соответствии с документацией - отключить анимацию для тестов e2e. Я проверил это, и этоработает, но мне интересно, есть ли лучший способ?
В этом конкретном случае фактическая анимация занимает несколько сотен мс, и после этого мы отображаем представление и ожидаем его исчезновения. Детокс не нужно ждать. Настоящим пользователям, использующим приложение, тоже не нужно ждать.
Есть ли способ сделать все это немного более удобным для пользователей, пишущих тесты:)