Merge branch 'develop' into chore/ts-RoomView

This commit is contained in:
AlexAlexandre 2021-12-02 14:50:55 -03:00
commit b1d1e29d41
69 changed files with 1345 additions and 811 deletions

View File

@ -1419,6 +1419,7 @@ Array [
</Text> </Text>
</View> </View>
<View <View
accessibilityLabel="Use"
accessible={true} accessible={true}
focusable={true} focusable={true}
onClick={[Function]} onClick={[Function]}
@ -1581,6 +1582,7 @@ Array [
</Text> </Text>
</View> </View>
<View <View
accessibilityLabel="Use"
accessible={true} accessible={true}
focusable={true} focusable={true}
onClick={[Function]} onClick={[Function]}
@ -41244,6 +41246,7 @@ exports[`Storyshots Message Show a button as attachment 1`] = `
Test Button Test Button
</Text> </Text>
<View <View
accessibilityLabel="Text button"
accessible={true} accessible={true}
focusable={true} focusable={true}
onClick={[Function]} onClick={[Function]}

View File

@ -151,6 +151,8 @@ android {
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60" // See note below! missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60" // See note below!
} }
resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative" resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
} }
signingConfigs { signingConfigs {
@ -203,6 +205,10 @@ android {
dimension = "app" dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "false" buildConfigField "boolean", "IS_OFFICIAL", "false"
} }
e2e {
dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "false"
}
foss { foss {
dimension = "type" dimension = "type"
buildConfigField "boolean", "FDROID_BUILD", "true" buildConfigField "boolean", "FDROID_BUILD", "true"
@ -230,6 +236,16 @@ android {
java.srcDirs = ['src/main/java', 'src/play/java'] java.srcDirs = ['src/main/java', 'src/play/java']
manifest.srcFile 'src/play/AndroidManifest.xml' manifest.srcFile 'src/play/AndroidManifest.xml'
} }
e2ePlayDebug {
java.srcDirs = ['src/main/java', 'src/play/java']
res.srcDirs = ['src/experimental/res']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
e2ePlayRelease {
java.srcDirs = ['src/main/java', 'src/play/java']
res.srcDirs = ['src/experimental/res']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
} }
applicationVariants.all { variant -> applicationVariants.all { variant ->
@ -294,6 +310,8 @@ dependencies {
implementation "com.tencent:mmkv-static:1.2.1" implementation "com.tencent:mmkv-static:1.2.1"
implementation 'com.squareup.okhttp3:okhttp:4.9.0' implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.9.0" implementation "com.squareup.okhttp3:okhttp-urlconnection:4.9.0"
androidTestImplementation('com.wix:detox:+') { transitive = true }
androidTestImplementation 'junit:junit:4.12'
} }
// Run this once to be able to run the application with BUCK // Run this once to be able to run the application with BUCK

View File

@ -0,0 +1,32 @@
// Replace "com.example" here and below with your app's package name from the top of MainActivity.java
package chat.rocket.reactnative;
import com.wix.detox.Detox;
import com.wix.detox.config.DetoxConfig;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class DetoxTest {
@Rule
// Replace 'MainActivity' with the value of android:name entry in
// <activity> in AndroidManifest.xml
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
@Test
public void runDetoxTests() {
DetoxConfig detoxConfig = new DetoxConfig();
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
detoxConfig.rnContextLoadTimeoutSec = (chat.rocket.reactnative.BuildConfig.DEBUG ? 180 : 60);
Detox.runTests(mActivityRule, detoxConfig);
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@ -53,6 +53,10 @@ allprojects {
url("$rootDir/../node_modules/jsc-android/dist") url("$rootDir/../node_modules/jsc-android/dist")
} }
maven {
url "$rootDir/../node_modules/detox/Detox-android"
}
maven { maven {
url jitsi_url url jitsi_url
} }

View File

@ -124,7 +124,11 @@ const ActionSheet = React.memo(
const renderFooter = () => const renderFooter = () =>
data?.hasCancel ? ( data?.hasCancel ? (
<Button onPress={hide} style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]} theme={theme}> <Button
onPress={hide}
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]}
theme={theme}
accessibilityLabel={I18n.t('Cancel')}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text> <Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text>
</Button> </Button>
) : null; ) : null;

View File

@ -70,6 +70,7 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
disabled && styles.disabled, disabled && styles.disabled,
style style
]} ]}
accessibilityLabel={title}
{...otherProps}> {...otherProps}>
{loading ? ( {loading ? (
<ActivityIndicator color={textColor} /> <ActivityIndicator color={textColor} />

View File

@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
interface IHeaderButtonContainer { interface IHeaderButtonContainer {
children: JSX.Element; children: React.ReactNode;
left?: boolean; left?: boolean;
} }

View File

@ -20,7 +20,7 @@ interface IPasscodeBase {
previousPasscode?: string; previousPasscode?: string;
title: string; title: string;
subtitle?: string; subtitle?: string;
showBiometry?: string; showBiometry?: boolean;
onEndProcess: Function; onEndProcess: Function;
onError?: Function; onError?: Function;
onBiometryPress?(): void; onBiometryPress?(): void;

View File

@ -15,7 +15,7 @@ import I18n from '../../i18n';
interface IPasscodePasscodeEnter { interface IPasscodePasscodeEnter {
theme: string; theme: string;
hasBiometry: string; hasBiometry: boolean;
finishProcess: Function; finishProcess: Function;
} }

View File

@ -8,6 +8,7 @@ interface IStatus {
status: string; status: string;
size: number; size: number;
style?: StyleProp<TextStyle>; style?: StyleProp<TextStyle>;
testID?: string;
} }
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => { const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => {

View File

@ -43,7 +43,11 @@ const Content = React.memo(
content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>; content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>;
} else if (props.isEncrypted) { } else if (props.isEncrypted) {
content = ( content = (
<Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Encrypted_message')}</Text> <Text
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
accessibilityLabel={I18n.t('Encrypted_message')}>
{I18n.t('Encrypted_message')}
</Text>
); );
} else { } else {
const { baseUrl, user, onLinkPress } = useContext(MessageContext); const { baseUrl, user, onLinkPress } = useContext(MessageContext);

View File

@ -1,5 +1,5 @@
// https://github.com/RocketChat/Rocket.Chat/blob/develop/definition/ITeam.ts // https://github.com/RocketChat/Rocket.Chat/blob/develop/definition/ITeam.ts
export const TEAM_TYPE = { exports.TEAM_TYPE = {
PUBLIC: 0, PUBLIC: 0,
PRIVATE: 1 PRIVATE: 1
}; };

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Text, View } from 'react-native'; import { Text, View, ViewStyle } from 'react-native';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
@ -10,7 +10,7 @@ import { themes } from '../../constants/colors';
export { ROW_HEIGHT }; export { ROW_HEIGHT };
interface IDirectoryItemLabel { interface IDirectoryItemLabel {
text: string; text?: string;
theme: string; theme: string;
} }
@ -21,9 +21,9 @@ interface IDirectoryItem {
type: string; type: string;
onPress(): void; onPress(): void;
testID: string; testID: string;
style: any; style?: ViewStyle;
rightLabel: string; rightLabel?: string;
rid: string; rid?: string;
theme: string; theme: string;
teamMain?: boolean; teamMain?: boolean;
} }
@ -32,7 +32,7 @@ const DirectoryItemLabel = React.memo(({ text, theme }: IDirectoryItemLabel) =>
if (!text) { if (!text) {
return null; return null;
} }
return <Text style={[styles.directoryItemLabel, { color: themes[theme!].auxiliaryText }]}>{text}</Text>; return <Text style={[styles.directoryItemLabel, { color: themes[theme].auxiliaryText }]}>{text}</Text>;
}); });
const DirectoryItem = ({ const DirectoryItem = ({

View File

@ -4,7 +4,7 @@ import { KeyboardAwareScrollView, KeyboardAwareScrollViewProps } from '@codler/r
import scrollPersistTaps from '../utils/scrollPersistTaps'; import scrollPersistTaps from '../utils/scrollPersistTaps';
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps { interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
keyboardVerticalOffset: number; keyboardVerticalOffset?: number;
scrollEnabled?: boolean; scrollEnabled?: boolean;
children: React.ReactNode; children: React.ReactNode;
} }

View File

@ -46,7 +46,7 @@ interface IUserItem {
testID: string; testID: string;
onLongPress?: () => void; onLongPress?: () => void;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
icon: string; icon?: string;
theme: string; theme: string;
} }

View File

@ -63,7 +63,11 @@ export default class DirectoryOptions extends PureComponent<IDirectoryOptionsPro
} }
return ( return (
<Touch onPress={() => changeType(itemType)} style={styles.dropdownItemButton} theme={theme}> <Touch
onPress={() => changeType(itemType)}
style={styles.dropdownItemButton}
theme={theme}
accessibilityLabel={I18n.t(text)}>
<View style={styles.dropdownItemContainer}> <View style={styles.dropdownItemContainer}>
<CustomIcon style={[styles.dropdownItemIcon, { color: themes[theme].bodyText }]} size={22} name={icon} /> <CustomIcon style={[styles.dropdownItemIcon, { color: themes[theme].bodyText }]} size={22} name={icon} />
<Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text> <Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text>
@ -90,7 +94,7 @@ export default class DirectoryOptions extends PureComponent<IDirectoryOptionsPro
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
<Animated.View <Animated.View
style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: themes[theme].backgroundColor }]}> style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: themes[theme].backgroundColor }]}>
<Touch onPress={this.close} theme={theme}> <Touch onPress={this.close} theme={theme} accessibilityLabel={I18n.t('Search_by')}>
<View <View
style={[ style={[
styles.dropdownContainerHeader, styles.dropdownContainerHeader,

View File

@ -1,11 +1,12 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { FlatList, StyleSheet, Text, View } from 'react-native'; import { FlatList, StyleSheet, Text, View } from 'react-native';
import { Dispatch } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import * as List from '../containers/List';
import * as List from '../containers/List';
import Touch from '../utils/touch'; import Touch from '../utils/touch';
import database from '../lib/database'; import database from '../lib/database';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
@ -18,7 +19,6 @@ import * as HeaderButton from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { getUserSelector } from '../selectors/login';
import Navigation from '../lib/Navigation'; import Navigation from '../lib/Navigation';
import { createChannelRequest } from '../actions/createChannel'; import { createChannelRequest } from '../actions/createChannel';
import { goRoom } from '../utils/goRoom'; import { goRoom } from '../utils/goRoom';
@ -47,33 +47,54 @@ const styles = StyleSheet.create({
} }
}); });
class NewMessageView extends React.Component { interface IButton {
static navigationOptions = ({ navigation }) => ({ onPress: () => void;
testID: string;
title: string;
icon: string;
first?: boolean;
}
interface ISearch {
_id: string;
status: string;
username: string;
avatarETag: string;
outside: boolean;
rid: string;
name: string;
t: string;
search: boolean;
}
interface INewMessageViewState {
search: ISearch[];
// TODO: Refactor when migrate room
chats: any[];
permissions: boolean[];
}
interface INewMessageViewProps {
navigation: StackNavigationProp<any, 'NewMessageView'>;
create: (params: { group: boolean }) => void;
maxUsers: number;
theme: string;
isMasterDetail: boolean;
serverVersion: string;
createTeamPermission: string[];
createDirectMessagePermission: string[];
createPublicChannelPermission: string[];
createPrivateChannelPermission: string[];
createDiscussionPermission: string[];
}
class NewMessageView extends React.Component<INewMessageViewProps, INewMessageViewState> {
static navigationOptions = ({ navigation }: INewMessageViewProps): StackNavigationOptions => ({
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />, headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />,
title: I18n.t('New_Message') title: I18n.t('New_Message')
}); });
static propTypes = { constructor(props: INewMessageViewProps) {
navigation: PropTypes.object,
baseUrl: PropTypes.string,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string,
roles: PropTypes.array
}),
create: PropTypes.func,
maxUsers: PropTypes.number,
theme: PropTypes.string,
isMasterDetail: PropTypes.bool,
serverVersion: PropTypes.string,
createTeamPermission: PropTypes.array,
createDirectMessagePermission: PropTypes.array,
createPublicChannelPermission: PropTypes.array,
createPrivateChannelPermission: PropTypes.array,
createDiscussionPermission: PropTypes.array
};
constructor(props) {
super(props); super(props);
this.init(); this.init();
this.state = { this.state = {
@ -102,7 +123,7 @@ class NewMessageView extends React.Component {
this.handleHasPermission(); this.handleHasPermission();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: INewMessageViewProps) {
const { const {
createTeamPermission, createTeamPermission,
createPublicChannelPermission, createPublicChannelPermission,
@ -122,7 +143,7 @@ class NewMessageView extends React.Component {
} }
} }
onSearchChangeText(text) { onSearchChangeText(text: string) {
this.search(text); this.search(text);
} }
@ -131,7 +152,7 @@ class NewMessageView extends React.Component {
return navigation.pop(); return navigation.pop();
}; };
search = async text => { search = async (text: string) => {
const result = await RocketChat.search({ text, filterRooms: false }); const result = await RocketChat.search({ text, filterRooms: false });
this.setState({ this.setState({
search: result search: result
@ -162,7 +183,8 @@ class NewMessageView extends React.Component {
}); });
}; };
goRoom = item => { // TODO: Refactor when migrate room
goRoom = (item: any) => {
logEvent(events.NEW_MSG_CHAT_WITH_USER); logEvent(events.NEW_MSG_CHAT_WITH_USER);
const { isMasterDetail, navigation } = this.props; const { isMasterDetail, navigation } = this.props;
if (isMasterDetail) { if (isMasterDetail) {
@ -171,7 +193,7 @@ class NewMessageView extends React.Component {
goRoom({ item, isMasterDetail }); goRoom({ item, isMasterDetail });
}; };
renderButton = ({ onPress, testID, title, icon, first }) => { renderButton = ({ onPress, testID, title, icon, first }: IButton) => {
const { theme } = this.props; const { theme } = this.props;
return ( return (
<Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID} theme={theme}> <Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID} theme={theme}>
@ -218,7 +240,7 @@ class NewMessageView extends React.Component {
return ( return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}> <View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='new-message-view-search' /> <SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='new-message-view-search' />
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
{permissions[0] || permissions[1] {permissions[0] || permissions[1]
? this.renderButton({ ? this.renderButton({
@ -258,9 +280,10 @@ class NewMessageView extends React.Component {
); );
}; };
renderItem = ({ item, index }) => { // TODO: Refactor when migrate room
renderItem = ({ item, index }: { item: ISearch | any; index: number }) => {
const { search, chats } = this.state; const { search, chats } = this.state;
const { baseUrl, user, theme } = this.props; const { theme } = this.props;
let style = { borderColor: themes[theme].separatorColor }; let style = { borderColor: themes[theme].separatorColor };
if (index === 0) { if (index === 0) {
@ -277,10 +300,8 @@ class NewMessageView extends React.Component {
name={item.search ? item.name : item.fname} name={item.search ? item.name : item.fname}
username={item.search ? item.username : item.name} username={item.search ? item.username : item.name}
onPress={() => this.goRoom(item)} onPress={() => this.goRoom(item)}
baseUrl={baseUrl}
testID={`new-message-view-item-${item.name}`} testID={`new-message-view-item-${item.name}`}
style={style} style={style}
user={user}
theme={theme} theme={theme}
/> />
); );
@ -313,12 +334,10 @@ class NewMessageView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
serverVersion: state.server.version, serverVersion: state.server.version,
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
baseUrl: state.server.server,
maxUsers: state.settings.DirectMesssage_maxUsers || 1, maxUsers: state.settings.DirectMesssage_maxUsers || 1,
user: getUserSelector(state),
createTeamPermission: state.permissions['create-team'], createTeamPermission: state.permissions['create-team'],
createDirectMessagePermission: state.permissions['create-d'], createDirectMessagePermission: state.permissions['create-d'],
createPublicChannelPermission: state.permissions['create-c'], createPublicChannelPermission: state.permissions['create-c'],
@ -326,8 +345,8 @@ const mapStateToProps = state => ({
createDiscussionPermission: state.permissions['start-discussion'] createDiscussionPermission: state.permissions['start-discussion']
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({
create: params => dispatch(createChannelRequest(params)) create: (params: { group: boolean }) => dispatch(createChannelRequest(params))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewMessageView)); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewMessageView));

View File

@ -918,7 +918,6 @@ class RoomActionsView extends React.Component {
event: this.convertTeamToChannel event: this.convertTeamToChannel
}) })
} }
testID='room-actions-convert-channel-to-team'
left={() => <List.Icon name='channel-public' />} left={() => <List.Icon name='channel-public' />}
showActionIndicator showActionIndicator
/> />

View File

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Switch } from 'react-native'; import { Switch } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { StackNavigationOptions } from '@react-navigation/stack';
import Model from '@nozbe/watermelondb/Model';
import { Subscription } from 'rxjs';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
@ -16,19 +18,42 @@ import { events, logEvent } from '../utils/log';
const DEFAULT_BIOMETRY = false; const DEFAULT_BIOMETRY = false;
class ScreenLockConfigView extends React.Component { interface IServerRecords extends Model {
static navigationOptions = () => ({ autoLock?: boolean;
autoLockTime?: number;
biometry?: boolean;
}
interface IItem {
title: string;
value: number;
disabled?: boolean;
}
interface IScreenLockConfigViewProps {
theme: string;
server: string;
Force_Screen_Lock: boolean;
Force_Screen_Lock_After: number;
}
interface IScreenLockConfigViewState {
autoLock?: boolean;
autoLockTime?: number | null;
biometry?: boolean;
biometryLabel: null;
}
class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, IScreenLockConfigViewState> {
private serverRecord?: IServerRecords;
private observable?: Subscription;
static navigationOptions = (): StackNavigationOptions => ({
title: I18n.t('Screen_lock') title: I18n.t('Screen_lock')
}); });
static propTypes = { constructor(props: IScreenLockConfigViewProps) {
theme: PropTypes.string,
server: PropTypes.string,
Force_Screen_Lock: PropTypes.string,
Force_Screen_Lock_After: PropTypes.string
};
constructor(props) {
super(props); super(props);
this.state = { this.state = {
autoLock: false, autoLock: false,
@ -104,7 +129,7 @@ class ScreenLockConfigView extends React.Component {
logEvent(events.SLC_SAVE_SCREEN_LOCK); logEvent(events.SLC_SAVE_SCREEN_LOCK);
const { autoLock, autoLockTime, biometry } = this.state; const { autoLock, autoLockTime, biometry } = this.state;
const serversDB = database.servers; const serversDB = database.servers;
await serversDB.action(async () => { await serversDB.write(async () => {
await this.serverRecord?.update(record => { await this.serverRecord?.update(record => {
record.autoLock = autoLock; record.autoLock = autoLock;
record.autoLockTime = autoLockTime === null ? DEFAULT_AUTO_LOCK : autoLockTime; record.autoLockTime = autoLockTime === null ? DEFAULT_AUTO_LOCK : autoLockTime;
@ -113,7 +138,7 @@ class ScreenLockConfigView extends React.Component {
}); });
}; };
changePasscode = async ({ force }) => { changePasscode = async ({ force }: { force: boolean }) => {
logEvent(events.SLC_CHANGE_PASSCODE); logEvent(events.SLC_CHANGE_PASSCODE);
await changePasscode({ force }); await changePasscode({ force });
}; };
@ -144,12 +169,12 @@ class ScreenLockConfigView extends React.Component {
); );
}; };
isSelected = value => { isSelected = (value: number) => {
const { autoLockTime } = this.state; const { autoLockTime } = this.state;
return autoLockTime === value; return autoLockTime === value;
}; };
changeAutoLockTime = autoLockTime => { changeAutoLockTime = (autoLockTime: number) => {
logEvent(events.SLC_CHANGE_AUTOLOCK_TIME); logEvent(events.SLC_CHANGE_AUTOLOCK_TIME);
this.setState({ autoLockTime }, () => this.save()); this.setState({ autoLockTime }, () => this.save());
}; };
@ -159,7 +184,7 @@ class ScreenLockConfigView extends React.Component {
return <List.Icon name='check' color={themes[theme].tintColor} />; return <List.Icon name='check' color={themes[theme].tintColor} />;
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: IItem }) => {
const { title, value, disabled } = item; const { title, value, disabled } = item;
return ( return (
<> <>
@ -194,7 +219,7 @@ class ScreenLockConfigView extends React.Component {
if (!autoLock) { if (!autoLock) {
return null; return null;
} }
let items = this.defaultAutoLockOptions; let items: IItem[] = this.defaultAutoLockOptions;
if (Force_Screen_Lock && Force_Screen_Lock_After > 0) { if (Force_Screen_Lock && Force_Screen_Lock_After > 0) {
items = [ items = [
{ {
@ -262,7 +287,7 @@ class ScreenLockConfigView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
server: state.server.server, server: state.server.server,
Force_Screen_Lock: state.settings.Force_Screen_Lock, Force_Screen_Lock: state.settings.Force_Screen_Lock,
Force_Screen_Lock_After: state.settings.Force_Screen_Lock_After Force_Screen_Lock_After: state.settings.Force_Screen_Lock_After

View File

@ -1,19 +1,25 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
import useDeepCompareEffect from 'use-deep-compare-effect'; import useDeepCompareEffect from 'use-deep-compare-effect';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import Orientation from 'react-native-orientation-locker'; import Orientation from 'react-native-orientation-locker';
import { withTheme } from '../theme'; import { useTheme } from '../theme';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { LOCAL_AUTHENTICATE_EMITTER } from '../constants/localAuthentication'; import { LOCAL_AUTHENTICATE_EMITTER } from '../constants/localAuthentication';
import { isTablet } from '../utils/deviceInfo'; import { isTablet } from '../utils/deviceInfo';
import { PasscodeEnter } from '../containers/Passcode'; import { PasscodeEnter } from '../containers/Passcode';
const ScreenLockedView = ({ theme }) => { interface IData {
submit?: () => void;
hasBiometry?: boolean;
}
const ScreenLockedView = (): JSX.Element => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [data, setData] = useState({}); const [data, setData] = useState<IData>({});
const { theme } = useTheme();
useDeepCompareEffect(() => { useDeepCompareEffect(() => {
if (!isEmpty(data)) { if (!isEmpty(data)) {
@ -23,7 +29,7 @@ const ScreenLockedView = ({ theme }) => {
} }
}, [data]); }, [data]);
const showScreenLock = args => { const showScreenLock = (args: IData) => {
setData(args); setData(args);
}; };
@ -56,13 +62,9 @@ const ScreenLockedView = ({ theme }) => {
style={{ margin: 0 }} style={{ margin: 0 }}
animationIn='fadeIn' animationIn='fadeIn'
animationOut='fadeOut'> animationOut='fadeOut'>
<PasscodeEnter theme={theme} hasBiometry={data?.hasBiometry} finishProcess={onSubmit} /> <PasscodeEnter theme={theme} hasBiometry={!!data?.hasBiometry} finishProcess={onSubmit} />
</Modal> </Modal>
); );
}; };
ScreenLockedView.propTypes = { export default ScreenLockedView;
theme: PropTypes.string
};
export default withTheme(ScreenLockedView);

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Switch } from 'react-native'; import { Switch } from 'react-native';
import PropTypes from 'prop-types'; import { StackNavigationProp } from '@react-navigation/stack';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
@ -20,11 +20,15 @@ import {
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import { isFDroidBuild } from '../constants/environment'; import { isFDroidBuild } from '../constants/environment';
const SecurityPrivacyView = ({ navigation }) => { interface ISecurityPrivacyViewProps {
navigation: StackNavigationProp<any, 'SecurityPrivacyView'>;
}
const SecurityPrivacyView = ({ navigation }: ISecurityPrivacyViewProps): JSX.Element => {
const [crashReportState, setCrashReportState] = useState(getReportCrashErrorsValue()); const [crashReportState, setCrashReportState] = useState(getReportCrashErrorsValue());
const [analyticsEventsState, setAnalyticsEventsState] = useState(getReportAnalyticsEventsValue()); const [analyticsEventsState, setAnalyticsEventsState] = useState(getReportAnalyticsEventsValue());
const e2eEnabled = useSelector(state => state.settings.E2E_Enable); const e2eEnabled = useSelector((state: any) => state.settings.E2E_Enable);
useEffect(() => { useEffect(() => {
navigation.setOptions({ navigation.setOptions({
@ -32,21 +36,22 @@ const SecurityPrivacyView = ({ navigation }) => {
}); });
}, []); }, []);
const toggleCrashReport = value => { const toggleCrashReport = (value: boolean) => {
logEvent(events.SE_TOGGLE_CRASH_REPORT); logEvent(events.SP_TOGGLE_CRASH_REPORT);
AsyncStorage.setItem(CRASH_REPORT_KEY, JSON.stringify(value)); AsyncStorage.setItem(CRASH_REPORT_KEY, JSON.stringify(value));
setCrashReportState(value); setCrashReportState(value);
toggleCrashErrorsReport(value); toggleCrashErrorsReport(value);
}; };
const toggleAnalyticsEvents = value => { const toggleAnalyticsEvents = (value: boolean) => {
logEvent(events.SE_TOGGLE_ANALYTICS_EVENTS); logEvent(events.SP_TOGGLE_ANALYTICS_EVENTS);
AsyncStorage.setItem(ANALYTICS_EVENTS_KEY, JSON.stringify(value)); AsyncStorage.setItem(ANALYTICS_EVENTS_KEY, JSON.stringify(value));
setAnalyticsEventsState(value); setAnalyticsEventsState(value);
toggleAnalyticsEventsReport(value); toggleAnalyticsEventsReport(value);
}; };
const navigateToScreen = screen => { const navigateToScreen = (screen: 'E2EEncryptionSecurityView' | 'ScreenLockConfigView') => {
// @ts-ignore
logEvent(events[`SP_GO_${screen.replace('View', '').toUpperCase()}`]); logEvent(events[`SP_GO_${screen.replace('View', '').toUpperCase()}`]);
navigation.navigate(screen); navigation.navigate(screen);
}; };
@ -106,8 +111,4 @@ const SecurityPrivacyView = ({ navigation }) => {
); );
}; };
SecurityPrivacyView.propTypes = {
navigation: PropTypes.object
};
export default SecurityPrivacyView; export default SecurityPrivacyView;

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { FlatList, StyleSheet, Text, View } from 'react-native'; import { FlatList, StyleSheet, Text, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { RadioButton } from 'react-native-ui-lib'; import { RadioButton } from 'react-native-ui-lib';
import { RouteProp } from '@react-navigation/native';
import log from '../utils/log'; import log from '../utils/log';
import * as List from '../containers/List'; import * as List from '../containers/List';
@ -25,15 +26,58 @@ const styles = StyleSheet.create({
} }
}); });
class SelectListView extends React.Component { interface IData {
static propTypes = { rid: string;
navigation: PropTypes.object, name: string;
route: PropTypes.object, t?: string;
theme: PropTypes.string, teamMain?: boolean;
isMasterDetail: PropTypes.bool alert?: boolean;
}; }
constructor(props) { interface ISelectListViewState {
data: IData[];
dataFiltered: IData[];
isSearching: boolean;
selected: string[];
}
interface ISelectListViewProps {
navigation: StackNavigationProp<any, 'SelectListView'>;
route: RouteProp<
{
SelectView: {
data: IData[];
title: string;
infoText: string;
nextAction(selected: string[]): void;
showAlert(): void;
isSearch: boolean;
onSearch(text: string): IData[];
isRadio: boolean;
};
},
'SelectView'
>;
theme: string;
isMasterDetail: boolean;
}
class SelectListView extends React.Component<ISelectListViewProps, ISelectListViewState> {
private title: string;
private infoText: string;
private nextAction: (selected: string[]) => void;
private showAlert: () => void;
private isSearch: boolean;
private onSearch: (text: string) => IData[];
private isRadio: boolean;
constructor(props: ISelectListViewProps) {
super(props); super(props);
const data = props.route?.params?.data; const data = props.route?.params?.data;
this.title = props.route?.params?.title; this.title = props.route?.params?.title;
@ -56,7 +100,7 @@ class SelectListView extends React.Component {
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
const { selected } = this.state; const { selected } = this.state;
const options = { const options: StackNavigationOptions = {
headerTitle: I18n.t(this.title) headerTitle: I18n.t(this.title)
}; };
@ -87,7 +131,7 @@ class SelectListView extends React.Component {
return ( return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}> <View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox <SearchBox
onChangeText={text => this.search(text)} onChangeText={(text: string) => this.search(text)}
testID='select-list-view-search' testID='select-list-view-search'
onCancelPress={() => this.setState({ isSearching: false })} onCancelPress={() => this.setState({ isSearching: false })}
/> />
@ -95,7 +139,7 @@ class SelectListView extends React.Component {
); );
}; };
search = async text => { search = async (text: string) => {
try { try {
this.setState({ isSearching: true }); this.setState({ isSearching: true });
const result = await this.onSearch(text); const result = await this.onSearch(text);
@ -105,12 +149,12 @@ class SelectListView extends React.Component {
} }
}; };
isChecked = rid => { isChecked = (rid: string) => {
const { selected } = this.state; const { selected } = this.state;
return selected.includes(rid); return selected.includes(rid);
}; };
toggleItem = rid => { toggleItem = (rid: string) => {
const { selected } = this.state; const { selected } = this.state;
animateNextTransition(); animateNextTransition();
@ -126,7 +170,7 @@ class SelectListView extends React.Component {
} }
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: IData }) => {
const { theme } = this.props; const { theme } = this.props;
const { selected } = this.state; const { selected } = this.state;
@ -187,7 +231,7 @@ class SelectListView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail
}); });

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import PropTypes from 'prop-types'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q, Model } from '@nozbe/watermelondb';
import I18n from '../i18n'; import I18n from '../i18n';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
@ -12,29 +12,39 @@ import database from '../lib/database';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import * as List from '../containers/List'; import * as List from '../containers/List';
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index }); const getItemLayout = (data: any, index: number) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
const keyExtractor = item => item.id; const keyExtractor = (item: IServer) => item.id;
class SelectServerView extends React.Component { interface IServer extends Model {
static navigationOptions = () => ({ id: string;
iconURL?: string;
name?: string;
}
interface ISelectServerViewState {
servers: IServer[];
}
interface ISelectServerViewProps {
navigation: StackNavigationProp<any, 'SelectServerView'>;
server: string;
}
class SelectServerView extends React.Component<ISelectServerViewProps, ISelectServerViewState> {
static navigationOptions = (): StackNavigationOptions => ({
title: I18n.t('Select_Server') title: I18n.t('Select_Server')
}); });
static propTypes = { state = { servers: [] as IServer[] };
server: PropTypes.string,
navigation: PropTypes.object
};
state = { servers: [] };
async componentDidMount() { async componentDidMount() {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.get('servers'); const serversCollection = serversDB.get('servers');
const servers = await serversCollection.query(Q.where('rooms_updated_at', Q.notEq(null))).fetch(); const servers: IServer[] = await serversCollection.query(Q.where('rooms_updated_at', Q.notEq(null))).fetch();
this.setState({ servers }); this.setState({ servers });
} }
select = async server => { select = async (server: string) => {
const { server: currentServer, navigation } = this.props; const { server: currentServer, navigation } = this.props;
navigation.navigate('ShareListView'); navigation.navigate('ShareListView');
@ -43,7 +53,7 @@ class SelectServerView extends React.Component {
} }
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: IServer }) => {
const { server } = this.props; const { server } = this.props;
return <ServerItem onPress={() => this.select(item.id)} item={item} hasCheck={item.id === server} />; return <ServerItem onPress={() => this.select(item.id)} item={item} hasCheck={item.id === server} />;
}; };
@ -62,7 +72,6 @@ class SelectServerView extends React.Component {
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}
ListHeaderComponent={List.Separator} ListHeaderComponent={List.Separator}
ListFooterComponent={List.Separator} ListFooterComponent={List.Separator}
enableEmptySections
removeClippedSubviews removeClippedSubviews
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />
@ -71,7 +80,7 @@ class SelectServerView extends React.Component {
} }
} }
const mapStateToProps = ({ share }) => ({ const mapStateToProps = ({ share }: any) => ({
server: share.server.server server: share.server.server
}); });

View File

@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
import { ScrollView, StyleSheet, Text } from 'react-native'; import { ScrollView, StyleSheet, Text } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Orientation from 'react-native-orientation-locker'; import Orientation from 'react-native-orientation-locker';
import { RouteProp } from '@react-navigation/native';
import { loginRequest as loginRequestAction } from '../actions/login'; import { loginRequest as loginRequestAction } from '../actions/login';
import TextInput from '../containers/TextInput'; import TextInput from '../containers/TextInput';
@ -27,21 +29,27 @@ const styles = StyleSheet.create({
} }
}); });
class SetUsernameView extends React.Component { interface ISetUsernameViewState {
static navigationOptions = ({ route }) => ({ username: string;
saving: boolean;
}
interface ISetUsernameViewProps {
navigation: StackNavigationProp<any, 'SetUsernameView'>;
route: RouteProp<{ SetUsernameView: { title: string } }, 'SetUsernameView'>;
server: string;
userId: string;
loginRequest: ({ resume }: { resume: string }) => void;
token: string;
theme: string;
}
class SetUsernameView extends React.Component<ISetUsernameViewProps, ISetUsernameViewState> {
static navigationOptions = ({ route }: Pick<ISetUsernameViewProps, 'route'>): StackNavigationOptions => ({
title: route.params?.title title: route.params?.title
}); });
static propTypes = { constructor(props: ISetUsernameViewProps) {
navigation: PropTypes.object,
server: PropTypes.string,
userId: PropTypes.string,
loginRequest: PropTypes.func,
token: PropTypes.string,
theme: PropTypes.string
};
constructor(props) {
super(props); super(props);
this.state = { this.state = {
username: '', username: '',
@ -61,7 +69,7 @@ class SetUsernameView extends React.Component {
} }
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: ISetUsernameViewProps, nextState: ISetUsernameViewState) {
const { username, saving } = this.state; const { username, saving } = this.state;
const { theme } = this.props; const { theme } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
@ -88,7 +96,7 @@ class SetUsernameView extends React.Component {
try { try {
await RocketChat.saveUserProfile({ username }); await RocketChat.saveUserProfile({ username });
await loginRequest({ resume: token }); await loginRequest({ resume: token });
} catch (e) { } catch (e: any) {
showErrorAlert(e.message, I18n.t('Oops')); showErrorAlert(e.message, I18n.t('Oops'));
} }
this.setState({ saving: false }); this.setState({ saving: false });
@ -136,13 +144,13 @@ class SetUsernameView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
server: state.server.server, server: state.server.server,
token: getUserSelector(state).token token: getUserSelector(state).token
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({
loginRequest: params => dispatch(loginRequestAction(params)) loginRequest: (params: { resume: string }) => dispatch(loginRequestAction(params))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SetUsernameView)); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SetUsernameView));

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Keyboard, StyleSheet, View } from 'react-native'; import { Keyboard, StyleSheet, View } from 'react-native';
import ShareExtension from 'rn-extensions-share'; import ShareExtension from 'rn-extensions-share';
@ -8,6 +7,7 @@ import * as HeaderButton from '../../../containers/HeaderButton';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import { animateNextTransition } from '../../../utils/layoutAnimation'; import { animateNextTransition } from '../../../utils/layoutAnimation';
import { IShareListHeaderIos } from './interface';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -16,10 +16,10 @@ const styles = StyleSheet.create({
} }
}); });
const Header = React.memo(({ searching, onChangeSearchText, initSearch, cancelSearch, theme }) => { const Header = React.memo(({ searching, onChangeSearchText, initSearch, cancelSearch, theme }: IShareListHeaderIos) => {
const [text, setText] = useState(''); const [text, setText] = useState('');
const onChangeText = searchText => { const onChangeText = (searchText: string) => {
onChangeSearchText(searchText); onChangeSearchText(searchText);
setText(searchText); setText(searchText);
}; };
@ -59,12 +59,4 @@ const Header = React.memo(({ searching, onChangeSearchText, initSearch, cancelSe
); );
}); });
Header.propTypes = {
searching: PropTypes.bool,
onChangeSearchText: PropTypes.func,
initSearch: PropTypes.func,
cancelSearch: PropTypes.func,
theme: PropTypes.string
};
export default Header; export default Header;

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import TextInput from '../../../presentation/TextInput'; import TextInput from '../../../presentation/TextInput';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import { IShareListHeader } from './interface';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -24,7 +24,7 @@ const styles = StyleSheet.create({
} }
}); });
const Header = React.memo(({ searching, onChangeSearchText, theme }) => { const Header = React.memo(({ searching, onChangeSearchText, theme }: IShareListHeader) => {
const titleColorStyle = { color: themes[theme].headerTintColor }; const titleColorStyle = { color: themes[theme].headerTintColor };
const isLight = theme === 'light'; const isLight = theme === 'light';
if (searching) { if (searching) {
@ -43,10 +43,4 @@ const Header = React.memo(({ searching, onChangeSearchText, theme }) => {
return <Text style={[styles.title, titleColorStyle]}>{I18n.t('Send_to')}</Text>; return <Text style={[styles.title, titleColorStyle]}>{I18n.t('Send_to')}</Text>;
}); });
Header.propTypes = {
searching: PropTypes.bool,
onChangeSearchText: PropTypes.func,
theme: PropTypes.string
};
export default Header; export default Header;

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Header from './Header'; import Header from './Header';
import { IShareListHeader } from './interface';
const ShareListHeader = React.memo(({ searching, initSearch, cancelSearch, search, theme }) => { const ShareListHeader = React.memo(({ searching, initSearch, cancelSearch, onChangeSearchText, theme }: IShareListHeader) => {
const onSearchChangeText = text => { const onSearchChangeText = (text: string) => {
search(text.trim()); onChangeSearchText(text.trim());
}; };
return ( return (
@ -19,12 +19,4 @@ const ShareListHeader = React.memo(({ searching, initSearch, cancelSearch, searc
); );
}); });
ShareListHeader.propTypes = {
searching: PropTypes.bool,
initSearch: PropTypes.func,
cancelSearch: PropTypes.func,
search: PropTypes.func,
theme: PropTypes.string
};
export default ShareListHeader; export default ShareListHeader;

View File

@ -0,0 +1,13 @@
import { TextInputProps } from 'react-native';
type RequiredOnChangeText = Required<Pick<TextInputProps, 'onChangeText'>>;
export interface IShareListHeader {
searching: boolean;
onChangeSearchText: RequiredOnChangeText['onChangeText'];
theme: string;
initSearch?: () => void;
cancelSearch?: () => void;
}
export type IShareListHeaderIos = Required<IShareListHeader>;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { StackNavigationProp } from '@react-navigation/stack';
import { BackHandler, FlatList, Keyboard, PermissionsAndroid, ScrollView, Text, View } from 'react-native'; import { BackHandler, FlatList, Keyboard, PermissionsAndroid, ScrollView, Text, View, Rationale } from 'react-native';
import ShareExtension from 'rn-extensions-share'; import ShareExtension from 'rn-extensions-share';
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -25,24 +25,75 @@ import { sanitizeLikeString } from '../../lib/database/utils';
import styles from './styles'; import styles from './styles';
import ShareListHeader from './Header'; import ShareListHeader from './Header';
const permission = { interface IFile {
value: string;
type: string;
}
interface IAttachment {
filename: string;
description: string;
size: number;
mime: any;
path: string;
}
interface IChat {
rid: string;
t: string;
name: string;
fname: string;
blocked: boolean;
blocker: boolean;
prid: string;
uids: string[];
usernames: string[];
topic: string;
description: string;
}
interface IServerInfo {
useRealName: boolean;
}
interface IState {
searching: boolean;
searchText: string;
searchResults: IChat[];
chats: IChat[];
serversCount: number;
attachments: IAttachment[];
text: string;
loading: boolean;
serverInfo: IServerInfo;
needsPermission: boolean;
}
interface INavigationOption {
navigation: StackNavigationProp<any, 'ShareListView'>;
}
interface IShareListViewProps extends INavigationOption {
server: string;
token: string;
userId: string;
theme: string;
}
const permission: Rationale = {
title: I18n.t('Read_External_Permission'), title: I18n.t('Read_External_Permission'),
message: I18n.t('Read_External_Permission_Message') message: I18n.t('Read_External_Permission_Message'),
buttonPositive: 'Ok'
}; };
const getItemLayout = (data, index) => ({ length: data.length, offset: ROW_HEIGHT * index, index }); const getItemLayout = (data: any, index: number) => ({ length: data.length, offset: ROW_HEIGHT * index, index });
const keyExtractor = item => item.rid; const keyExtractor = (item: IChat) => item.rid;
class ShareListView extends React.Component { class ShareListView extends React.Component<IShareListViewProps, IState> {
static propTypes = { private unsubscribeFocus: (() => void) | undefined;
navigation: PropTypes.object,
server: PropTypes.string,
token: PropTypes.string,
userId: PropTypes.string,
theme: PropTypes.string
};
constructor(props) { private unsubscribeBlur: (() => void) | undefined;
constructor(props: IShareListViewProps) {
super(props); super(props);
this.state = { this.state = {
searching: false, searching: false,
@ -53,7 +104,7 @@ class ShareListView extends React.Component {
attachments: [], attachments: [],
text: '', text: '',
loading: true, loading: true,
serverInfo: null, serverInfo: {} as IServerInfo,
needsPermission: isAndroid || false needsPermission: isAndroid || false
}; };
this.setHeader(); this.setHeader();
@ -70,7 +121,7 @@ class ShareListView extends React.Component {
async componentDidMount() { async componentDidMount() {
const { server } = this.props; const { server } = this.props;
try { try {
const data = await ShareExtension.data(); const data = (await ShareExtension.data()) as IFile[];
if (isAndroid) { if (isAndroid) {
await this.askForPermission(data); await this.askForPermission(data);
} }
@ -85,7 +136,7 @@ class ShareListView extends React.Component {
size: file.size, size: file.size,
mime: mime.lookup(file.uri), mime: mime.lookup(file.uri),
path: file.uri path: file.uri
})); })) as IAttachment[];
const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, ''); const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, '');
this.setState({ this.setState({
text, text,
@ -98,14 +149,14 @@ class ShareListView extends React.Component {
this.getSubscriptions(server); this.getSubscriptions(server);
} }
UNSAFE_componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps: IShareListViewProps) {
const { server } = this.props; const { server } = this.props;
if (nextProps.server !== server) { if (nextProps.server !== server) {
this.getSubscriptions(nextProps.server); this.getSubscriptions(nextProps.server);
} }
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: IShareListViewProps, nextState: IState) {
const { searching, needsPermission } = this.state; const { searching, needsPermission } = this.state;
if (nextState.searching !== searching) { if (nextState.searching !== searching) {
return true; return true;
@ -151,7 +202,7 @@ class ShareListView extends React.Component {
searching={searching} searching={searching}
initSearch={this.initSearch} initSearch={this.initSearch}
cancelSearch={this.cancelSearch} cancelSearch={this.cancelSearch}
search={this.search} onChangeSearchText={this.search}
theme={theme} theme={theme}
/> />
) )
@ -168,7 +219,7 @@ class ShareListView extends React.Component {
) : ( ) : (
<HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' /> <HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' />
), ),
headerTitle: () => <ShareListHeader searching={searching} search={this.search} theme={theme} />, headerTitle: () => <ShareListHeader searching={searching} onChangeSearchText={this.search} theme={theme} />,
headerRight: () => headerRight: () =>
searching ? null : ( searching ? null : (
<HeaderButton.Container> <HeaderButton.Container>
@ -178,16 +229,16 @@ class ShareListView extends React.Component {
}); });
}; };
// eslint-disable-next-line react/sort-comp internalSetState = (...args: object[]) => {
internalSetState = (...args) => {
const { navigation } = this.props; const { navigation } = this.props;
if (navigation.isFocused()) { if (navigation.isFocused()) {
animateNextTransition(); animateNextTransition();
} }
// @ts-ignore
this.setState(...args); this.setState(...args);
}; };
query = async text => { query = async (text?: string) => {
const db = database.active; const db = database.active;
const defaultWhereClause = [ const defaultWhereClause = [
Q.where('archived', false), Q.where('archived', false),
@ -195,15 +246,16 @@ class ShareListView extends React.Component {
Q.experimentalSkip(0), Q.experimentalSkip(0),
Q.experimentalTake(20), Q.experimentalTake(20),
Q.experimentalSortBy('room_updated_at', Q.desc) Q.experimentalSortBy('room_updated_at', Q.desc)
]; ] as (Q.WhereDescription | Q.Skip | Q.Take | Q.SortBy | Q.Or)[];
if (text) { if (text) {
const likeString = sanitizeLikeString(text); const likeString = sanitizeLikeString(text);
defaultWhereClause.push(Q.or(Q.where('name', Q.like(`%${likeString}%`)), Q.where('fname', Q.like(`%${likeString}%`)))); defaultWhereClause.push(Q.or(Q.where('name', Q.like(`%${likeString}%`)), Q.where('fname', Q.like(`%${likeString}%`))));
} }
const data = await db const data = (await db
.get('subscriptions') .get('subscriptions')
.query(...defaultWhereClause) .query(...defaultWhereClause)
.fetch(); .fetch()) as IChat[];
return data.map(item => ({ return data.map(item => ({
rid: item.rid, rid: item.rid,
t: item.t, t: item.t,
@ -218,7 +270,7 @@ class ShareListView extends React.Component {
})); }));
}; };
getSubscriptions = async server => { getSubscriptions = async (server: string) => {
const serversDB = database.servers; const serversDB = database.servers;
if (server) { if (server) {
@ -242,7 +294,7 @@ class ShareListView extends React.Component {
} }
}; };
askForPermission = async data => { askForPermission = async (data: IFile[]) => {
const mediaIndex = data.findIndex(item => item.type === 'media'); const mediaIndex = data.findIndex(item => item.type === 'media');
if (mediaIndex !== -1) { if (mediaIndex !== -1) {
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, permission); const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, permission);
@ -255,15 +307,14 @@ class ShareListView extends React.Component {
return Promise.resolve(); return Promise.resolve();
}; };
uriToPath = uri => decodeURIComponent(isIOS ? uri.replace(/^file:\/\//, '') : uri); uriToPath = (uri: string) => decodeURIComponent(isIOS ? uri.replace(/^file:\/\//, '') : uri);
getRoomTitle = item => { getRoomTitle = (item: IChat) => {
const { serverInfo } = this.state; const { serverInfo } = this.state;
const { useRealName } = serverInfo; return ((item.prid || serverInfo?.useRealName) && item.fname) || item.name;
return ((item.prid || useRealName) && item.fname) || item.name;
}; };
shareMessage = room => { shareMessage = (room: IChat) => {
const { attachments, text, serverInfo } = this.state; const { attachments, text, serverInfo } = this.state;
const { navigation } = this.props; const { navigation } = this.props;
@ -276,7 +327,7 @@ class ShareListView extends React.Component {
}); });
}; };
search = async text => { search = async (text: string) => {
const result = await this.query(text); const result = await this.query(text);
this.internalSetState({ this.internalSetState({
searchResults: result, searchResults: result,
@ -303,7 +354,7 @@ class ShareListView extends React.Component {
return false; return false;
}; };
renderSectionHeader = header => { renderSectionHeader = (header: string) => {
const { searching } = this.state; const { searching } = this.state;
const { theme } = this.props; const { theme } = this.props;
if (searching) { if (searching) {
@ -320,10 +371,9 @@ class ShareListView extends React.Component {
); );
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: IChat }) => {
const { serverInfo } = this.state; const { serverInfo } = this.state;
const { useRealName } = serverInfo; const { theme } = this.props;
const { userId, token, server, theme } = this.props;
let description; let description;
switch (item.t) { switch (item.t) {
case 'c': case 'c':
@ -333,7 +383,7 @@ class ShareListView extends React.Component {
description = item.topic || item.description; description = item.topic || item.description;
break; break;
case 'd': case 'd':
description = useRealName ? item.name : item.fname; description = serverInfo?.useRealName ? item.name : item.fname;
break; break;
default: default:
description = item.fname; description = item.fname;
@ -341,12 +391,7 @@ class ShareListView extends React.Component {
} }
return ( return (
<DirectoryItem <DirectoryItem
user={{
id: userId,
token
}}
title={this.getRoomTitle(item)} title={this.getRoomTitle(item)}
baseUrl={server}
avatar={RocketChat.getRoomAvatar(item)} avatar={RocketChat.getRoomAvatar(item)}
description={description} description={description}
type={item.prid ? 'discussion' : item.t} type={item.prid ? 'discussion' : item.t}
@ -439,7 +484,7 @@ class ShareListView extends React.Component {
}; };
} }
const mapStateToProps = ({ share }) => ({ const mapStateToProps = ({ share }: any) => ({
userId: share.user && share.user.id, userId: share.user && share.user.id,
token: share.user && share.user.token, token: share.user && share.user.token,
server: share.server.server server: share.server.server

View File

@ -25,7 +25,7 @@ const Item = React.memo(({ left, right, text, onPress, testID, current, theme }:
style={[styles.item, current && { backgroundColor: themes[theme].borderColor }]}> style={[styles.item, current && { backgroundColor: themes[theme].borderColor }]}>
<View style={styles.itemHorizontal}>{left}</View> <View style={styles.itemHorizontal}>{left}</View>
<View style={styles.itemCenter}> <View style={styles.itemCenter}>
<Text style={[styles.itemText, { color: themes[theme].titleText }]} numberOfLines={1}> <Text style={[styles.itemText, { color: themes[theme].titleText }]} numberOfLines={1} accessibilityLabel={text}>
{text} {text}
</Text> </Text>
</View> </View>

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { StackNavigationProp } from '@react-navigation/stack';
import { FlatList, StyleSheet } from 'react-native'; import { FlatList, StyleSheet } from 'react-native';
import { Dispatch } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import I18n from '../i18n'; import I18n from '../i18n';
@ -53,23 +54,29 @@ const styles = StyleSheet.create({
} }
}); });
class StatusView extends React.Component { interface IUser {
static propTypes = { id: string;
user: PropTypes.shape({ status: string;
id: PropTypes.string, statusText: string;
status: PropTypes.string, }
statusText: PropTypes.string
}),
theme: PropTypes.string,
navigation: PropTypes.object,
isMasterDetail: PropTypes.bool,
setUser: PropTypes.func,
Accounts_AllowInvisibleStatusOption: PropTypes.bool
};
constructor(props) { interface IStatusViewState {
statusText: string;
loading: boolean;
}
interface IStatusViewProps {
navigation: StackNavigationProp<any, 'StatusView'>;
user: IUser;
theme: string;
isMasterDetail: boolean;
setUser: (user: Partial<IUser>) => void;
Accounts_AllowInvisibleStatusOption: boolean;
}
class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
constructor(props: IStatusViewProps) {
super(props); super(props);
const { statusText } = props.user; const { statusText } = props.user;
this.state = { statusText: statusText || '', loading: false }; this.state = { statusText: statusText || '', loading: false };
this.setHeader(); this.setHeader();
@ -103,7 +110,7 @@ class StatusView extends React.Component {
navigation.goBack(); navigation.goBack();
}; };
setCustomStatus = async statusText => { setCustomStatus = async (statusText: string) => {
const { user, setUser } = this.props; const { user, setUser } = this.props;
this.setState({ loading: true }); this.setState({ loading: true });
@ -147,7 +154,7 @@ class StatusView extends React.Component {
); );
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: { id: string; name: string } }) => {
const { statusText } = this.state; const { statusText } = this.state;
const { user, setUser } = this.props; const { user, setUser } = this.props;
const { id, name } = item; const { id, name } = item;
@ -155,6 +162,7 @@ class StatusView extends React.Component {
<List.Item <List.Item
title={name} title={name}
onPress={async () => { onPress={async () => {
// @ts-ignore
logEvent(events[`STATUS_${item.id.toUpperCase()}`]); logEvent(events[`STATUS_${item.id.toUpperCase()}`]);
if (user.status !== item.id) { if (user.status !== item.id) {
try { try {
@ -162,7 +170,7 @@ class StatusView extends React.Component {
if (result.success) { if (result.success) {
setUser({ status: item.id }); setUser({ status: item.id });
} }
} catch (e) { } catch (e: any) {
showErrorAlert(I18n.t(e.data.errorType)); showErrorAlert(I18n.t(e.data.errorType));
logEvent(events.SET_STATUS_FAIL); logEvent(events.SET_STATUS_FAIL);
log(e); log(e);
@ -197,14 +205,14 @@ class StatusView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
user: getUserSelector(state), user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
Accounts_AllowInvisibleStatusOption: state.settings.Accounts_AllowInvisibleStatusOption ?? true Accounts_AllowInvisibleStatusOption: state.settings.Accounts_AllowInvisibleStatusOption ?? true
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({
setUser: user => dispatch(setUserAction(user)) setUser: (user: IUser) => dispatch(setUserAction(user))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(StatusView)); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(StatusView));

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { Alert, FlatList, Keyboard } from 'react-native'; import { Alert, FlatList, Keyboard } from 'react-native';
import PropTypes from 'prop-types'; import { RouteProp } from '@react-navigation/native';
import { Dispatch } from 'redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { HeaderBackButton } from '@react-navigation/stack'; import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import RoomHeader from '../containers/RoomHeader'; import RoomHeader from '../containers/RoomHeader';
@ -38,35 +39,74 @@ const PERMISSION_EDIT_TEAM_CHANNEL = 'edit-team-channel';
const PERMISSION_REMOVE_TEAM_CHANNEL = 'remove-team-channel'; const PERMISSION_REMOVE_TEAM_CHANNEL = 'remove-team-channel';
const PERMISSION_ADD_TEAM_CHANNEL = 'add-team-channel'; const PERMISSION_ADD_TEAM_CHANNEL = 'add-team-channel';
const getItemLayout = (data, index) => ({ const getItemLayout = (data: IItem[] | null | undefined, index: number) => ({
length: data.length, length: data?.length || 0,
offset: ROW_HEIGHT * index, offset: ROW_HEIGHT * index,
index index
}); });
const keyExtractor = item => item._id; const keyExtractor = (item: IItem) => item._id;
class TeamChannelsView extends React.Component { // This interface comes from request RocketChat.getTeamListRoom
static propTypes = { interface IItem {
route: PropTypes.object, _id: string;
navigation: PropTypes.object, fname: string;
isMasterDetail: PropTypes.bool, customFields: object;
insets: PropTypes.object, broadcast: boolean;
theme: PropTypes.string, encrypted: boolean;
useRealName: PropTypes.bool, name: string;
width: PropTypes.number, t: string;
StoreLastMessage: PropTypes.bool, msgs: number;
addTeamChannelPermission: PropTypes.array, usersCount: number;
editTeamChannelPermission: PropTypes.array, u: { _id: string; name: string };
removeTeamChannelPermission: PropTypes.array, ts: string;
deleteCPermission: PropTypes.array, ro: boolean;
deletePPermission: PropTypes.array, teamId: string;
showActionSheet: PropTypes.func, default: boolean;
deleteRoom: PropTypes.func, sysMes: boolean;
showAvatar: PropTypes.bool, _updatedAt: string;
displayMode: PropTypes.string teamDefault: boolean;
}; }
constructor(props) { interface ITeamChannelsViewState {
loading: boolean;
loadingMore: boolean;
data: IItem[];
isSearching: boolean;
searchText: string | null;
search: IItem[];
end: boolean;
showCreate: boolean;
}
interface ITeamChannelsViewProps {
route: RouteProp<{ TeamChannelsView: { teamId: string } }, 'TeamChannelsView'>;
navigation: StackNavigationProp<any, 'TeamChannelsView'>;
isMasterDetail: boolean;
insets: EdgeInsets;
theme: string;
useRealName: boolean;
width: number;
StoreLastMessage: boolean;
addTeamChannelPermission: string[];
editTeamChannelPermission: string[];
removeTeamChannelPermission: string[];
deleteCPermission: string[];
deletePPermission: string[];
showActionSheet: (options: any) => void;
deleteRoom: (rid: string, t: string) => void;
showAvatar: boolean;
displayMode: string;
}
class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChannelsViewState> {
private teamId: string;
// TODO: Refactor when migrate room
private teamChannels: any;
// TODO: Refactor when migrate room
private team: any;
constructor(props: ITeamChannelsViewProps) {
super(props); super(props);
this.teamId = props.route.params?.teamId; this.teamId = props.route.params?.teamId;
this.state = { this.state = {
@ -95,7 +135,7 @@ class TeamChannelsView extends React.Component {
try { try {
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
this.teamChannels = await subCollection.query(Q.where('team_id', Q.eq(this.teamId))); this.teamChannels = await subCollection.query(Q.where('team_id', Q.eq(this.teamId)));
this.team = this.teamChannels?.find(channel => channel.teamMain); this.team = this.teamChannels?.find((channel: any) => channel.teamMain);
this.setHeader(); this.setHeader();
if (!this.team) { if (!this.team) {
@ -140,7 +180,7 @@ class TeamChannelsView extends React.Component {
loading: false, loading: false,
loadingMore: false, loadingMore: false,
end: result.rooms.length < API_FETCH_COUNT end: result.rooms.length < API_FETCH_COUNT
}; } as ITeamChannelsViewState;
if (isSearching) { if (isSearching) {
newState.search = [...search, ...result.rooms]; newState.search = [...search, ...result.rooms];
@ -170,7 +210,7 @@ class TeamChannelsView extends React.Component {
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 }); const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 });
if (isSearching) { if (isSearching) {
const options = { const options: StackNavigationOptions = {
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerLeft: () => ( headerLeft: () => (
<HeaderButton.Container left> <HeaderButton.Container left>
@ -187,7 +227,7 @@ class TeamChannelsView extends React.Component {
return navigation.setOptions(options); return navigation.setOptions(options);
} }
const options = { const options: StackNavigationOptions = {
headerShown: true, headerShown: true,
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: { headerTitleContainerStyle: {
@ -232,7 +272,7 @@ class TeamChannelsView extends React.Component {
this.setState({ isSearching: true }, () => this.setHeader()); this.setState({ isSearching: true }, () => this.setHeader());
}; };
onSearchChangeText = debounce(searchText => { onSearchChangeText = debounce((searchText: string) => {
this.setState( this.setState(
{ {
searchText, searchText,
@ -270,7 +310,7 @@ class TeamChannelsView extends React.Component {
); );
}; };
goRoomActionsView = screen => { goRoomActionsView = (screen: string) => {
logEvent(events.TC_GO_ACTIONS); logEvent(events.TC_GO_ACTIONS);
const { team } = this; const { team } = this;
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
@ -293,12 +333,12 @@ class TeamChannelsView extends React.Component {
} }
}; };
getRoomTitle = item => RocketChat.getRoomTitle(item); getRoomTitle = (item: IItem) => RocketChat.getRoomTitle(item);
getRoomAvatar = item => RocketChat.getRoomAvatar(item); getRoomAvatar = (item: IItem) => RocketChat.getRoomAvatar(item);
onPressItem = debounce( onPressItem = debounce(
async item => { async (item: IItem) => {
logEvent(events.TC_GO_ROOM); logEvent(events.TC_GO_ROOM);
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
try { try {
@ -314,7 +354,7 @@ class TeamChannelsView extends React.Component {
navigation.pop(); navigation.pop();
} }
goRoom({ item: params, isMasterDetail, navigationMethod: navigation.push }); goRoom({ item: params, isMasterDetail, navigationMethod: navigation.push });
} catch (e) { } catch (e: any) {
if (e.data.error === 'not-allowed') { if (e.data.error === 'not-allowed') {
showErrorAlert(I18n.t('error-not-allowed')); showErrorAlert(I18n.t('error-not-allowed'));
} else { } else {
@ -326,7 +366,7 @@ class TeamChannelsView extends React.Component {
true true
); );
toggleAutoJoin = async item => { toggleAutoJoin = async (item: IItem) => {
logEvent(events.TC_TOGGLE_AUTOJOIN); logEvent(events.TC_TOGGLE_AUTOJOIN);
try { try {
const { data } = this.state; const { data } = this.state;
@ -346,7 +386,7 @@ class TeamChannelsView extends React.Component {
} }
}; };
remove = item => { remove = (item: IItem) => {
Alert.alert( Alert.alert(
I18n.t('Confirmation'), I18n.t('Confirmation'),
I18n.t('Remove_Team_Room_Warning'), I18n.t('Remove_Team_Room_Warning'),
@ -365,7 +405,7 @@ class TeamChannelsView extends React.Component {
); );
}; };
removeRoom = async item => { removeRoom = async (item: IItem) => {
logEvent(events.TC_DELETE_ROOM); logEvent(events.TC_DELETE_ROOM);
try { try {
const { data } = this.state; const { data } = this.state;
@ -380,7 +420,7 @@ class TeamChannelsView extends React.Component {
} }
}; };
delete = item => { delete = (item: IItem) => {
logEvent(events.TC_DELETE_ROOM); logEvent(events.TC_DELETE_ROOM);
const { deleteRoom } = this.props; const { deleteRoom } = this.props;
@ -402,7 +442,7 @@ class TeamChannelsView extends React.Component {
); );
}; };
showChannelActions = async item => { showChannelActions = async (item: IItem) => {
logEvent(events.ROOM_SHOW_BOX_ACTIONS); logEvent(events.ROOM_SHOW_BOX_ACTIONS);
const { const {
showActionSheet, showActionSheet,
@ -464,7 +504,7 @@ class TeamChannelsView extends React.Component {
showActionSheet({ options }); showActionSheet({ options });
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: IItem }) => {
const { StoreLastMessage, useRealName, theme, width, showAvatar, displayMode } = this.props; const { StoreLastMessage, useRealName, theme, width, showAvatar, displayMode } = this.props;
return ( return (
<RoomItem <RoomItem
@ -534,7 +574,7 @@ class TeamChannelsView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
baseUrl: state.server.server, baseUrl: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
useRealName: state.settings.UI_Use_Real_Name, useRealName: state.settings.UI_Use_Real_Name,
@ -549,8 +589,8 @@ const mapStateToProps = state => ({
displayMode: state.sortPreferences.displayMode displayMode: state.sortPreferences.displayMode
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({
deleteRoom: (rid, t) => dispatch(deleteRoomAction(rid, t)) deleteRoom: (rid: string, t: string) => dispatch(deleteRoomAction(rid, t))
}); });
export default connect( export default connect(

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { StackNavigationOptions } from '@react-navigation/stack';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
@ -51,18 +51,29 @@ if (supportSystemTheme()) {
const themeGroup = THEMES.filter(item => item.group === THEME_GROUP); const themeGroup = THEMES.filter(item => item.group === THEME_GROUP);
const darkGroup = THEMES.filter(item => item.group === DARK_GROUP); const darkGroup = THEMES.filter(item => item.group === DARK_GROUP);
class ThemeView extends React.Component { interface ITheme {
static navigationOptions = () => ({ label: string;
value: string;
group: string;
}
interface IThemePreference {
currentTheme?: string;
darkLevel?: string;
}
interface IThemeViewProps {
theme: string;
themePreferences: IThemePreference;
setTheme(newTheme?: IThemePreference): void;
}
class ThemeView extends React.Component<IThemeViewProps> {
static navigationOptions = (): StackNavigationOptions => ({
title: I18n.t('Theme') title: I18n.t('Theme')
}); });
static propTypes = { isSelected = (item: ITheme) => {
theme: PropTypes.string,
themePreferences: PropTypes.object,
setTheme: PropTypes.func
};
isSelected = item => {
const { themePreferences } = this.props; const { themePreferences } = this.props;
const { group } = item; const { group } = item;
const { darkLevel, currentTheme } = themePreferences; const { darkLevel, currentTheme } = themePreferences;
@ -74,11 +85,11 @@ class ThemeView extends React.Component {
} }
}; };
onClick = item => { onClick = (item: ITheme) => {
const { themePreferences } = this.props; const { themePreferences } = this.props;
const { darkLevel, currentTheme } = themePreferences; const { darkLevel, currentTheme } = themePreferences;
const { value, group } = item; const { value, group } = item;
let changes = {}; let changes: IThemePreference = {};
if (group === THEME_GROUP && currentTheme !== value) { if (group === THEME_GROUP && currentTheme !== value) {
logEvent(events.THEME_SET_THEME_GROUP, { theme_group: value }); logEvent(events.THEME_SET_THEME_GROUP, { theme_group: value });
changes = { currentTheme: value }; changes = { currentTheme: value };
@ -90,7 +101,7 @@ class ThemeView extends React.Component {
this.setTheme(changes); this.setTheme(changes);
}; };
setTheme = async theme => { setTheme = async (theme: IThemePreference) => {
const { setTheme, themePreferences } = this.props; const { setTheme, themePreferences } = this.props;
const newTheme = { ...themePreferences, ...theme }; const newTheme = { ...themePreferences, ...theme };
setTheme(newTheme); setTheme(newTheme);
@ -102,7 +113,7 @@ class ThemeView extends React.Component {
return <List.Icon name='check' color={themes[theme].tintColor} />; return <List.Icon name='check' color={themes[theme].tintColor} />;
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: ITheme }) => {
const { label, value } = item; const { label, value } = item;
return ( return (
<> <>

View File

@ -1,10 +1,33 @@
const { exec } = require('child_process');
const data = require('../data'); const data = require('../data');
const platformTypes = {
android: {
// Android types
alertButtonType: 'android.widget.Button',
scrollViewType: 'android.widget.ScrollView',
textInputType: 'android.widget.EditText',
textMatcher: 'text'
},
ios: {
// iOS types
alertButtonType: '_UIAlertControllerActionView',
scrollViewType: 'UIScrollView',
textInputType: '_UIAlertControllerTextField',
textMatcher: 'label'
}
};
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}
async function navigateToWorkspace(server = data.server) { async function navigateToWorkspace(server = data.server) {
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);
await element(by.id('new-server-view-input')).typeText(`${server}\n`); await element(by.id('new-server-view-input')).replaceText(`${server}`);
await element(by.id('new-server-view-input')).tapReturnKey();
await waitFor(element(by.id('workspace-view'))) await waitFor(element(by.id('workspace-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);
@ -41,6 +64,8 @@ async function login(username, password) {
} }
async function logout() { async function logout() {
const deviceType = device.getPlatform();
const { scrollViewType, textMatcher } = platformTypes[deviceType];
await element(by.id('rooms-list-view-sidebar')).tap(); await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar-view'))) await waitFor(element(by.id('sidebar-view')))
.toBeVisible() .toBeVisible()
@ -52,14 +77,14 @@ async function logout() {
await waitFor(element(by.id('settings-view'))) await waitFor(element(by.id('settings-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('bottom'); await element(by.type(scrollViewType)).atIndex(1).scrollTo('bottom');
await element(by.id('settings-logout')).tap(); await element(by.id('settings-logout')).tap();
const logoutAlertMessage = 'You will be logged out of this application.'; const logoutAlertMessage = 'You will be logged out of this application.';
await waitFor(element(by.text(logoutAlertMessage)).atIndex(0)) await waitFor(element(by[textMatcher](logoutAlertMessage)).atIndex(0))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist(); await expect(element(by[textMatcher](logoutAlertMessage)).atIndex(0)).toExist();
await element(by.text('Logout')).tap(); await element(by[textMatcher]('Logout')).atIndex(0).tap();
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
@ -67,66 +92,73 @@ async function logout() {
} }
async function mockMessage(message, isThread = false) { async function mockMessage(message, isThread = false) {
const deviceType = device.getPlatform();
const { textMatcher } = platformTypes[deviceType];
const input = isThread ? 'messagebox-input-thread' : 'messagebox-input'; const input = isThread ? 'messagebox-input-thread' : 'messagebox-input';
await element(by.id(input)).tap(); await element(by.id(input)).replaceText(`${data.random}${message}`);
await element(by.id(input)).typeText(`${data.random}${message}`); await sleep(300);
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by.label(`${data.random}${message}`))) await waitFor(element(by[textMatcher](`${data.random}${message}`)))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await expect(element(by.label(`${data.random}${message}`))).toExist(); await element(by[textMatcher](`${data.random}${message}`))
await element(by.label(`${data.random}${message}`))
.atIndex(0) .atIndex(0)
.tap(); .tap();
} }
async function starMessage(message) { async function starMessage(message) {
const deviceType = device.getPlatform();
const { textMatcher } = platformTypes[deviceType];
const messageLabel = `${data.random}${message}`; const messageLabel = `${data.random}${message}`;
await element(by.label(messageLabel)).atIndex(0).longPress(); await element(by[textMatcher](messageLabel)).atIndex(0).longPress();
await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet'))).toExist();
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Star')).atIndex(0).tap(); await element(by[textMatcher]('Star')).atIndex(0).tap();
await waitFor(element(by.id('action-sheet'))) await waitFor(element(by.id('action-sheet')))
.not.toExist() .not.toExist()
.withTimeout(5000); .withTimeout(5000);
} }
async function pinMessage(message) { async function pinMessage(message) {
const deviceType = device.getPlatform();
const { textMatcher } = platformTypes[deviceType];
const messageLabel = `${data.random}${message}`; const messageLabel = `${data.random}${message}`;
await waitFor(element(by.label(messageLabel)).atIndex(0)).toExist(); await waitFor(element(by[textMatcher](messageLabel)).atIndex(0)).toExist();
await element(by.label(messageLabel)).atIndex(0).longPress(); await element(by[textMatcher](messageLabel)).atIndex(0).longPress();
await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet'))).toExist();
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Pin')).atIndex(0).tap(); await element(by[textMatcher]('Pin')).atIndex(0).tap();
await waitFor(element(by.id('action-sheet'))) await waitFor(element(by.id('action-sheet')))
.not.toExist() .not.toExist()
.withTimeout(5000); .withTimeout(5000);
} }
async function dismissReviewNag() { async function dismissReviewNag() {
await waitFor(element(by.text('Are you enjoying this app?'))) const deviceType = device.getPlatform();
const { textMatcher } = platformTypes[deviceType];
await waitFor(element(by[textMatcher]('Are you enjoying this app?')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await element(by.label('No').and(by.type('_UIAlertControllerActionView'))).tap(); // Tap `no` on ask for review alert await element(by[textMatcher]('No')).atIndex(0).tap(); // Tap `no` on ask for review alert
} }
async function tapBack() { async function tapBack() {
await element(by.id('header-back')).atIndex(0).tap(); await element(by.id('header-back')).atIndex(0).tap();
} }
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}
async function searchRoom(room) { async function searchRoom(room) {
await waitFor(element(by.id('rooms-list-view')))
.toBeVisible()
.withTimeout(30000);
await element(by.id('rooms-list-view-search')).tap(); await element(by.id('rooms-list-view-search')).tap();
await expect(element(by.id('rooms-list-view-search-input'))).toExist(); await expect(element(by.id('rooms-list-view-search-input'))).toExist();
await waitFor(element(by.id('rooms-list-view-search-input'))) await waitFor(element(by.id('rooms-list-view-search-input')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('rooms-list-view-search-input')).typeText(room); await sleep(300);
await element(by.id('rooms-list-view-search-input')).replaceText(room);
await sleep(300); await sleep(300);
await waitFor(element(by.id(`rooms-list-view-item-${room}`))) await waitFor(element(by.id(`rooms-list-view-item-${room}`)))
.toBeVisible() .toBeVisible()
@ -162,6 +194,29 @@ const checkServer = async server => {
await element(by.id('sidebar-close-drawer')).tap(); await element(by.id('sidebar-close-drawer')).tap();
}; };
function runCommand(command) {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(new Error(`exec error: ${stderr}`));
return;
}
resolve();
});
});
}
async function prepareAndroid() {
if (device.getPlatform() !== 'android') {
return;
}
await runCommand('adb shell settings put secure spell_checker_enabled 0');
await runCommand('adb shell settings put secure autofill_service null');
await runCommand('adb shell settings put global window_animation_scale 0.0');
await runCommand('adb shell settings put global transition_animation_scale 0.0');
await runCommand('adb shell settings put global animator_duration_scale 0.0');
}
module.exports = { module.exports = {
navigateToWorkspace, navigateToWorkspace,
navigateToLogin, navigateToLogin,
@ -176,5 +231,7 @@ module.exports = {
sleep, sleep,
searchRoom, searchRoom,
tryTapping, tryTapping,
checkServer checkServer,
platformTypes,
prepareAndroid
}; };

View File

@ -1,4 +1,5 @@
const { navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout } = require('../../helpers/app'); const { navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout, platformTypes } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
const testuser = data.users.regular; const testuser = data.users.regular;
@ -17,8 +18,9 @@ const checkServer = async server => {
}; };
const checkBanner = async () => { const checkBanner = async () => {
await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))) // TODO: Assert 'Save Your Encryption Password'
.toBeVisible() await waitFor(element(by.id('listheader-encryption')))
.toExist()
.withTimeout(10000); .withTimeout(10000);
}; };
@ -58,9 +60,13 @@ async function navigateSecurityPrivacy() {
describe('E2E Encryption', () => { describe('E2E Encryption', () => {
const room = `encrypted${data.random}`; const room = `encrypted${data.random}`;
const newPassword = 'abc'; const newPassword = 'abc';
let alertButtonType;
let scrollViewType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, scrollViewType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
}); });
@ -187,11 +193,11 @@ describe('E2E Encryption', () => {
it('should change password', async () => { it('should change password', async () => {
await element(by.id('e2e-encryption-security-view-password')).typeText(newPassword); await element(by.id('e2e-encryption-security-view-password')).typeText(newPassword);
await element(by.id('e2e-encryption-security-view-change-password')).tap(); await element(by.id('e2e-encryption-security-view-change-password')).tap();
await waitFor(element(by.text('Are you sure?'))) await waitFor(element(by[textMatcher]('Are you sure?')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.text("Make sure you've saved it carefully somewhere else."))).toExist(); await expect(element(by[textMatcher]("Make sure you've saved it carefully somewhere else."))).toExist();
await element(by.label('Yes, change it').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by[textMatcher]('Yes, change it')).atIndex(0).tap();
await waitForToast(); await waitForToast();
}); });
@ -216,7 +222,7 @@ describe('E2E Encryption', () => {
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await navigateToRoom(room); await navigateToRoom(room);
await waitFor(element(by.label(`${data.random}message`)).atIndex(0)) await waitFor(element(by[textMatcher](`${data.random}message`)).atIndex(0))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
}); });
@ -230,7 +236,7 @@ describe('E2E Encryption', () => {
await navigateToLogin(); await navigateToLogin();
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
await navigateToRoom(room); await navigateToRoom(room);
await waitFor(element(by.label(`${data.random}message`)).atIndex(0)) await waitFor(element(by[textMatcher](`${data.random}message`)).atIndex(0))
.not.toExist() .not.toExist()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.label('Encrypted message')).atIndex(0)).toExist(); await expect(element(by.label('Encrypted message')).atIndex(0)).toExist();
@ -241,10 +247,11 @@ describe('E2E Encryption', () => {
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Enter Your E2E Password')))) // TODO: assert 'Enter Your E2E Password'
await waitFor(element(by.id('listheader-encryption')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('listheader-encryption').withDescendant(by.label('Enter Your E2E Password'))).tap(); await element(by.id('listheader-encryption')).tap();
await waitFor(element(by.id('e2e-enter-your-password-view'))) await waitFor(element(by.id('e2e-enter-your-password-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
@ -254,43 +261,52 @@ describe('E2E Encryption', () => {
.not.toExist() .not.toExist()
.withTimeout(10000); .withTimeout(10000);
await navigateToRoom(room); await navigateToRoom(room);
await waitFor(element(by.label(`${data.random}message`)).atIndex(0)) await waitFor(element(by[textMatcher](`${data.random}message`)).atIndex(0))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
}); });
}); });
describe('Reset E2E key', () => { describe('Reset E2E key', () => {
it('should reset e2e key', async () => { before(async () => {
await tapBack(); await tapBack();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
});
it('should reset e2e key', async () => {
// FIXME: too flaky on Android for now... let's fix it later
// It's also flaky on iOS, but it works from time to time
if (device.getPlatform() === 'android') {
return;
}
await navigateSecurityPrivacy(); await navigateSecurityPrivacy();
await element(by.id('security-privacy-view-e2e-encryption')).tap(); await element(by.id('security-privacy-view-e2e-encryption')).tap();
await waitFor(element(by.id('e2e-encryption-security-view'))) await waitFor(element(by.id('e2e-encryption-security-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key'))).tap(); await element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key'))).tap();
await waitFor(element(by.text('Are you sure?'))) await waitFor(element(by[textMatcher]('Are you sure?')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.text("You're going to be logged out."))).toExist(); await expect(element(by[textMatcher]("You're going to be logged out."))).toExist();
await element(by.label('Yes, reset it').and(by.type('UILabel'))).tap(); await element(by[textMatcher]('Yes, reset it').and(by.type(alertButtonType))).tap();
await sleep(2000); await sleep(2000);
await waitFor(element(by[textMatcher]("You've been logged out by the server. Please log in again.")))
.toExist()
.withTimeout(20000);
await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('workspace-view'))) await waitFor(element(by.id('workspace-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
await waitFor(element(by.text("You've been logged out by the server. Please log in again.")))
.toExist()
.withTimeout(2000);
await element(by.label('OK').and(by.type('_UIAlertControllerActionView'))).tap();
await element(by.id('workspace-view-login')).tap(); await element(by.id('workspace-view-login')).tap();
await waitFor(element(by.id('login-view'))) await waitFor(element(by.id('login-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))) // TODO: assert 'Save Your Encryption Password'
await waitFor(element(by.id('listheader-encryption')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
}); });
@ -298,6 +314,14 @@ describe('E2E Encryption', () => {
}); });
describe('Persist Banner', () => { describe('Persist Banner', () => {
before(async () => {
// reinstall the app because of one flaky test above
if (device.getPlatform() === 'android') {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin();
await login(testuser.username, testuser.password);
}
});
it('check save banner', async () => { it('check save banner', async () => {
await checkServer(data.server); await checkServer(data.server);
await checkBanner(); await checkBanner();
@ -315,7 +339,8 @@ describe('E2E Encryption', () => {
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);
await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}`);
await element(by.id('new-server-view-input')).tapReturnKey();
await waitFor(element(by.id('workspace-view'))) await waitFor(element(by.id('workspace-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);
@ -328,7 +353,7 @@ describe('E2E Encryption', () => {
await element(by.id('register-view-name')).replaceText(data.registeringUser.username); await element(by.id('register-view-name')).replaceText(data.registeringUser.username);
await element(by.id('register-view-username')).replaceText(data.registeringUser.username); await element(by.id('register-view-username')).replaceText(data.registeringUser.username);
await element(by.id('register-view-email')).replaceText(data.registeringUser.email); await element(by.id('register-view-email')).replaceText(data.registeringUser.email);
await element(by.id('register-view-password')).typeText(data.registeringUser.password); await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
await element(by.id('register-view-submit')).tap(); await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()

View File

@ -1,15 +1,17 @@
// const OTP = require('otp.js'); // const OTP = require('otp.js');
// const GA = OTP.googleAuthenticator; // const GA = OTP.googleAuthenticator;
const { navigateToLogin, login, mockMessage, tapBack, searchRoom } = require('../../helpers/app'); const { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
const testuser = data.users.regular; const testuser = data.users.regular;
const otheruser = data.users.alternate; const otheruser = data.users.alternate;
describe('Broadcast room', () => { describe('Broadcast room', () => {
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
}); });
@ -101,7 +103,7 @@ describe('Broadcast room', () => {
}); });
it('should have the message created earlier', async () => { it('should have the message created earlier', async () => {
await waitFor(element(by.label(`${data.random}message`))) await waitFor(element(by[textMatcher](`${data.random}message`)))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
}); });

View File

@ -1,4 +1,4 @@
const { navigateToLogin, login, sleep } = require('../../helpers/app'); const { navigateToLogin, login, sleep, platformTypes } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
const profileChangeUser = data.users.profileChanges; const profileChangeUser = data.users.profileChanges;
@ -14,8 +14,14 @@ async function waitForToast() {
} }
describe('Profile screen', () => { describe('Profile screen', () => {
let textInputType;
let scrollViewType;
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textInputType, scrollViewType, alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(profileChangeUser.username, profileChangeUser.password); await login(profileChangeUser.username, profileChangeUser.password);
await element(by.id('rooms-list-view-sidebar')).tap(); await element(by.id('rooms-list-view-sidebar')).tap();
@ -92,8 +98,8 @@ describe('Profile screen', () => {
describe('Usage', () => { describe('Usage', () => {
it('should change name and username', async () => { it('should change name and username', async () => {
await element(by.id('profile-view-name')).replaceText(`${profileChangeUser.username}new`); await element(by.id('profile-view-name')).replaceText(`${profileChangeUser.username}new`);
await element(by.id('profile-view-username')).typeText(`${profileChangeUser.username}new`); await element(by.id('profile-view-username')).replaceText(`${profileChangeUser.username}new`);
await element(by.type('UIScrollView')).atIndex(1).swipe('up'); await element(by.type(scrollViewType)).atIndex(1).swipe('up');
await element(by.id('profile-view-submit')).tap(); await element(by.id('profile-view-submit')).tap();
await waitForToast(); await waitForToast();
}); });
@ -102,12 +108,13 @@ describe('Profile screen', () => {
await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`); await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`);
await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`); await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`);
await element(by.id('profile-view-submit')).tap(); await element(by.id('profile-view-submit')).tap();
await element(by.type('_UIAlertControllerTextField')).typeText(`${profileChangeUser.password}\n`); await element(by.type(textInputType)).replaceText(`${profileChangeUser.password}`);
await element(by[textMatcher]('Save').and(by.type(alertButtonType))).tap();
await waitForToast(); await waitForToast();
}); });
it('should reset avatar', async () => { it('should reset avatar', async () => {
await element(by.type('UIScrollView')).atIndex(1).swipe('up'); await element(by.type(scrollViewType)).atIndex(1).swipe('up');
await element(by.id('profile-view-reset-avatar')).tap(); await element(by.id('profile-view-reset-avatar')).tap();
await waitForToast(); await waitForToast();
}); });

View File

@ -1,11 +1,15 @@
const { navigateToLogin, login } = require('../../helpers/app'); const { navigateToLogin, login, platformTypes } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
const testuser = data.users.regular; const testuser = data.users.regular;
describe('Settings screen', () => { describe('Settings screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
@ -72,10 +76,10 @@ describe('Settings screen', () => {
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('settings-view-clear-cache')).tap(); await element(by.id('settings-view-clear-cache')).tap();
await waitFor(element(by.text('This will clear all your offline data.'))) await waitFor(element(by[textMatcher]('This will clear all your offline data.')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.label('Clear').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by[textMatcher]('Clear').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);

View File

@ -1,5 +1,5 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, mockMessage, tapBack, searchRoom } = require('../../helpers/app'); const { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } = require('../../helpers/app');
const testuser = data.users.regular; const testuser = data.users.regular;
const room = data.channels.detoxpublic.name; const room = data.channels.detoxpublic.name;
@ -7,21 +7,24 @@ const room = data.channels.detoxpublic.name;
async function navigateToRoom() { async function navigateToRoom() {
await searchRoom(room); await searchRoom(room);
await element(by.id(`rooms-list-view-item-${room}`)).tap(); await element(by.id(`rooms-list-view-item-${room}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')).atIndex(0))
.toBeVisible() .toExist()
.withTimeout(5000); .withTimeout(5000);
} }
async function navigateToRoomActions() { async function navigateToRoomActions() {
await element(by.id('room-header')).tap(); await element(by.id(`room-view-title-${room}`)).tap();
await waitFor(element(by.id('room-actions-view'))) await waitFor(element(by.id('room-actions-view')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
} }
describe('Join public room', () => { describe('Join public room', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
await navigateToRoom(); await navigateToRoom();
@ -32,10 +35,6 @@ describe('Join public room', () => {
await expect(element(by.id('room-view'))).toBeVisible(); await expect(element(by.id('room-view'))).toBeVisible();
}); });
// it('should have messages list', async() => {
// await expect(element(by.id('room-view-messages'))).toBeVisible();
// });
// Render - Header // Render - Header
describe('Header', () => { describe('Header', () => {
it('should have actions button ', async () => { it('should have actions button ', async () => {
@ -75,16 +74,10 @@ describe('Join public room', () => {
await expect(element(by.id('room-actions-info'))).toBeVisible(); await expect(element(by.id('room-actions-info'))).toBeVisible();
}); });
// it('should have voice', async() => {
// await expect(element(by.id('room-actions-voice'))).toBeVisible();
// });
// it('should have video', async() => {
// await expect(element(by.id('room-actions-video'))).toBeVisible();
// });
it('should have members', async () => { it('should have members', async () => {
await expect(element(by.id('room-actions-members'))).toBeVisible(); await waitFor(element(by.id('room-actions-members')))
.toBeVisible()
.withTimeout(2000);
}); });
it('should have files', async () => { it('should have files', async () => {
@ -147,32 +140,29 @@ describe('Join public room', () => {
await navigateToRoomActions(); await navigateToRoomActions();
await expect(element(by.id('room-actions-view'))).toBeVisible(); await expect(element(by.id('room-actions-view'))).toBeVisible();
await expect(element(by.id('room-actions-info'))).toBeVisible(); await expect(element(by.id('room-actions-info'))).toBeVisible();
// await expect(element(by.id('room-actions-voice'))).toBeVisible();
// await expect(element(by.id('room-actions-video'))).toBeVisible();
await expect(element(by.id('room-actions-members'))).toBeVisible(); await expect(element(by.id('room-actions-members'))).toBeVisible();
await expect(element(by.id('room-actions-files'))).toBeVisible(); await expect(element(by.id('room-actions-files'))).toBeVisible();
await expect(element(by.id('room-actions-mentioned'))).toBeVisible(); await expect(element(by.id('room-actions-mentioned'))).toBeVisible();
await expect(element(by.id('room-actions-starred'))).toBeVisible(); await expect(element(by.id('room-actions-starred'))).toBeVisible();
await element(by.id('room-actions-scrollview')).swipe('down');
await expect(element(by.id('room-actions-share'))).toBeVisible(); await expect(element(by.id('room-actions-share'))).toBeVisible();
await expect(element(by.id('room-actions-pinned'))).toBeVisible(); await expect(element(by.id('room-actions-pinned'))).toBeVisible();
await expect(element(by.id('room-actions-notifications'))).toBeVisible(); await expect(element(by.id('room-actions-notifications'))).toBeVisible();
await element(by.id('room-actions-scrollview')).scrollTo('bottom');
await expect(element(by.id('room-actions-leave-channel'))).toBeVisible(); await expect(element(by.id('room-actions-leave-channel'))).toBeVisible();
}); });
it('should leave room', async () => { it('should leave room', async () => {
await element(by.id('room-actions-leave-channel')).tap(); await element(by.id('room-actions-leave-channel')).tap();
await waitFor(element(by.text('Yes, leave it!'))) await waitFor(element(by[textMatcher]('Yes, leave it!').and(by.type(alertButtonType))))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
await expect(element(by.text('Yes, leave it!'))).toBeVisible(); await element(by[textMatcher]('Yes, leave it!').and(by.type(alertButtonType))).tap();
await element(by.text('Yes, leave it!')).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
await waitFor(element(by.id(`rooms-list-view-item-${room}`))) await waitFor(element(by.id(`rooms-list-view-item-${room}`)))
.toBeNotVisible() .toBeNotVisible()
.withTimeout(60000); .withTimeout(60000); // flaky on Android
}); });
}); });
}); });

View File

@ -45,8 +45,9 @@ describe('Status screen', () => {
.withTimeout(2000); .withTimeout(2000);
}); });
// TODO: flaky
it('should change status text', async () => { it('should change status text', async () => {
await element(by.id('status-view-input')).typeText('status-text-new'); await element(by.id('status-view-input')).replaceText('status-text-new');
await element(by.id('status-view-submit')).tap(); await element(by.id('status-view-submit')).tap();
await waitForToast(); await waitForToast();
await waitFor(element(by.label('status-text-new').withAncestor(by.id('sidebar-custom-status')))) await waitFor(element(by.label('status-text-new').withAncestor(by.id('sidebar-custom-status'))))

View File

@ -1,8 +1,8 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, checkServer } = require('../../helpers/app'); const { navigateToLogin, login, checkServer, platformTypes } = require('../../helpers/app');
const reopenAndCheckServer = async server => { const reopenAndCheckServer = async server => {
await device.launchApp({ permissions: { notifications: 'YES' } }); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true });
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
@ -37,7 +37,8 @@ describe('Change server', () => {
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(6000); .withTimeout(6000);
await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); await element(by.id('new-server-view-input')).replaceText(`${data.alternateServer}`);
await element(by.id('new-server-view-input')).tapReturnKey();
await waitFor(element(by.id('workspace-view'))) await waitFor(element(by.id('workspace-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);

View File

@ -1,5 +1,5 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, mockMessage, searchRoom } = require('../../helpers/app'); const { navigateToLogin, login, mockMessage, searchRoom, sleep } = require('../../helpers/app');
const testuser = data.users.regular; const testuser = data.users.regular;
const room = data.channels.detoxpublicprotected.name; const room = data.channels.detoxpublicprotected.name;
@ -9,15 +9,25 @@ async function navigateToRoom() {
await searchRoom(room); await searchRoom(room);
await element(by.id(`rooms-list-view-item-${room}`)).tap(); await element(by.id(`rooms-list-view-item-${room}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toBeVisible() .toExist()
.withTimeout(5000); .withTimeout(5000);
} }
async function openJoinCode() { async function openJoinCode() {
await element(by.id('room-view-join-button')).tap(); await waitFor(element(by.id('room-view-join-button')))
await waitFor(element(by.id('join-code'))) .toExist()
.toBeVisible() .withTimeout(2000);
.withTimeout(5000); let n = 0;
while (n < 3) {
try {
await element(by.id('room-view-join-button')).tap();
await waitFor(element(by.id('join-code')))
.toBeVisible()
.withTimeout(500);
} catch (error) {
n += 1;
}
}
} }
describe('Join protected room', () => { describe('Join protected room', () => {

View File

@ -10,7 +10,7 @@ async function navigateToRoom(search) {
.withTimeout(10000); .withTimeout(10000);
await sleep(300); // app takes some time to animate await sleep(300); // app takes some time to animate
await element(by.id(`directory-view-item-${search}`)).tap(); await element(by.id(`directory-view-item-${search}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')).atIndex(0))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await waitFor(element(by.id(`room-view-title-${search}`))) await waitFor(element(by.id(`room-view-title-${search}`)))
@ -44,20 +44,20 @@ describe('Join room from directory', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('directory-view-dropdown')).tap(); await element(by.id('directory-view-dropdown')).tap();
await element(by.label('Users')).tap(); await element(by.label('Users')).atIndex(0).tap();
await element(by.label('Search by')).tap(); await element(by.label('Search by')).atIndex(0).tap();
await navigateToRoom(data.users.alternate.username); await navigateToRoom(data.users.alternate.username);
}); });
it('should search user and navigate', async () => { it('should search team and navigate', async () => {
await tapBack(); await tapBack();
await element(by.id('rooms-list-view-directory')).tap(); await element(by.id('rooms-list-view-directory')).tap();
await waitFor(element(by.id('directory-view'))) await waitFor(element(by.id('directory-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('directory-view-dropdown')).tap(); await element(by.id('directory-view-dropdown')).tap();
await element(by.label('Teams')).tap(); await element(by.label('Teams')).atIndex(0).tap();
await element(by.label('Search by')).tap(); await element(by.label('Search by')).atIndex(0).tap();
await navigateToRoom(data.teams.private.name); await navigateToRoom(data.teams.private.name);
}); });
}); });

View File

@ -1,9 +1,12 @@
const data = require('../../data'); const data = require('../../data');
const { sleep, navigateToLogin, login, checkServer } = require('../../helpers/app'); const { sleep, navigateToLogin, login, checkServer, platformTypes } = require('../../helpers/app');
describe('Delete server', () => { describe('Delete server', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
}); });
@ -23,7 +26,8 @@ describe('Delete server', () => {
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); await element(by.id('new-server-view-input')).replaceText(`${data.alternateServer}`);
await element(by.id('new-server-view-input')).tapReturnKey();
await waitFor(element(by.id('workspace-view'))) await waitFor(element(by.id('workspace-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
@ -36,7 +40,7 @@ describe('Delete server', () => {
await element(by.id('register-view-name')).replaceText(data.registeringUser3.username); await element(by.id('register-view-name')).replaceText(data.registeringUser3.username);
await element(by.id('register-view-username')).replaceText(data.registeringUser3.username); await element(by.id('register-view-username')).replaceText(data.registeringUser3.username);
await element(by.id('register-view-email')).replaceText(data.registeringUser3.email); await element(by.id('register-view-email')).replaceText(data.registeringUser3.email);
await element(by.id('register-view-password')).typeText(data.registeringUser3.password); await element(by.id('register-view-password')).replaceText(data.registeringUser3.password);
await element(by.id('register-view-submit')).tap(); await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
@ -51,7 +55,7 @@ describe('Delete server', () => {
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
await element(by.id(`rooms-list-header-server-${data.server}`)).longPress(1500); await element(by.id(`rooms-list-header-server-${data.server}`)).longPress(1500);
await element(by.label('Delete').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by[textMatcher]('Delete').and(by.type(alertButtonType))).tap();
await element(by.id('rooms-list-header-server-dropdown-button')).tap(); await element(by.id('rooms-list-header-server-dropdown-button')).tap();
await waitFor(element(by.id('rooms-list-header-server-dropdown'))) await waitFor(element(by.id('rooms-list-header-server-dropdown')))
.toBeVisible() .toBeVisible()

View File

@ -1,10 +1,13 @@
const data = require('../../data'); const data = require('../../data');
const { tapBack, checkServer, navigateToRegister } = require('../../helpers/app'); const { tapBack, checkServer, navigateToRegister, platformTypes } = require('../../helpers/app');
const { get, login, sendMessage } = require('../../helpers/data_setup'); const { get, login, sendMessage } = require('../../helpers/data_setup');
const DEEPLINK_METHODS = { AUTH: 'auth', ROOM: 'room' }; const DEEPLINK_METHODS = { AUTH: 'auth', ROOM: 'room' };
let amp = '&';
const getDeepLink = (method, server, params) => { const getDeepLink = (method, server, params) => {
const deeplink = `rocketchat://${method}?host=${server.replace(/^(http:\/\/|https:\/\/)/, '')}&${params}`; const deeplink = `rocketchat://${method}?host=${server.replace(/^(http:\/\/|https:\/\/)/, '')}${amp}${params}`;
console.log(`Deeplinking to: ${deeplink}`); console.log(`Deeplinking to: ${deeplink}`);
return deeplink; return deeplink;
}; };
@ -12,11 +15,17 @@ const getDeepLink = (method, server, params) => {
describe('Deep linking', () => { describe('Deep linking', () => {
let userId; let userId;
let authToken; let authToken;
let scrollViewType;
let threadId; let threadId;
let textMatcher;
let alertButtonType;
const threadMessage = `to-thread-${data.random}`; const threadMessage = `to-thread-${data.random}`;
before(async () => { before(async () => {
const loginResult = await login(data.users.regular.username, data.users.regular.password); const loginResult = await login(data.users.regular.username, data.users.regular.password);
({ userId, authToken } = loginResult); ({ userId, authToken } = loginResult);
const deviceType = device.getPlatform();
amp = deviceType === 'android' ? '\\&' : '&';
({ scrollViewType, textMatcher, alertButtonType } = platformTypes[deviceType]);
// create a thread with api // create a thread with api
const result = await sendMessage(data.users.regular, data.groups.alternate2.name, threadMessage); const result = await sendMessage(data.users.regular, data.groups.alternate2.name, threadMessage);
threadId = result.message._id; threadId = result.message._id;
@ -28,10 +37,9 @@ describe('Deep linking', () => {
await device.launchApp({ await device.launchApp({
permissions: { notifications: 'YES' }, permissions: { notifications: 'YES' },
delete: true, delete: true,
url: getDeepLink(DEEPLINK_METHODS.AUTH, data.server, 'userId=123&token=abc'), url: getDeepLink(DEEPLINK_METHODS.AUTH, data.server, `userId=123${amp}token=abc`)
sourceApp: 'com.apple.mobilesafari'
}); });
await waitFor(element(by.text("You've been logged out by the server. Please log in again."))) await waitFor(element(by[textMatcher]("You've been logged out by the server. Please log in again.")))
.toExist() .toExist()
.withTimeout(10000); // TODO: we need to improve this message .withTimeout(10000); // TODO: we need to improve this message
}); });
@ -43,9 +51,8 @@ describe('Deep linking', () => {
url: getDeepLink( url: getDeepLink(
DEEPLINK_METHODS.AUTH, DEEPLINK_METHODS.AUTH,
data.server, data.server,
`userId=${userId}&token=${authToken}&path=group/${data.groups.private.name}` `userId=${userId}${amp}token=${authToken}${amp}path=group/${data.groups.private.name}`
), )
sourceApp: 'com.apple.mobilesafari'
}); });
await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`)))
.toExist() .toExist()
@ -56,7 +63,7 @@ describe('Deep linking', () => {
.withTimeout(10000); .withTimeout(10000);
await checkServer(data.server); await checkServer(data.server);
await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`))) await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`)))
.toBeVisible() .toExist()
.withTimeout(2000); .withTimeout(2000);
}; };
@ -70,7 +77,8 @@ describe('Deep linking', () => {
await element(by.id('register-view-name')).replaceText(data.registeringUser4.username); await element(by.id('register-view-name')).replaceText(data.registeringUser4.username);
await element(by.id('register-view-username')).replaceText(data.registeringUser4.username); await element(by.id('register-view-username')).replaceText(data.registeringUser4.username);
await element(by.id('register-view-email')).replaceText(data.registeringUser4.email); await element(by.id('register-view-email')).replaceText(data.registeringUser4.email);
await element(by.id('register-view-password')).typeText(data.registeringUser4.password); await element(by.id('register-view-password')).replaceText(data.registeringUser4.password);
await element(by.type(scrollViewType)).atIndex(0).scrollTo('bottom');
await element(by.id('register-view-submit')).tap(); await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
@ -85,8 +93,7 @@ describe('Deep linking', () => {
await device.launchApp({ await device.launchApp({
permissions: { notifications: 'YES' }, permissions: { notifications: 'YES' },
newInstance: true, newInstance: true,
url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`), url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`)
sourceApp: 'com.apple.mobilesafari'
}); });
await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`)))
.toExist() .toExist()
@ -97,8 +104,7 @@ describe('Deep linking', () => {
await device.launchApp({ await device.launchApp({
permissions: { notifications: 'YES' }, permissions: { notifications: 'YES' },
newInstance: true, newInstance: true,
url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.alternate2.name}/thread/${threadId}`), url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.alternate2.name}/thread/${threadId}`)
sourceApp: 'com.apple.mobilesafari'
}); });
await waitFor(element(by.id(`room-view-title-${threadMessage}`))) await waitFor(element(by.id(`room-view-title-${threadMessage}`)))
.toExist() .toExist()
@ -110,8 +116,7 @@ describe('Deep linking', () => {
await device.launchApp({ await device.launchApp({
permissions: { notifications: 'YES' }, permissions: { notifications: 'YES' },
newInstance: true, newInstance: true,
url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `rid=${roomResult.data.group._id}`), url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `rid=${roomResult.data.group._id}`)
sourceApp: 'com.apple.mobilesafari'
}); });
await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`)))
.toExist() .toExist()
@ -135,8 +140,7 @@ describe('Deep linking', () => {
await device.launchApp({ await device.launchApp({
permissions: { notifications: 'YES' }, permissions: { notifications: 'YES' },
newInstance: true, newInstance: true,
url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`), url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`)
sourceApp: 'com.apple.mobilesafari'
}); });
await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`)))
.toExist() .toExist()
@ -147,8 +151,7 @@ describe('Deep linking', () => {
await device.launchApp({ await device.launchApp({
permissions: { notifications: 'YES' }, permissions: { notifications: 'YES' },
newInstance: true, newInstance: true,
url: getDeepLink(DEEPLINK_METHODS.ROOM, 'https://google.com'), url: getDeepLink(DEEPLINK_METHODS.ROOM, 'https://google.com')
sourceApp: 'com.apple.mobilesafari'
}); });
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()

View File

@ -29,6 +29,9 @@ const navToLanguage = async () => {
describe('i18n', () => { describe('i18n', () => {
describe('OS language', () => { describe('OS language', () => {
it("OS set to 'en' and proper translate to 'en'", async () => { it("OS set to 'en' and proper translate to 'en'", async () => {
if (device.getPlatform() === 'android') {
return; // FIXME: Passing language with launch parameters doesn't work with Android
}
await device.launchApp({ await device.launchApp({
...defaultLaunchArgs, ...defaultLaunchArgs,
languageAndLocale: { languageAndLocale: {
@ -44,6 +47,9 @@ describe('i18n', () => {
}); });
it("OS set to unavailable language and fallback to 'en'", async () => { it("OS set to unavailable language and fallback to 'en'", async () => {
if (device.getPlatform() === 'android') {
return; // FIXME: Passing language with launch parameters doesn't work with Android
}
await device.launchApp({ await device.launchApp({
...defaultLaunchArgs, ...defaultLaunchArgs,
languageAndLocale: { languageAndLocale: {
@ -74,7 +80,7 @@ describe('i18n', () => {
describe('Rocket.Chat language', () => { describe('Rocket.Chat language', () => {
before(async () => { before(async () => {
await device.launchApp(defaultLaunchArgs); await device.launchApp({ ...defaultLaunchArgs, delete: true });
await navigateToLogin(); await navigateToLogin();
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
}); });
@ -113,7 +119,7 @@ describe('i18n', () => {
it("should set unsupported language and fallback to 'en'", async () => { it("should set unsupported language and fallback to 'en'", async () => {
await post('users.setPreferences', { data: { language: 'eo' } }); // Set language to Esperanto await post('users.setPreferences', { data: { language: 'eo' } }); // Set language to Esperanto
await device.launchApp(defaultLaunchArgs); await device.launchApp({ ...defaultLaunchArgs, newInstance: true });
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);

View File

@ -1,4 +1,4 @@
const { login, navigateToLogin } = require('../../helpers/app'); const { login, navigateToLogin, sleep } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
const goToDisplayPref = async () => { const goToDisplayPref = async () => {
@ -14,7 +14,7 @@ const goToRoomList = async () => {
await element(by.id('sidebar-chats')).tap(); await element(by.id('sidebar-chats')).tap();
}; };
describe('Rooms list screen', () => { describe('Display prefs', () => {
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
await navigateToLogin(); await navigateToLogin();
@ -89,7 +89,9 @@ describe('Rooms list screen', () => {
await expect(element(by.id('display-pref-view-avatar-switch'))).toBeVisible(); await expect(element(by.id('display-pref-view-avatar-switch'))).toBeVisible();
await element(by.id('display-pref-view-avatar-switch')).tap(); await element(by.id('display-pref-view-avatar-switch')).tap();
await goToRoomList(); await goToRoomList();
await expect(element(by.id('avatar'))).not.toBeVisible(); await waitFor(element(by.id('avatar').withAncestor(by.id('rooms-list-view-item-general'))))
.not.toBeVisible()
.withTimeout(2000);
}); });
}); });
}); });

View File

@ -3,9 +3,11 @@ const adapter = require('detox/runners/mocha/adapter');
const config = require('../../package.json').detox; const config = require('../../package.json').detox;
const { setup } = require('../helpers/data_setup'); const { setup } = require('../helpers/data_setup');
const { prepareAndroid } = require('../helpers/app');
before(async () => { before(async () => {
await Promise.all([setup(), detox.init(config, { launchApp: false })]); await Promise.all([setup(), detox.init(config, { launchApp: false })]);
await prepareAndroid(); // Make Android less flaky
// await dataSetup() // await dataSetup()
// await detox.init(config, { launchApp: false }); // await detox.init(config, { launchApp: false });
// await device.launchApp({ permissions: { notifications: 'YES' } }); // await device.launchApp({ permissions: { notifications: 'YES' } });

View File

@ -1,8 +1,12 @@
const data = require('../../data'); const data = require('../../data');
const { platformTypes } = require('../../helpers/app');
describe('Onboarding', () => { describe('Onboarding', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(20000); .withTimeout(20000);
@ -19,17 +23,13 @@ describe('Onboarding', () => {
}); });
describe('Usage', () => { describe('Usage', () => {
// it('should navigate to create new workspace', async() => {
// // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
// });
it('should enter an invalid server and get error', async () => { it('should enter an invalid server and get error', async () => {
await element(by.id('new-server-view-input')).typeText('invalidtest\n'); await element(by.id('new-server-view-input')).replaceText('invalidtest');
const errorText = 'Oops!'; await element(by.id('new-server-view-input')).tapReturnKey();
await waitFor(element(by.text(errorText))) await waitFor(element(by[textMatcher]('Oops!')))
.toBeVisible() .toExist()
.withTimeout(60000); .withTimeout(10000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
}); });
it('should tap on "Join our open workspace" and navigate', async () => { it('should tap on "Join our open workspace" and navigate', async () => {
@ -44,7 +44,8 @@ describe('Onboarding', () => {
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('new-server-view-input')).typeText(`${data.server}\n`); await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-input')).tapReturnKey();
await waitFor(element(by.id('workspace-view'))) await waitFor(element(by.id('workspace-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);

View File

@ -1,9 +1,12 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin } = require('../../helpers/app'); const { navigateToLogin, platformTypes } = require('../../helpers/app');
describe('Forgot password screen', () => { describe('Forgot password screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await element(by.id('login-view-forgot-password')).tap(); await element(by.id('login-view-forgot-password')).tap();
await waitFor(element(by.id('forgot-password-view'))) await waitFor(element(by.id('forgot-password-view')))
@ -29,10 +32,10 @@ describe('Forgot password screen', () => {
it('should reset password and navigate to login', async () => { it('should reset password and navigate to login', async () => {
await element(by.id('forgot-password-view-email')).replaceText(data.users.existing.email); await element(by.id('forgot-password-view-email')).replaceText(data.users.existing.email);
await element(by.id('forgot-password-view-submit')).tap(); await element(by.id('forgot-password-view-submit')).tap();
await waitFor(element(by.text('OK'))) await waitFor(element(by[textMatcher]('OK')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('login-view'))) await waitFor(element(by.id('login-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);

View File

@ -1,9 +1,12 @@
const { navigateToRegister } = require('../../helpers/app'); const { navigateToRegister, platformTypes } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
describe('Create user screen', () => { describe('Create user screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToRegister(); await navigateToRegister();
}); });
@ -50,10 +53,10 @@ describe('Create user screen', () => {
await element(by.id('register-view-email')).replaceText(data.users.existing.email); await element(by.id('register-view-email')).replaceText(data.users.existing.email);
await element(by.id('register-view-password')).replaceText(data.registeringUser.password); await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
await element(by.id('register-view-submit')).tap(); await element(by.id('register-view-submit')).tap();
await waitFor(element(by.text('Email already exists. [403]')).atIndex(0)) await waitFor(element(by[textMatcher]('Email already exists. [403]')).atIndex(0))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
}); });
it('should submit username already taken and raise error', async () => { it('should submit username already taken and raise error', async () => {
@ -62,10 +65,10 @@ describe('Create user screen', () => {
await element(by.id('register-view-email')).replaceText(data.registeringUser.email); await element(by.id('register-view-email')).replaceText(data.registeringUser.email);
await element(by.id('register-view-password')).replaceText(data.registeringUser.password); await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
await element(by.id('register-view-submit')).tap(); await element(by.id('register-view-submit')).tap();
await waitFor(element(by.text('Username is already in use')).atIndex(0)) await waitFor(element(by[textMatcher]('Username is already in use')).atIndex(0))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
}); });
it('should register', async () => { it('should register', async () => {

View File

@ -1,9 +1,12 @@
const { navigateToLogin, tapBack } = require('../../helpers/app'); const { navigateToLogin, tapBack, platformTypes } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
describe('Login screen', () => { describe('Login screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
}); });
@ -58,10 +61,10 @@ describe('Login screen', () => {
await element(by.id('login-view-email')).replaceText(data.users.regular.username); await element(by.id('login-view-email')).replaceText(data.users.regular.username);
await element(by.id('login-view-password')).replaceText('NotMyActualPassword'); await element(by.id('login-view-password')).replaceText('NotMyActualPassword');
await element(by.id('login-view-submit')).tap(); await element(by.id('login-view-submit')).tap();
await waitFor(element(by.text('Your credentials were rejected! Please try again.'))) await waitFor(element(by[textMatcher]('Your credentials were rejected! Please try again.')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
}); });
it('should login with success', async () => { it('should login with success', async () => {

View File

@ -14,7 +14,9 @@ describe('Rooms list screen', () => {
}); });
it('should have room item', async () => { it('should have room item', async () => {
await expect(element(by.id('rooms-list-view-item-general'))).toExist(); await waitFor(element(by.id('rooms-list-view-item-general')))
.toExist()
.withTimeout(10000);
}); });
// Render - Header // Render - Header

View File

@ -25,10 +25,10 @@ describe('Server history', () => {
it('should tap on a server history and navigate to login', async () => { it('should tap on a server history and navigate to login', async () => {
await element(by.id(`server-history-${data.server}`)).tap(); await element(by.id(`server-history-${data.server}`)).tap();
await waitFor(element(by.id('login-view'))) await waitFor(element(by.id('login-view-email')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
await expect(element(by.id('login-view-email'))).toHaveText(data.users.regular.username); await expect(element(by.label(data.users.regular.username).withAncestor(by.id('login-view-email'))));
}); });
it('should delete server from history', async () => { it('should delete server from history', async () => {

View File

@ -1,9 +1,12 @@
const data = require('../../data'); const data = require('../../data');
const { tapBack, navigateToLogin, login, tryTapping } = require('../../helpers/app'); const { tapBack, navigateToLogin, login, tryTapping, platformTypes } = require('../../helpers/app');
describe('Create room screen', () => { describe('Create room screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
}); });
@ -121,20 +124,26 @@ describe('Create room screen', () => {
describe('Usage', () => { describe('Usage', () => {
it('should get invalid room', async () => { it('should get invalid room', async () => {
await element(by.id('create-channel-name')).typeText('general'); await element(by.id('create-channel-name')).replaceText('general');
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(2000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.text('A channel with name general exists'))) await waitFor(element(by[textMatcher]('A channel with name general exists')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await expect(element(by.text('A channel with name general exists'))).toExist(); await expect(element(by[textMatcher]('A channel with name general exists'))).toExist();
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
}); });
it('should create public room', async () => { it('should create public room', async () => {
const room = `public${data.random}`; const room = `public${data.random}`;
await element(by.id('create-channel-name')).replaceText(''); await element(by.id('create-channel-name')).replaceText('');
await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-name')).replaceText(room);
await element(by.id('create-channel-type')).tap(); await element(by.id('create-channel-type')).tap();
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(2000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
@ -175,7 +184,10 @@ describe('Create room screen', () => {
await waitFor(element(by.id('create-channel-view'))) await waitFor(element(by.id('create-channel-view')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-name')).replaceText(room);
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(2000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
@ -213,7 +225,10 @@ describe('Create room screen', () => {
await waitFor(element(by.id('create-channel-view'))) await waitFor(element(by.id('create-channel-view')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-name')).replaceText(room);
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(2000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()

View File

@ -9,7 +9,8 @@ const {
starMessage, starMessage,
pinMessage, pinMessage,
dismissReviewNag, dismissReviewNag,
tryTapping tryTapping,
platformTypes
} = require('../../helpers/app'); } = require('../../helpers/app');
async function navigateToRoom(roomName) { async function navigateToRoom(roomName) {
@ -22,9 +23,12 @@ async function navigateToRoom(roomName) {
describe('Room screen', () => { describe('Room screen', () => {
const mainRoom = data.groups.private.name; const mainRoom = data.groups.private.name;
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
await navigateToRoom(mainRoom); await navigateToRoom(mainRoom);
@ -79,7 +83,7 @@ describe('Room screen', () => {
describe('Messagebox', () => { describe('Messagebox', () => {
it('should send message', async () => { it('should send message', async () => {
await mockMessage('message'); await mockMessage('message');
await expect(element(by.label(`${data.random}message`)).atIndex(0)).toExist(); await expect(element(by[textMatcher](`${data.random}message`)).atIndex(0)).toExist();
}); });
it('should show/hide emoji keyboard', async () => { it('should show/hide emoji keyboard', async () => {
@ -100,8 +104,8 @@ describe('Room screen', () => {
}); });
it('should show/hide emoji autocomplete', async () => { it('should show/hide emoji autocomplete', async () => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(':joy'); await element(by.id('messagebox-input')).typeText(':joy');
await sleep(300);
await waitFor(element(by.id('messagebox-container'))) await waitFor(element(by.id('messagebox-container')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
@ -112,9 +116,8 @@ describe('Room screen', () => {
}); });
it('should show and tap on emoji autocomplete', async () => { it('should show and tap on emoji autocomplete', async () => {
await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText(':joy');
await element(by.id('messagebox-input')).replaceText(':'); await sleep(300);
await element(by.id('messagebox-input')).typeText('joy'); // workaround for number keyboard
await waitFor(element(by.id('messagebox-container'))) await waitFor(element(by.id('messagebox-container')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
@ -124,9 +127,8 @@ describe('Room screen', () => {
}); });
it('should not show emoji autocomplete on semicolon in middle of a string', async () => { it('should not show emoji autocomplete on semicolon in middle of a string', async () => {
await element(by.id('messagebox-input')).tap();
// await element(by.id('messagebox-input')).replaceText(':');
await element(by.id('messagebox-input')).typeText('name:is'); await element(by.id('messagebox-input')).typeText('name:is');
await sleep(300);
await waitFor(element(by.id('messagebox-container'))) await waitFor(element(by.id('messagebox-container')))
.toNotExist() .toNotExist()
.withTimeout(20000); .withTimeout(20000);
@ -135,8 +137,11 @@ describe('Room screen', () => {
it('should show and tap on user autocomplete and send mention', async () => { it('should show and tap on user autocomplete and send mention', async () => {
const { username } = data.users.regular; const { username } = data.users.regular;
await element(by.id('messagebox-input')).tap(); const messageMention = `@${username}`;
const message = `${data.random}mention`;
const fullMessage = `${messageMention} ${message}`;
await element(by.id('messagebox-input')).typeText(`@${username}`); await element(by.id('messagebox-input')).typeText(`@${username}`);
await sleep(300);
await waitFor(element(by.id('messagebox-container'))) await waitFor(element(by.id('messagebox-container')))
.toExist() .toExist()
.withTimeout(4000); .withTimeout(4000);
@ -144,15 +149,24 @@ describe('Room screen', () => {
.toBeVisible() .toBeVisible()
.withTimeout(4000); .withTimeout(4000);
await tryTapping(element(by.id(`mention-item-${username}`)), 2000, true); await tryTapping(element(by.id(`mention-item-${username}`)), 2000, true);
await expect(element(by.id('messagebox-input'))).toHaveText(`@${username} `); await expect(element(by.id('messagebox-input'))).toHaveText(`${messageMention} `);
await tryTapping(element(by.id('messagebox-input')), 2000); await tryTapping(element(by.id('messagebox-input')), 2000);
await element(by.id('messagebox-input')).typeText(`${data.random}mention`); if (device.getPlatform() === 'ios') {
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-input')).typeText(message);
// await waitFor(element(by.label(`@${ data.user } ${ data.random }mention`)).atIndex(0)).toExist().withTimeout(60000); await element(by.id('messagebox-send-message')).tap();
const fullMessageMatcher = fullMessage.substr(1); // removes `@`
await waitFor(element(by[textMatcher](fullMessageMatcher)))
.toExist()
.withTimeout(60000);
await expect(element(by[textMatcher](fullMessageMatcher))).toExist();
await element(by[textMatcher](fullMessageMatcher)).atIndex(0).tap();
} else {
await element(by.id('messagebox-input')).replaceText(fullMessage);
await element(by.id('messagebox-send-message')).tap();
}
}); });
it('should not show user autocomplete on @ in the middle of a string', async () => { it('should not show user autocomplete on @ in the middle of a string', async () => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText('email@gmail'); await element(by.id('messagebox-input')).typeText('email@gmail');
await waitFor(element(by.id('messagebox-container'))) await waitFor(element(by.id('messagebox-container')))
.toNotExist() .toNotExist()
@ -161,9 +175,7 @@ describe('Room screen', () => {
}); });
it('should show and tap on room autocomplete', async () => { it('should show and tap on room autocomplete', async () => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText('#general'); await element(by.id('messagebox-input')).typeText('#general');
// await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(4000);
await waitFor(element(by.id('mention-item-general'))) await waitFor(element(by.id('mention-item-general')))
.toBeVisible() .toBeVisible()
.withTimeout(4000); .withTimeout(4000);
@ -181,7 +193,6 @@ describe('Room screen', () => {
await element(by.id('messagebox-input')).clearText(); await element(by.id('messagebox-input')).clearText();
}); });
it('should draft message', async () => { it('should draft message', async () => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(`${data.random}draft`); await element(by.id('messagebox-input')).typeText(`${data.random}draft`);
await tapBack(); await tapBack();
@ -197,25 +208,29 @@ describe('Room screen', () => {
describe('Message', () => { describe('Message', () => {
it('should copy permalink', async () => { it('should copy permalink', async () => {
await element(by.label(`${data.random}message`)) await element(by[textMatcher](`${data.random}message`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Permalink')).atIndex(0).tap(); await element(by[textMatcher]('Permalink')).atIndex(0).tap();
// TODO: test clipboard // TODO: test clipboard
}); });
it('should copy message', async () => { it('should copy message', async () => {
await element(by.label(`${data.random}message`)) await element(by[textMatcher](`${data.random}message`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Copy')).atIndex(0).tap(); await element(by[textMatcher]('Copy')).atIndex(0).tap();
// TODO: test clipboard // TODO: test clipboard
}); });
@ -224,23 +239,33 @@ describe('Room screen', () => {
await starMessage('message'); await starMessage('message');
await sleep(1000); // https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2324 await sleep(1000); // https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2324
await element(by.label(`${data.random}message`)) await element(by[textMatcher](`${data.random}message`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'slow', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'slow', 0.5);
await waitFor(element(by.label('Unstar')).atIndex(0)) await waitFor(element(by[textMatcher]('Unstar')).atIndex(0))
.toExist() .toExist()
.withTimeout(6000); .withTimeout(6000);
await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.8); await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.8);
}); });
it('should react to message', async () => { it('should react to message', async () => {
await element(by.label(`${data.random}message`)) await waitFor(element(by[textMatcher](`${data.random}message`)))
.toExist()
.withTimeout(60000);
await element(by[textMatcher](`${data.random}message`))
.atIndex(0)
.tap();
await element(by[textMatcher](`${data.random}message`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.id('add-reaction')).tap(); await element(by.id('add-reaction')).tap();
@ -258,10 +283,12 @@ describe('Room screen', () => {
}); });
it('should react to message with frequently used emoji', async () => { it('should react to message with frequently used emoji', async () => {
await element(by.label(`${data.random}message`)) await element(by[textMatcher](`${data.random}message`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await waitFor(element(by.id('message-actions-emoji-+1'))) await waitFor(element(by.id('message-actions-emoji-+1')))
@ -273,7 +300,7 @@ describe('Room screen', () => {
.withTimeout(60000); .withTimeout(60000);
}); });
it('should show reaction picker on add reaction button pressed and have frequently used emoji', async () => { it('should show reaction picker on add reaction button pressed and have frequently used emoji, and dismiss review nag', async () => {
await element(by.id('message-add-reaction')).tap(); await element(by.id('message-add-reaction')).tap();
await waitFor(element(by.id('reaction-picker'))) await waitFor(element(by.id('reaction-picker')))
.toExist() .toExist()
@ -291,10 +318,6 @@ describe('Room screen', () => {
.withTimeout(60000); .withTimeout(60000);
}); });
it('should ask for review', async () => {
await dismissReviewNag(); // TODO: Create a proper test for this elsewhere.
});
it('should remove reaction', async () => { it('should remove reaction', async () => {
await element(by.id('message-reaction-:grinning:')).tap(); await element(by.id('message-reaction-:grinning:')).tap();
await waitFor(element(by.id('message-reaction-:grinning:'))) await waitFor(element(by.id('message-reaction-:grinning:')))
@ -302,32 +325,43 @@ describe('Room screen', () => {
.withTimeout(60000); .withTimeout(60000);
}); });
it('should ask for review', async () => {
await dismissReviewNag(); // TODO: Create a proper test for this elsewhere.
});
it('should edit message', async () => { it('should edit message', async () => {
await mockMessage('edit'); await mockMessage('edit');
await element(by.label(`${data.random}edit`)) await element(by[textMatcher](`${data.random}edit`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Edit')).atIndex(0).tap(); await element(by[textMatcher]('Edit')).atIndex(0).tap();
await element(by.id('messagebox-input')).typeText('ed'); await element(by.id('messagebox-input')).replaceText(`${data.random}edited`);
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by.label(`${data.random}edited (edited)`)).atIndex(0)) await waitFor(element(by[textMatcher](`${data.random}edited (edited)`)).atIndex(0))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
}); });
it('should quote message', async () => { it('should quote message', async () => {
await mockMessage('quote'); await mockMessage('quote');
await element(by.label(`${data.random}quote`)) await element(by[textMatcher](`${data.random}quote`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Quote')).atIndex(0).tap(); await element(by[textMatcher]('Quote')).atIndex(0).tap();
await element(by.id('messagebox-input')).typeText(`${data.random}quoted`); await element(by.id('messagebox-input')).replaceText(`${data.random}quoted`);
await waitFor(element(by.id('messagebox-send-message')))
.toExist()
.withTimeout(2000);
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
// TODO: test if quote was sent // TODO: test if quote was sent
@ -337,13 +371,13 @@ describe('Room screen', () => {
await mockMessage('pin'); await mockMessage('pin');
await pinMessage('pin'); await pinMessage('pin');
await waitFor(element(by.label(`${data.random}pin`)).atIndex(0)) await waitFor(element(by[textMatcher](`${data.random}pin`)).atIndex(0))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await waitFor(element(by.label(`${data.users.regular.username} Message pinned`)).atIndex(0)) await waitFor(element(by[textMatcher](`${data.users.regular.username} Message pinned`)).atIndex(0))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label(`${data.random}pin`)) await element(by[textMatcher](`${data.random}pin`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await waitFor(element(by.id('action-sheet'))) await waitFor(element(by.id('action-sheet')))
@ -351,7 +385,7 @@ describe('Room screen', () => {
.withTimeout(1000); .withTimeout(1000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await waitFor(element(by.label('Unpin')).atIndex(0)) await waitFor(element(by[textMatcher]('Unpin')).atIndex(0))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.8); await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.8);
@ -359,26 +393,26 @@ describe('Room screen', () => {
it('should delete message', async () => { it('should delete message', async () => {
await mockMessage('delete'); await mockMessage('delete');
await waitFor(element(by[textMatcher](`${data.random}delete`)).atIndex(0)).toBeVisible();
await waitFor(element(by.label(`${data.random}delete`)).atIndex(0)).toBeVisible(); await element(by[textMatcher](`${data.random}delete`))
await element(by.label(`${data.random}delete`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await waitFor(element(by.label('Delete'))) await waitFor(element(by[textMatcher]('Delete')))
.toExist() .toExist()
.withTimeout(1000); .withTimeout(1000);
await element(by.label('Delete')).atIndex(0).tap(); await element(by[textMatcher]('Delete')).atIndex(0).tap();
const deleteAlertMessage = 'You will not be able to recover this message!'; const deleteAlertMessage = 'You will not be able to recover this message!';
await waitFor(element(by.text(deleteAlertMessage)).atIndex(0)) await waitFor(element(by[textMatcher](deleteAlertMessage)).atIndex(0))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await element(by.text('Delete')).tap(); await element(by[textMatcher]('Delete').and(by.type(alertButtonType))).tap();
await waitFor(element(by[textMatcher](`${data.random}delete`)).atIndex(0))
await waitFor(element(by.label(`${data.random}delete`)).atIndex(0))
.toNotExist() .toNotExist()
.withTimeout(2000); .withTimeout(2000);
}); });

View File

@ -1,5 +1,15 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, tapBack, sleep, searchRoom, mockMessage, starMessage, pinMessage } = require('../../helpers/app'); const {
navigateToLogin,
login,
tapBack,
sleep,
searchRoom,
mockMessage,
starMessage,
pinMessage,
platformTypes
} = require('../../helpers/app');
const { sendMessage } = require('../../helpers/data_setup'); const { sendMessage } = require('../../helpers/data_setup');
async function navigateToRoomActions(type) { async function navigateToRoomActions(type) {
@ -43,10 +53,13 @@ async function waitForToast() {
} }
describe('Room actions screen', () => { describe('Room actions screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
}); });
describe('Render', () => { describe('Render', () => {
@ -172,36 +185,12 @@ describe('Room actions screen', () => {
}); });
describe('Usage', () => { describe('Usage', () => {
describe('TDB', async () => {
// TODO: test into a jitsi call
// it('should NOT navigate to voice call', async() => {
// await waitFor(element(by.id('room-actions-voice'))).toExist();
// await element(by.id('room-actions-voice')).tap();
// await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000);
// await expect(element(by.id('room-actions-view'))).toExist();
// });
// TODO: test into a jitsi call
// it('should NOT navigate to video call', async() => {
// await element(by.id('room-actions-video')).tap();
// await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000);
// await expect(element(by.id('room-actions-view'))).toExist();
// });
// TODO: test share room link
// it('should NOT navigate to share room', async() => {
// await waitFor(element(by.id('room-actions-share'))).toExist();
// await element(by.id('room-actions-share')).tap();
// await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000);
// await expect(element(by.id('room-actions-view'))).toExist();
// });
});
describe('Common', () => { describe('Common', () => {
it('should show mentioned messages', async () => { it('should show mentioned messages', async () => {
await element(by.id('room-actions-mentioned')).tap(); await element(by.id('room-actions-mentioned')).tap();
await waitFor(element(by.id('mentioned-messages-view'))) await waitFor(element(by.id('mentioned-messages-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
// await waitFor(element(by.text(` ${ data.random }mention`))).toExist().withTimeout(60000);
await backToActions(); await backToActions();
}); });
@ -220,23 +209,25 @@ describe('Room actions screen', () => {
.withTimeout(5000); .withTimeout(5000);
// Go to starred messages // Go to starred messages
await element(by.id('room-actions-view')).swipe('up');
await waitFor(element(by.id('room-actions-starred'))).toExist();
await element(by.id('room-actions-starred')).tap(); await element(by.id('room-actions-starred')).tap();
await waitFor(element(by.id('starred-messages-view'))) await waitFor(element(by.id('starred-messages-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await waitFor(element(by.label(`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view')))) await waitFor(element(by[textMatcher](`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view'))))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
// Unstar message // Unstar message
await element(by.label(`${data.random}messageToStar`)) await element(by[textMatcher](`${data.random}messageToStar`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet'))).toExist();
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.label('Unstar')).atIndex(0).tap(); await element(by[textMatcher]('Unstar')).atIndex(0).tap();
await waitFor(element(by.label(`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view')))) await waitFor(element(by[textMatcher](`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view'))))
.toBeNotVisible() .toBeNotVisible()
.withTimeout(60000); .withTimeout(60000);
await backToActions(); await backToActions();
@ -261,40 +252,22 @@ describe('Room actions screen', () => {
await waitFor(element(by.id('pinned-messages-view'))) await waitFor(element(by.id('pinned-messages-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await waitFor(element(by.label(`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view')))) await waitFor(element(by[textMatcher](`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view'))))
.toExist() .toExist()
.withTimeout(6000); .withTimeout(6000);
await element(by.label(`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view'))) await element(by[textMatcher](`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view')))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet'))).toExist();
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.label('Unpin')).atIndex(0).tap(); await element(by[textMatcher]('Unpin')).atIndex(0).tap();
await waitFor(element(by.label(`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view')))) await waitFor(element(by[textMatcher](`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view'))))
.not.toExist() .not.toExist()
.withTimeout(6000); .withTimeout(6000);
await backToActions(); await backToActions();
}); });
// it('should search and find a message', async() => {
// //Go back to room and send a message
// await tapBack();
// await mockMessage('messageToFind');
// //Back into Room Actions
// await element(by.id('room-header')).tap();
// await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
// await element(by.id('room-actions-search')).tap();
// await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(2000);
// await expect(element(by.id('search-message-view-input'))).toExist();
// await element(by.id('search-message-view-input')).replaceText(`/${ data.random }messageToFind/`);
// await waitFor(element(by.label(`${ data.random }messageToFind`).withAncestor(by.id('search-messages-view')))).toExist().withTimeout(60000);
// await backToActions();
// });
}); });
describe('Notification', () => { describe('Notification', () => {
@ -370,14 +343,14 @@ describe('Room actions screen', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-actions-leave-channel')).tap(); await element(by.id('room-actions-leave-channel')).tap();
await waitFor(element(by.text('Yes, leave it!'))) await waitFor(element(by[textMatcher]('Yes, leave it!')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.text('Yes, leave it!')).tap(); await element(by[textMatcher]('Yes, leave it!').and(by.type(alertButtonType))).tap();
await waitFor(element(by.text('You are the last owner. Please set new owner before leaving the room.'))) await waitFor(element(by[textMatcher]('You are the last owner. Please set new owner before leaving the room.')))
.toExist() .toExist()
.withTimeout(8000); .withTimeout(8000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('room-actions-view'))) await waitFor(element(by.id('room-actions-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
@ -404,6 +377,7 @@ describe('Room actions screen', () => {
.withTimeout(4000); .withTimeout(4000);
await element(by.id('select-users-view-search')).tap(); await element(by.id('select-users-view-search')).tap();
await element(by.id('select-users-view-search')).replaceText(user.username); await element(by.id('select-users-view-search')).replaceText(user.username);
await sleep(300);
await waitFor(element(by.id(`select-users-view-item-${user.username}`))) await waitFor(element(by.id(`select-users-view-item-${user.username}`)))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
@ -437,14 +411,30 @@ describe('Room actions screen', () => {
await waitFor(element(by.id(`room-members-view-item-${username}`))) await waitFor(element(by.id(`room-members-view-item-${username}`)))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id(`room-members-view-item-${username}`)).tap(); let n = 0;
await sleep(300); while (n < 3) {
await expect(element(by.id('action-sheet'))).toExist(); // Max tries three times, in case it does not register the click
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); try {
await element(by.id(`room-members-view-item-${username}`)).tap();
await sleep(300);
await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(5000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up');
return;
} catch (e) {
n += 1;
}
}
}; };
const closeActionSheet = async () => { const closeActionSheet = async () => {
await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6);
await waitFor(element(by.id('action-sheet')))
.toBeNotVisible()
.withTimeout(1000);
await sleep(100);
}; };
it('should show all users', async () => { it('should show all users', async () => {
@ -471,11 +461,14 @@ describe('Room actions screen', () => {
it('should remove user from room', async () => { it('should remove user from room', async () => {
await openActionSheet('rocket.cat'); await openActionSheet('rocket.cat');
await element(by.label('Remove from room')).atIndex(0).tap(); await waitFor(element(by[textMatcher]('Remove from room')))
await waitFor(element(by.label('Are you sure?'))) .toExist()
.withTimeout(2000);
await element(by[textMatcher]('Remove from room')).atIndex(0).tap();
await waitFor(element(by[textMatcher]('Are you sure?')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('Yes, remove user!').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by[textMatcher]('Yes, remove user!').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('room-members-view-item-rocket.cat'))) await waitFor(element(by.id('room-members-view-item-rocket.cat')))
.toBeNotVisible() .toBeNotVisible()
.withTimeout(60000); .withTimeout(60000);
@ -548,24 +541,24 @@ describe('Room actions screen', () => {
it('should set/remove as mute', async () => { it('should set/remove as mute', async () => {
await openActionSheet(user.username); await openActionSheet(user.username);
await element(by.label('Mute')).atIndex(0).tap(); await element(by[textMatcher]('Mute')).atIndex(0).tap();
await waitFor(element(by.label('Are you sure?'))) await waitFor(element(by[textMatcher]('Are you sure?')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('Mute').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by[textMatcher]('Mute').and(by.type(alertButtonType))).tap();
await waitForToast(); await waitForToast();
await openActionSheet(user.username); await openActionSheet(user.username);
await element(by.label('Unmute')).atIndex(0).tap(); await element(by[textMatcher]('Unmute')).atIndex(0).tap();
await waitFor(element(by.label('Are you sure?'))) await waitFor(element(by[textMatcher]('Are you sure?')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('Unmute').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by[textMatcher]('Unmute').and(by.type(alertButtonType))).tap();
await waitForToast(); await waitForToast();
await openActionSheet(user.username); await openActionSheet(user.username);
// Tests if Remove as mute worked // Tests if Remove as mute worked
await waitFor(element(by.label('Mute'))) await waitFor(element(by[textMatcher]('Mute')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await closeActionSheet(); await closeActionSheet();
@ -576,21 +569,21 @@ describe('Room actions screen', () => {
const channelName = `#${data.groups.private.name}`; const channelName = `#${data.groups.private.name}`;
await sendMessage(user, channelName, message); await sendMessage(user, channelName, message);
await openActionSheet(user.username); await openActionSheet(user.username);
await element(by.label('Ignore')).atIndex(0).tap(); await element(by[textMatcher]('Ignore')).atIndex(0).tap();
await waitForToast(); await waitForToast();
await backToActions(); await backToActions();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await waitFor(element(by.label('Message ignored. Tap to display it.')).atIndex(0)) await waitFor(element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await element(by.label('Message ignored. Tap to display it.')).atIndex(0).tap(); await element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0).tap();
await waitFor(element(by.label(message)).atIndex(0)) await waitFor(element(by[textMatcher](message)).atIndex(0))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await element(by.label(message)).atIndex(0).tap(); await element(by[textMatcher](message)).atIndex(0).tap();
}); });
it('should navigate to direct message', async () => { it('should navigate to direct message', async () => {
@ -607,7 +600,7 @@ describe('Room actions screen', () => {
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await openActionSheet(user.username); await openActionSheet(user.username);
await element(by.label('Direct message')).atIndex(0).tap(); await element(by[textMatcher]('Direct message')).atIndex(0).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
@ -630,11 +623,11 @@ describe('Room actions screen', () => {
it('should block/unblock user', async () => { it('should block/unblock user', async () => {
await waitFor(element(by.id('room-actions-block-user'))).toExist(); await waitFor(element(by.id('room-actions-block-user'))).toExist();
await element(by.id('room-actions-block-user')).tap(); await element(by.id('room-actions-block-user')).tap();
await waitFor(element(by.label('Unblock user'))) await waitFor(element(by[textMatcher]('Unblock user')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await element(by.id('room-actions-block-user')).tap(); await element(by.id('room-actions-block-user')).tap();
await waitFor(element(by.label('Block user'))) await waitFor(element(by[textMatcher]('Block user')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
}); });

View File

@ -1,4 +1,4 @@
const { navigateToLogin, login, mockMessage, tapBack, searchRoom } = require('../../helpers/app'); const { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
const channel = data.groups.private.name; const channel = data.groups.private.name;
@ -12,8 +12,10 @@ const navigateToRoom = async () => {
}; };
describe('Discussion', () => { describe('Discussion', () => {
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
}); });
@ -24,12 +26,12 @@ describe('Discussion', () => {
await waitFor(element(by.id('new-message-view'))) await waitFor(element(by.id('new-message-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.label('Create Discussion')).atIndex(0).tap(); await element(by[textMatcher]('Create Discussion')).atIndex(0).tap();
await waitFor(element(by.id('create-discussion-view'))) await waitFor(element(by.id('create-discussion-view')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await expect(element(by.id('create-discussion-view'))).toExist(); await expect(element(by.id('create-discussion-view'))).toExist();
await element(by.label('Select a Channel...')).tap(); await element(by[textMatcher]('Select a Channel...')).tap();
await element(by.id('multi-select-search')).replaceText(`${channel}`); await element(by.id('multi-select-search')).replaceText(`${channel}`);
await waitFor(element(by.id(`multi-select-item-${channel}`))) await waitFor(element(by.id(`multi-select-item-${channel}`)))
.toExist() .toExist()
@ -59,7 +61,7 @@ describe('Discussion', () => {
await waitFor(element(by.id('action-sheet'))) await waitFor(element(by.id('action-sheet')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.label('Create Discussion')).atIndex(0).tap(); await element(by[textMatcher]('Create Discussion')).atIndex(0).tap();
await waitFor(element(by.id('create-discussion-view'))) await waitFor(element(by.id('create-discussion-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
@ -86,11 +88,11 @@ describe('Discussion', () => {
it('should create discussion', async () => { it('should create discussion', async () => {
const discussionName = `${data.random}message`; const discussionName = `${data.random}message`;
await element(by.label(discussionName)).atIndex(0).longPress(); await element(by[textMatcher](discussionName)).atIndex(0).longPress();
await waitFor(element(by.id('action-sheet'))) await waitFor(element(by.id('action-sheet')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.label('Start a Discussion')).atIndex(0).tap(); await element(by[textMatcher]('Start a Discussion')).atIndex(0).tap();
await waitFor(element(by.id('create-discussion-view'))) await waitFor(element(by.id('create-discussion-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
@ -136,6 +138,7 @@ describe('Discussion', () => {
}); });
it('should have starred', async () => { it('should have starred', async () => {
await element(by.id('room-actions-scrollview')).swipe('up', 'slow', 0.5);
await expect(element(by.id('room-actions-starred'))).toBeVisible(); await expect(element(by.id('room-actions-starred'))).toBeVisible();
}); });

View File

@ -1,5 +1,14 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, dismissReviewNag } = require('../../helpers/app'); const {
navigateToLogin,
login,
mockMessage,
tapBack,
sleep,
searchRoom,
platformTypes,
dismissReviewNag
} = require('../../helpers/app');
async function navigateToRoom(roomName) { async function navigateToRoom(roomName) {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
@ -7,21 +16,22 @@ async function navigateToRoom(roomName) {
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); await element(by.id(`rooms-list-view-item-${roomName}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id(`room-view-title-${roomName}`)))
.toBeVisible() .toExist()
.withTimeout(5000); .withTimeout(5000);
} }
describe('Threads', () => { describe('Threads', () => {
const mainRoom = data.groups.private.name; const mainRoom = data.groups.private.name;
let textMatcher;
before(async () => { before(async () => {
({ textMatcher } = platformTypes[device.getPlatform()]);
await navigateToRoom(mainRoom); await navigateToRoom(mainRoom);
}); });
describe('Render', () => { describe('Render', () => {
it('should have room screen', async () => { it('should have room screen', async () => {
await expect(element(by.id('room-view'))).toExist();
await waitFor(element(by.id(`room-view-title-${mainRoom}`))) await waitFor(element(by.id(`room-view-title-${mainRoom}`)))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
@ -69,12 +79,15 @@ describe('Threads', () => {
const thread = `${data.random}thread`; const thread = `${data.random}thread`;
it('should create thread', async () => { it('should create thread', async () => {
await mockMessage('thread'); await mockMessage('thread');
await element(by.label(thread)).atIndex(0).longPress(); await element(by[textMatcher](thread)).atIndex(0).longPress();
await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet'))).toExist();
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Reply in Thread')).atIndex(0).tap(); await element(by[textMatcher]('Reply in Thread')).atIndex(0).tap();
await element(by.id('messagebox-input')).typeText('replied'); await element(by.id('messagebox-input')).replaceText('replied');
await waitFor(element(by.id('messagebox-send-message')))
.toExist()
.withTimeout(2000);
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by.id(`message-thread-button-${thread}`))) await waitFor(element(by.id(`message-thread-button-${thread}`)))
.toExist() .toExist()
@ -84,9 +97,6 @@ describe('Threads', () => {
it('should navigate to thread from button', async () => { it('should navigate to thread from button', async () => {
await element(by.id(`message-thread-button-${thread}`)).tap(); await element(by.id(`message-thread-button-${thread}`)).tap();
await waitFor(element(by.id('room-view')))
.toExist()
.withTimeout(5000);
await waitFor(element(by.id(`room-view-title-${thread}`))) await waitFor(element(by.id(`room-view-title-${thread}`)))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
@ -96,13 +106,9 @@ describe('Threads', () => {
it('should toggle follow thread', async () => { it('should toggle follow thread', async () => {
await element(by.id(`message-thread-button-${thread}`)).tap(); await element(by.id(`message-thread-button-${thread}`)).tap();
await waitFor(element(by.id('room-view')))
.toExist()
.withTimeout(5000);
await waitFor(element(by.id(`room-view-title-${thread}`))) await waitFor(element(by.id(`room-view-title-${thread}`)))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await expect(element(by.id(`room-view-title-${thread}`))).toExist();
await element(by.id('room-view-header-unfollow')).tap(); await element(by.id('room-view-header-unfollow')).tap();
await waitFor(element(by.id('room-view-header-follow'))) await waitFor(element(by.id('room-view-header-follow')))
.toExist() .toExist()
@ -119,14 +125,13 @@ describe('Threads', () => {
const messageText = 'threadonly'; const messageText = 'threadonly';
await mockMessage(messageText, true); await mockMessage(messageText, true);
await tapBack(); await tapBack();
await waitFor(element(by.id('room-header').and(by.label(`${mainRoom}`)))) await waitFor(element(by.id(`room-view-title-${data.random}thread`)))
.toBeVisible() .not.toExist()
.withTimeout(2000); .withTimeout(5000);
await waitFor(element(by.id('room-header').and(by.label(`${data.random}thread`)))) await waitFor(element(by.id(`room-view-title-${mainRoom}`)))
.toBeNotVisible() .toExist()
.withTimeout(2000); .withTimeout(5000);
await sleep(500); // TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( await waitFor(element(by[textMatcher](`${data.random}${messageText}`)).atIndex(0))
await waitFor(element(by.label(`${data.random}${messageText}`)).atIndex(0))
.toNotExist() .toNotExist()
.withTimeout(2000); .withTimeout(2000);
}); });
@ -137,40 +142,39 @@ describe('Threads', () => {
await waitFor(element(by.id('messagebox-input-thread'))) await waitFor(element(by.id('messagebox-input-thread')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('messagebox-input-thread')).typeText(messageText); await element(by.id('messagebox-input-thread')).replaceText(messageText);
await element(by.id('messagebox-send-to-channel')).tap(); await element(by.id('messagebox-send-to-channel')).tap();
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-header').and(by.label(`${mainRoom}`)))) await waitFor(element(by.id(`room-view-title-${data.random}thread`)))
.toBeVisible() .not.toExist()
.withTimeout(2000); .withTimeout(5000);
await waitFor(element(by.id('room-header').and(by.label(`${data.random}thread`)))) await waitFor(element(by.id(`room-view-title-${mainRoom}`)))
.toBeNotVisible() .toExist()
.withTimeout(2000); .withTimeout(5000);
await sleep(500); // TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( await waitFor(element(by[textMatcher](messageText)).atIndex(0))
await waitFor(element(by.label(messageText)).atIndex(0))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
}); });
it('should navigate to thread from thread name', async () => { it('should navigate to thread from thread name', async () => {
const messageText = 'navthreadname'; const messageText = 'navthreadname';
await mockMessage('dummymessagebetweenthethread'); await mockMessage('dummymessagebetweenthethread'); // TODO: Create a proper test for this elsewhere.
await dismissReviewNag(); // TODO: Create a proper test for this elsewhere. await dismissReviewNag();
await element(by.id(`message-thread-button-${thread}`)).tap(); await element(by.id(`message-thread-button-${thread}`)).tap();
await waitFor(element(by.id('messagebox-input-thread'))) await waitFor(element(by.id('messagebox-input-thread')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('messagebox-input-thread')).typeText(messageText); await element(by.id('messagebox-input-thread')).replaceText(messageText);
await element(by.id('messagebox-send-to-channel')).tap(); await element(by.id('messagebox-send-to-channel')).tap();
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-header').and(by.label(`${mainRoom}`)))) await waitFor(element(by.id(`room-view-title-${data.random}thread`)))
.toBeVisible() .not.toExist()
.withTimeout(2000); .withTimeout(5000);
await waitFor(element(by.id('room-header').and(by.label(`${data.random}thread`)))) await waitFor(element(by.id(`room-view-title-${mainRoom}`)))
.toBeNotVisible() .toExist()
.withTimeout(2000); .withTimeout(5000);
await waitFor(element(by.id(`message-thread-replied-on-${thread}`))) await waitFor(element(by.id(`message-thread-replied-on-${thread}`)))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
@ -211,7 +215,7 @@ describe('Threads', () => {
await waitFor(element(by.id(`room-view-title-${thread}`))) await waitFor(element(by.id(`room-view-title-${thread}`)))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('messagebox-input-thread')).typeText(`${thread}draft`); await element(by.id('messagebox-input-thread')).replaceText(`${thread}draft`);
await tapBack(); await tapBack();
await element(by.id(`message-thread-button-${thread}`)).tap(); await element(by.id(`message-thread-button-${thread}`)).tap();

View File

@ -1,9 +1,11 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login } = require('../../helpers/app'); const { navigateToLogin, login, platformTypes } = require('../../helpers/app');
describe('Group DM', () => { describe('Group DM', () => {
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
}); });
@ -29,7 +31,7 @@ describe('Group DM', () => {
describe('Usage', () => { describe('Usage', () => {
it('should navigate to create DM', async () => { it('should navigate to create DM', async () => {
await element(by.label('Create Direct Messages')).tap(); await element(by[textMatcher]('Create Direct Messages')).tap();
}); });
it('should add users', async () => { it('should add users', async () => {

View File

@ -1,5 +1,5 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, searchRoom, sleep } = require('../../helpers/app'); const { navigateToLogin, login, searchRoom, sleep, platformTypes } = require('../../helpers/app');
const { sendMessage } = require('../../helpers/data_setup'); const { sendMessage } = require('../../helpers/data_setup');
async function navigateToRoom(user) { async function navigateToRoom(user) {
@ -12,9 +12,11 @@ async function navigateToRoom(user) {
describe('Mark as unread', () => { describe('Mark as unread', () => {
const user = data.users.alternate.username; const user = data.users.alternate.username;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
await navigateToRoom(user); await navigateToRoom(user);
@ -26,16 +28,16 @@ describe('Mark as unread', () => {
const message = `${data.random}message-mark-as-unread`; const message = `${data.random}message-mark-as-unread`;
const channelName = `@${data.users.regular.username}`; const channelName = `@${data.users.regular.username}`;
await sendMessage(data.users.alternate, channelName, message); await sendMessage(data.users.alternate, channelName, message);
await waitFor(element(by.label(message)).atIndex(0)) await waitFor(element(by[textMatcher](message)).atIndex(0))
.toExist() .toExist()
.withTimeout(30000); .withTimeout(30000);
await sleep(300); await sleep(300);
await element(by.label(message)).atIndex(0).longPress(); await element(by[textMatcher](message)).atIndex(0).longPress();
await waitFor(element(by.id('action-sheet-handle'))) await waitFor(element(by.id('action-sheet-handle')))
.toBeVisible() .toBeVisible()
.withTimeout(3000); .withTimeout(3000);
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.label('Mark Unread')).atIndex(0).tap(); await element(by[textMatcher]('Mark Unread')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);

View File

@ -1,5 +1,5 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app'); const { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes } = require('../../helpers/app');
const privateRoomName = data.groups.private.name; const privateRoomName = data.groups.private.name;
@ -25,17 +25,20 @@ async function navigateToRoomInfo(type) {
.withTimeout(2000); .withTimeout(2000);
} }
async function swipe(direction) {
await element(by.id('room-info-edit-view-list')).swipe(direction, 'fast', 0.8);
}
async function waitForToast() { async function waitForToast() {
// await waitFor(element(by.id('toast'))).toExist().withTimeout(10000);
// await expect(element(by.id('toast'))).toExist();
// await waitFor(element(by.id('toast'))).toBeNotVisible().withTimeout(10000);
// await expect(element(by.id('toast'))).toBeNotVisible();
await sleep(300); await sleep(300);
} }
describe('Room info screen', () => { describe('Room info screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
}); });
@ -72,15 +75,15 @@ describe('Room info screen', () => {
}); });
it('should have description', async () => { it('should have description', async () => {
await expect(element(by.label('Description'))).toExist(); await expect(element(by[textMatcher]('Description'))).toExist();
}); });
it('should have topic', async () => { it('should have topic', async () => {
await expect(element(by.label('Topic'))).toExist(); await expect(element(by[textMatcher]('Topic'))).toExist();
}); });
it('should have announcement', async () => { it('should have announcement', async () => {
await expect(element(by.label('Announcement'))).toExist(); await expect(element(by[textMatcher]('Announcement'))).toExist();
}); });
it('should have edit button', async () => { it('should have edit button', async () => {
@ -124,8 +127,7 @@ describe('Room info screen', () => {
}); });
it('should have type switch', async () => { it('should have type switch', async () => {
// Ugly hack to scroll on detox await swipe('up');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.8);
await expect(element(by.id('room-info-edit-view-t'))).toExist(); await expect(element(by.id('room-info-edit-view-t'))).toExist();
}); });
@ -150,44 +152,33 @@ describe('Room info screen', () => {
}); });
after(async () => { after(async () => {
// Ugly hack to scroll on detox await swipe('down');
await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8);
}); });
}); });
describe('Usage', () => { describe('Usage', () => {
// it('should enter "invalid name" and get error', async() => {
// await element(by.type('UIScrollView')).atIndex(1).swipe('down');
// await element(by.id('room-info-edit-view-name')).replaceText('invalid name');
// await element(by.type('UIScrollView')).atIndex(1).swipe('up');
// await element(by.id('room-info-edit-view-submit')).tap();
// await waitFor(element(by.text('There was an error while saving settings!'))).toExist().withTimeout(60000);
// await expect(element(by.text('There was an error while saving settings!'))).toExist();
// await element(by.text('OK')).tap();
// await waitFor(element(by.text('There was an error while saving settings!'))).toBeNotVisible().withTimeout(10000);
// await element(by.type('UIScrollView')).atIndex(1).swipe('down');
// });
it('should change room name', async () => { it('should change room name', async () => {
await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}new`); await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}new`);
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-info-view'))) await waitFor(element(by.id('room-info-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.id('room-info-view-name'))).toHaveLabel(`${privateRoomName}new`); const matcher = device.getPlatform() === 'android' ? 'toHaveText' : 'toHaveLabel';
await expect(element(by.id('room-info-view-name')))[matcher](`${privateRoomName}new`);
// change name to original // change name to original
await element(by.id('room-info-view-edit-button')).tap(); await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))) await waitFor(element(by.id('room-info-edit-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`); await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`);
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); await swipe('down');
}); });
it('should reset form', async () => { it('should reset form', async () => {
@ -196,10 +187,11 @@ describe('Room info screen', () => {
await element(by.id('room-info-edit-view-topic')).replaceText('abc'); await element(by.id('room-info-edit-view-topic')).replaceText('abc');
await element(by.id('room-info-edit-view-announcement')).replaceText('abc'); await element(by.id('room-info-edit-view-announcement')).replaceText('abc');
await element(by.id('room-info-edit-view-password')).replaceText('abc'); await element(by.id('room-info-edit-view-password')).replaceText('abc');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-t')).tap(); await element(by.id('room-info-edit-view-t')).tap();
await swipe('up');
await element(by.id('room-info-edit-view-ro')).longPress(); // https://github.com/facebook/react-native/issues/28032 await element(by.id('room-info-edit-view-ro')).longPress(); // https://github.com/facebook/react-native/issues/28032
await element(by.id('room-info-edit-view-react-when-ro')).tap(); await element(by.id('room-info-edit-view-react-when-ro')).tap();
await swipe('up');
await element(by.id('room-info-edit-view-reset')).tap(); await element(by.id('room-info-edit-view-reset')).tap();
// after reset // after reset
await expect(element(by.id('room-info-edit-view-name'))).toHaveText(privateRoomName); await expect(element(by.id('room-info-edit-view-name'))).toHaveText(privateRoomName);
@ -207,22 +199,23 @@ describe('Room info screen', () => {
await expect(element(by.id('room-info-edit-view-topic'))).toHaveText(''); await expect(element(by.id('room-info-edit-view-topic'))).toHaveText('');
await expect(element(by.id('room-info-edit-view-announcement'))).toHaveText(''); await expect(element(by.id('room-info-edit-view-announcement'))).toHaveText('');
await expect(element(by.id('room-info-edit-view-password'))).toHaveText(''); await expect(element(by.id('room-info-edit-view-password'))).toHaveText('');
await expect(element(by.id('room-info-edit-view-t'))).toHaveValue('1'); // await swipe('down');
await expect(element(by.id('room-info-edit-view-ro'))).toHaveValue('0'); await expect(element(by.id('room-info-edit-view-t'))).toHaveToggleValue(true);
await expect(element(by.id('room-info-edit-view-ro'))).toHaveToggleValue(false);
await expect(element(by.id('room-info-edit-view-react-when-ro'))).toBeNotVisible(); await expect(element(by.id('room-info-edit-view-react-when-ro'))).toBeNotVisible();
await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); await swipe('down');
}); });
it('should change room description', async () => { it('should change room description', async () => {
await element(by.id('room-info-edit-view-description')).replaceText('new description'); await element(by.id('room-info-edit-view-description')).replaceText('new description');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-info-view'))) await waitFor(element(by.id('room-info-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.label('new description').withAncestor(by.id('room-info-view-description')))).toExist(); await expect(element(by[textMatcher]('new description').withAncestor(by.id('room-info-view-description')))).toExist();
}); });
it('should change room topic', async () => { it('should change room topic', async () => {
@ -234,14 +227,14 @@ describe('Room info screen', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-topic')).replaceText('new topic'); await element(by.id('room-info-edit-view-topic')).replaceText('new topic');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-info-view'))) await waitFor(element(by.id('room-info-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.label('new topic').withAncestor(by.id('room-info-view-topic')))).toExist(); await expect(element(by[textMatcher]('new topic').withAncestor(by.id('room-info-view-topic')))).toExist();
}); });
it('should change room announcement', async () => { it('should change room announcement', async () => {
@ -253,14 +246,14 @@ describe('Room info screen', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement'); await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-info-view'))) await waitFor(element(by.id('room-info-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.label('new announcement').withAncestor(by.id('room-info-view-announcement')))).toExist(); await expect(element(by[textMatcher]('new announcement').withAncestor(by.id('room-info-view-announcement')))).toExist();
}); });
it('should change room password', async () => { it('should change room password', async () => {
@ -271,61 +264,45 @@ describe('Room info screen', () => {
await waitFor(element(by.id('room-info-edit-view'))) await waitFor(element(by.id('room-info-edit-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-password')).replaceText('password'); await element(by.id('room-info-edit-view-password')).replaceText('password');
await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
}); });
it('should change room type', async () => { it('should change room type', async () => {
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await swipe('down');
await element(by.id('room-info-edit-view-t')).tap(); await element(by.id('room-info-edit-view-t')).tap();
await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await swipe('down');
await element(by.id('room-info-edit-view-t')).tap(); await element(by.id('room-info-edit-view-t')).tap();
await swipe('up');
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
}); });
// it('should change room read only and allow reactions', async() => {
// await sleep(1000);
// await element(by.type('UIScrollView')).atIndex(1).swipe('up');
// await element(by.id('room-info-edit-view-ro')).tap();
// await waitFor(element(by.id('room-info-edit-view-react-when-ro'))).toExist().withTimeout(2000);
// await expect(element(by.id('room-info-edit-view-react-when-ro'))).toExist();
// await element(by.id('room-info-edit-view-react-when-ro')).tap();
// await element(by.id('room-info-edit-view-submit')).tap();
// await waitForToast();
// // TODO: test if it's possible to react
// });
it('should archive room', async () => { it('should archive room', async () => {
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-archive')).tap(); await element(by.id('room-info-edit-view-archive')).tap();
await waitFor(element(by.text('Yes, archive it!'))) await waitFor(element(by[textMatcher]('Yes, archive it!')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.text('Yes, archive it!')).tap(); await element(by[textMatcher]('Yes, archive it!').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('room-info-edit-view-unarchive'))) await waitFor(element(by.id('room-info-edit-view-unarchive')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await expect(element(by.id('room-info-edit-view-archive'))).toBeNotVisible(); await expect(element(by.id('room-info-edit-view-archive'))).toBeNotVisible();
// TODO: needs permission to unarchive
// await element(by.id('room-info-edit-view-archive')).tap();
// await waitFor(element(by.text('Yes, unarchive it!'))).toExist().withTimeout(5000);
// await expect(element(by.text('Yes, unarchive it!'))).toExist();
// await element(by.text('Yes, unarchive it!')).tap();
// await waitFor(element(by.text('ARCHIVE'))).toExist().withTimeout(60000);
// await expect(element(by.text('ARCHIVE'))).toExist();
}); });
it('should delete room', async () => { it('should delete room', async () => {
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await swipe('up');
await element(by.id('room-info-edit-view-delete')).tap(); await element(by.id('room-info-edit-view-delete')).tap();
await waitFor(element(by.text('Yes, delete it!'))) await waitFor(element(by[textMatcher]('Yes, delete it!')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.text('Yes, delete it!')).tap(); await element(by[textMatcher]('Yes, delete it!').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);

View File

@ -1,5 +1,5 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, tapBack, login, searchRoom } = require('../../helpers/app'); const { navigateToLogin, tapBack, login, searchRoom, sleep, platformTypes } = require('../../helpers/app');
async function navigateToRoom(roomName) { async function navigateToRoom(roomName) {
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
@ -7,8 +7,14 @@ async function navigateToRoom(roomName) {
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
await waitFor(element(by.id(`room-view-title-${roomName}`)))
.toExist()
.withTimeout(5000);
} }
let textMatcher;
let alertButtonType;
async function clearCache() { async function clearCache() {
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toBeVisible() .toBeVisible()
@ -26,10 +32,10 @@ async function clearCache() {
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('settings-view-clear-cache')).tap(); await element(by.id('settings-view-clear-cache')).tap();
await waitFor(element(by.text('This will clear all your offline data.'))) await waitFor(element(by[textMatcher]('This will clear all your offline data.')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.label('Clear').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by[textMatcher]('Clear').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
@ -39,6 +45,10 @@ async function clearCache() {
} }
async function waitForLoading() { async function waitForLoading() {
if (device.getPlatform() === 'android') {
await sleep(10000);
return; // FIXME: Loading indicator doesn't animate properly on android
}
await waitFor(element(by.id('loading'))) await waitFor(element(by.id('loading')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
@ -50,21 +60,22 @@ async function waitForLoading() {
describe('Room', () => { describe('Room', () => {
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.adminUser, data.adminPassword); await login(data.adminUser, data.adminPassword);
}); });
it('should jump to an old message and load its surroundings', async () => { it('should jump to an old message and load its surroundings', async () => {
await navigateToRoom('jumping'); await navigateToRoom('jumping');
await waitFor(element(by.label('Quote first message'))) await waitFor(element(by[textMatcher]('300')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('1')).atIndex(0).tap(); await element(by[textMatcher]('1')).atIndex(0).tap();
await waitForLoading(); await waitForLoading();
await waitFor(element(by.label('1')).atIndex(0)) await waitFor(element(by[textMatcher]('1')).atIndex(0))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await expect(element(by.label('2'))).toExist(); await expect(element(by[textMatcher]('2'))).toExist();
}); });
it('should tap FAB and scroll to bottom', async () => { it('should tap FAB and scroll to bottom', async () => {
@ -72,7 +83,7 @@ describe('Room', () => {
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('nav-jump-to-bottom')).tap(); await element(by.id('nav-jump-to-bottom')).tap();
await waitFor(element(by.label('Quote first message'))) await waitFor(element(by[textMatcher]('Quote first message')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await clearCache(); await clearCache();
@ -83,14 +94,15 @@ describe('Room', () => {
await waitFor(element(by.id('room-view-messages'))) await waitFor(element(by.id('room-view-messages')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await waitFor(element(by.label('300'))) await waitFor(element(by[textMatcher]('300')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
let found = false; let found = false;
while (!found) { while (!found) {
await element(by.id('room-view-messages')).atIndex(0).scroll(500, 'up');
try { try {
await expect(element(by.label('249'))).toExist(); const direction = device.getPlatform() === 'android' ? 'down' : 'up';
await element(by.id('room-view-messages')).scroll(500, direction);
await expect(element(by[textMatcher]('249'))).toExist();
found = true; found = true;
} catch { } catch {
// //
@ -101,107 +113,130 @@ describe('Room', () => {
it('should search for old message and load its surroundings', async () => { it('should search for old message and load its surroundings', async () => {
await navigateToRoom('jumping'); await navigateToRoom('jumping');
await sleep(1000); // wait for proper load the room
await element(by.id('room-view-search')).tap(); await element(by.id('room-view-search')).tap();
await waitFor(element(by.id('search-messages-view'))) await waitFor(element(by.id('search-messages-view')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('search-message-view-input')).typeText('30\n'); await element(by.id('search-message-view-input')).replaceText('30');
await waitFor(element(by.label('30')).atIndex(0)) await waitFor(element(by[textMatcher]('30')).atIndex(1))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(30000);
await element(by.label('30')).atIndex(0).tap(); await element(by[textMatcher]('30')).atIndex(1).tap();
await waitForLoading(); await waitForLoading();
await expect(element(by.label('30'))).toExist(); await waitFor(element(by[textMatcher]('30')).atIndex(0))
await expect(element(by.label('31'))).toExist(); .toExist()
await expect(element(by.label('32'))).toExist(); .withTimeout(30000);
await expect(element(by[textMatcher]('31'))).toExist();
await expect(element(by[textMatcher]('32'))).toExist();
}); });
it('should load newer and older messages', async () => { it('should load newer and older messages', async () => {
await element(by.id('room-view-messages')).atIndex(0).swipe('down', 'fast', 0.8); // TODO: couldn't make it work on Android :(
await waitFor(element(by.label('5'))) if (device.getPlatform() === 'android') {
.toExist() return;
.withTimeout(10000); }
await waitFor(element(by.label('Load Older'))) let found = false;
.toExist() while (!found) {
.withTimeout(5000); try {
await element(by.label('Load Older')).atIndex(0).tap(); // it doesn't recognize this list
await waitFor(element(by.label('4'))) await element(by.id('room-view-messages')).scroll(500, 'up');
await expect(element(by[textMatcher]('Load Older'))).toBeVisible();
await expect(element(by[textMatcher]('5'))).toExist();
found = true;
} catch {
//
}
}
await element(by[textMatcher]('Load Older')).atIndex(0).tap();
await waitFor(element(by[textMatcher]('4')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('room-view-messages')).atIndex(0).swipe('down', 'fast', 0.5); await element(by.id('room-view-messages')).atIndex(0).swipe('down', 'fast', 0.5);
await waitFor(element(by.label('1'))) await waitFor(element(by[textMatcher]('1')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5); await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5);
await waitFor(element(by.label('25'))) await waitFor(element(by[textMatcher]('25')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5); await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5);
await waitFor(element(by.label('50'))) await waitFor(element(by[textMatcher]('50')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'slow', 0.5); await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'slow', 0.5);
await waitFor(element(by.label('Load Newer'))) await waitFor(element(by[textMatcher]('Load Newer')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('Load Newer')).atIndex(0).tap(); await element(by[textMatcher]('Load Newer')).atIndex(0).tap();
await waitFor(element(by.label('104'))) await waitFor(element(by[textMatcher]('104')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await waitFor(element(by.label('Load Newer'))) await waitFor(element(by[textMatcher]('Load Newer')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('Load Newer')).atIndex(0).tap(); await element(by[textMatcher]('Load Newer')).atIndex(0).tap();
await waitFor(element(by.label('154'))) await waitFor(element(by[textMatcher]('154')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await waitFor(element(by.label('Load Newer'))) await waitFor(element(by[textMatcher]('Load Newer')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('Load Newer')).atIndex(0).tap(); await element(by[textMatcher]('Load Newer')).atIndex(0).tap();
await waitFor(element(by.label('Load Newer'))) await waitFor(element(by[textMatcher]('Load Newer')))
.toNotExist() .toNotExist()
.withTimeout(5000); .withTimeout(5000);
await expect(element(by.label('Load More'))).toNotExist(); await expect(element(by[textMatcher]('Load More'))).toNotExist();
await expect(element(by.label('201'))).toExist(); await expect(element(by[textMatcher]('201'))).toExist();
await expect(element(by.label('202'))).toExist(); await expect(element(by[textMatcher]('202'))).toExist();
await tapBack(); await tapBack();
}); });
}); });
const expectThreadMessages = async message => { const expectThreadMessages = async message => {
await waitFor(element(by.id('room-view-title-jumping-thread'))) await waitFor(element(by.id('room-view-title-thread 1')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await expect(element(by.label(message))).toExist(); await waitForLoading();
await expect(element(by[textMatcher](message)).atIndex(0)).toExist();
await element(by[textMatcher](message)).atIndex(0).tap();
}; };
describe('Threads', () => { describe('Threads', () => {
before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true });
});
it('should navigate to a thread from another room', async () => { it('should navigate to a thread from another room', async () => {
await navigateToRoom('jumping'); await navigateToRoom('jumping');
await waitFor(element(by.label("Go to jumping-thread's thread")).atIndex(0)) await waitFor(element(by[textMatcher]("Go to jumping-thread's thread")).atIndex(0))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label("Go to jumping-thread's thread")).atIndex(0).tap(); await element(by[textMatcher]("Go to jumping-thread's thread")).atIndex(0).tap();
await waitForLoading();
await expectThreadMessages("Go to jumping-thread's thread"); await expectThreadMessages("Go to jumping-thread's thread");
await tapBack(); await tapBack();
}); });
it('should tap on thread message from main room', async () => { it('should tap on thread message from main room', async () => {
await waitFor(element(by.label('thread message sent to main room')).atIndex(0)) await waitFor(element(by.id('room-view-title-jumping-thread')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('thread message sent to main room')).atIndex(0).tap(); await waitFor(element(by[textMatcher]('thread message sent to main room')))
.toExist()
.withTimeout(10000);
await element(by[textMatcher]('thread message sent to main room')).atIndex(0).tap();
await expectThreadMessages('thread message sent to main room'); await expectThreadMessages('thread message sent to main room');
await tapBack(); await tapBack();
}); });
it('should tap on quote', async () => { it('should tap on quote', async () => {
await waitFor(element(by.label('quoted'))) await waitFor(element(by.id('room-view-title-jumping-thread')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.label('quoted')).atIndex(0).tap(); await waitFor(element(by[textMatcher]('quoted')))
.toExist()
.withTimeout(5000);
await element(by[textMatcher]('quoted')).atIndex(0).tap();
await expectThreadMessages('quoted'); await expectThreadMessages('quoted');
await tapBack(); await tapBack();
}); });
@ -214,11 +249,11 @@ describe('Threads', () => {
await waitFor(element(by.id('search-messages-view'))) await waitFor(element(by.id('search-messages-view')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('search-message-view-input')).typeText('to be searched\n'); await element(by.id('search-message-view-input')).replaceText('to be searched');
await waitFor(element(by.label('to be searched'))) await waitFor(element(by[textMatcher]('to be searched')).atIndex(1))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(30000);
await element(by.label('to be searched')).atIndex(1).tap(); await element(by[textMatcher]('to be searched')).atIndex(1).tap();
await expectThreadMessages('to be searched'); await expectThreadMessages('to be searched');
}); });

View File

@ -1,11 +1,14 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login } = require('../../helpers/app'); const { navigateToLogin, login, platformTypes } = require('../../helpers/app');
const teamName = `team-${data.random}`; const teamName = `team-${data.random}`;
describe('Create team screen', () => { describe('Create team screen', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
}); });
@ -41,17 +44,23 @@ describe('Create team screen', () => {
describe('Create Team', () => { describe('Create Team', () => {
describe('Usage', () => { describe('Usage', () => {
it('should get invalid team name', async () => { it('should get invalid team name', async () => {
await element(by.id('create-channel-name')).typeText(`${data.teams.private.name}`); await element(by.id('create-channel-name')).replaceText(`${data.teams.private.name}`);
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(2000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.text('OK'))) await waitFor(element(by[textMatcher]('OK').and(by.type(alertButtonType))))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
}); });
it('should create private team', async () => { it('should create private team', async () => {
await element(by.id('create-channel-name')).replaceText(''); await element(by.id('create-channel-name')).replaceText('');
await element(by.id('create-channel-name')).typeText(teamName); await element(by.id('create-channel-name')).replaceText(teamName);
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(2000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
@ -81,10 +90,10 @@ describe('Create team screen', () => {
await element(by.id('room-info-view-edit-button')).tap(); await element(by.id('room-info-view-edit-button')).tap();
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-delete')).tap(); await element(by.id('room-info-edit-view-delete')).tap();
await waitFor(element(by.text('Yes, delete it!'))) await waitFor(element(by[textMatcher]('Yes, delete it!')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by.text('Yes, delete it!')).tap(); await element(by[textMatcher]('Yes, delete it!').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);

View File

@ -1,5 +1,5 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app'); const { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes } = require('../../helpers/app');
async function navigateToRoom(roomName) { async function navigateToRoom(roomName) {
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
@ -17,6 +17,7 @@ async function openActionSheet(username) {
await sleep(300); await sleep(300);
await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet'))).toExist();
await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up');
} }
async function navigateToRoomActions() { async function navigateToRoomActions() {
@ -37,20 +38,41 @@ async function backToActions() {
} }
async function closeActionSheet() { async function closeActionSheet() {
await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6);
await waitFor(element(by.id('action-sheet-handle')))
.toBeNotVisible()
.withTimeout(3000);
await sleep(200);
} }
async function waitForToast() { async function waitForToast() {
await sleep(1000); await sleep(1000);
} }
async function swipeTillVisible(container, find, direction = 'up', delta = 0.3, speed = 'slow') {
let found = false;
while (!found) {
try {
await element(container).swipe(direction, speed, delta);
await sleep(200);
await expect(element(find)).toBeVisible();
found = true;
} catch (e) {
//
}
}
}
describe('Team', () => { describe('Team', () => {
const team = data.teams.private.name; const team = data.teams.private.name;
const user = data.users.alternate; const user = data.users.alternate;
const room = `private${data.random}-channel-team`; const room = `private${data.random}-channel-team`;
const existingRoom = data.groups.alternate.name; const existingRoom = data.groups.alternate.name;
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
await navigateToRoom(team); await navigateToRoom(team);
@ -86,7 +108,7 @@ describe('Team', () => {
describe('Team Channels Header', () => { describe('Team Channels Header', () => {
it('should have actions button ', async () => { it('should have actions button ', async () => {
await expect(element(by.id('room-header'))).toExist(); await expect(element(by.id('room-header')).atIndex(0)).toExist();
}); });
it('should have team channels button ', async () => { it('should have team channels button ', async () => {
@ -124,6 +146,9 @@ describe('Team', () => {
await element(by.id('add-channel-team-view-create-channel')).tap(); await element(by.id('add-channel-team-view-create-channel')).tap();
await element(by.id('select-users-view-search')).replaceText('rocket.cat'); await element(by.id('select-users-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id('select-users-view-item-rocket.cat')))
.toBeVisible()
.withTimeout(5000);
await element(by.id('select-users-view-item-rocket.cat')).tap(); await element(by.id('select-users-view-item-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))) await waitFor(element(by.id('selected-user-rocket.cat')))
.toBeVisible() .toBeVisible()
@ -134,7 +159,10 @@ describe('Team', () => {
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await element(by.id('create-channel-name')).replaceText(''); await element(by.id('create-channel-name')).replaceText('');
await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-name')).replaceText(room);
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(10000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -156,9 +184,9 @@ describe('Team', () => {
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
await expect(element(by.id(`room-view-title-${room}`))).toExist(); await expect(element(by.id(`room-view-title-${room}`))).toExist();
await expect(element(by.id('room-view-header-team-channels'))).toExist(); await expect(element(by.id('room-view-header-team-channels')).atIndex(0)).toExist();
await expect(element(by.id('room-view-header-threads'))).toExist(); await expect(element(by.id('room-view-header-threads')).atIndex(0)).toExist();
await expect(element(by.id('room-view-search'))).toExist(); await expect(element(by.id('room-view-search')).atIndex(0)).toExist();
await tapBack(); await tapBack();
}); });
@ -186,7 +214,7 @@ describe('Team', () => {
await expect(element(by.id('room-view-header-team-channels'))).toExist(); await expect(element(by.id('room-view-header-team-channels'))).toExist();
await element(by.id('room-view-header-team-channels')).tap(); await element(by.id('room-view-header-team-channels')).tap();
await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`))) await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`)).atIndex(0))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
}); });
@ -195,7 +223,8 @@ describe('Team', () => {
await element(by.id(`rooms-list-view-item-${existingRoom}`)) await element(by.id(`rooms-list-view-item-${existingRoom}`))
.atIndex(0) .atIndex(0)
.longPress(); .longPress();
await sleep(500);
await swipeTillVisible(by.id('action-sheet-remove-from-team'), by.id('action-sheet-delete'));
await waitFor(element(by.id('action-sheet-auto-join'))) await waitFor(element(by.id('action-sheet-auto-join')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
@ -224,7 +253,7 @@ describe('Team', () => {
await waitFor(element(by.id('auto-join-tag'))) await waitFor(element(by.id('auto-join-tag')))
.toBeNotVisible() .toBeNotVisible()
.withTimeout(5000); .withTimeout(5000);
await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`))) await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`)).atIndex(0))
.toExist() .toExist()
.withTimeout(6000); .withTimeout(6000);
}); });
@ -298,22 +327,22 @@ describe('Team', () => {
await waitFor( await waitFor(
element( element(
by.label( by[textMatcher](
'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.' 'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'
) )
) )
) )
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('select-list-view-submit'))) await waitFor(element(by.id('select-list-view-submit')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('select-list-view-submit')).tap(); await element(by.id('select-list-view-submit')).tap();
await waitFor(element(by.text('Last owner cannot be removed'))) await waitFor(element(by[textMatcher]('Last owner cannot be removed')))
.toExist() .toExist()
.withTimeout(8000); .withTimeout(8000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-actions-view'))) await waitFor(element(by.id('room-actions-view')))
.toExist() .toExist()
@ -352,6 +381,9 @@ describe('Team', () => {
it('should remove member from team', async () => { it('should remove member from team', async () => {
await openActionSheet('rocket.cat'); await openActionSheet('rocket.cat');
await waitFor(element(by.id('action-sheet-remove-from-team')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('action-sheet-remove-from-team')).tap(); await element(by.id('action-sheet-remove-from-team')).tap();
await waitFor(element(by.id('select-list-view'))) await waitFor(element(by.id('select-list-view')))
.toExist() .toExist()
@ -406,14 +438,14 @@ describe('Team', () => {
await waitFor( await waitFor(
element( element(
by.label( by[textMatcher](
'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.' 'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'
) )
) )
) )
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.text('OK')).tap(); await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('select-list-view-submit'))) await waitFor(element(by.id('select-list-view-submit')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);

View File

@ -1,5 +1,5 @@
const data = require('../../data'); const data = require('../../data');
const { navigateToLogin, login, tapBack, searchRoom, sleep } = require('../../helpers/app'); const { navigateToLogin, login, tapBack, searchRoom, sleep, platformTypes } = require('../../helpers/app');
const toBeConverted = `to-be-converted-${data.random}`; const toBeConverted = `to-be-converted-${data.random}`;
const toBeMoved = `to-be-moved-${data.random}`; const toBeMoved = `to-be-moved-${data.random}`;
@ -17,7 +17,10 @@ const createChannel = async room => {
await waitFor(element(by.id('create-channel-view'))) await waitFor(element(by.id('create-channel-view')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-name')).replaceText(room);
await waitFor(element(by.id('create-channel-submit')))
.toExist()
.withTimeout(10000);
await element(by.id('create-channel-submit')).tap(); await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
@ -51,8 +54,11 @@ async function navigateToRoomActions(room) {
} }
describe('Move/Convert Team', () => { describe('Move/Convert Team', () => {
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
}); });
@ -69,10 +75,10 @@ describe('Move/Convert Team', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-actions-convert-to-team')).tap(); await element(by.id('room-actions-convert-to-team')).tap();
await waitFor(element(by.label('You are converting this Channel to a Team. All Members will be kept.'))) await waitFor(element(by[textMatcher]('You are converting this Channel to a Team. All Members will be kept.')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.text('Convert')).tap(); await element(by[textMatcher]('Convert').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
.withTimeout(20000); .withTimeout(20000);
@ -101,12 +107,14 @@ describe('Move/Convert Team', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-actions-move-to-team')).tap(); await element(by.id('room-actions-move-to-team')).tap();
await waitFor(element(by.id('select-list-view'))) await waitFor(element(by[textMatcher]('Move to Team')).atIndex(0))
.toExist()
.withTimeout(2000);
await waitFor(element(by.id('select-list-view-submit')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('select-list-view-submit')).tap(); await element(by.id('select-list-view-submit')).tap();
await sleep(2000); await waitFor(element(by[textMatcher]('Select Team')))
await waitFor(element(by.id('select-list-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await waitFor(element(by.id(`select-list-view-item-${toBeConverted}`))) await waitFor(element(by.id(`select-list-view-item-${toBeConverted}`)))
@ -116,14 +124,14 @@ describe('Move/Convert Team', () => {
await element(by.id('select-list-view-submit')).atIndex(0).tap(); await element(by.id('select-list-view-submit')).atIndex(0).tap();
await waitFor( await waitFor(
element( element(
by.label( by[textMatcher](
'After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?' 'After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?'
) )
) )
) )
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.text('Yes, move it!')).tap(); await element(by[textMatcher]('Yes, move it!').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('room-view-header-team-channels'))) await waitFor(element(by.id('room-view-header-team-channels')))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(10000);
@ -141,12 +149,11 @@ describe('Move/Convert Team', () => {
it('should convert a team to a channel', async () => { it('should convert a team to a channel', async () => {
await navigateToRoomActions(toBeConverted); await navigateToRoomActions(toBeConverted);
await element(by.id('room-actions-scrollview')).scrollTo('bottom'); await element(by.id('room-actions-scrollview')).scrollTo('bottom');
await waitFor(element(by.id('room-actions-convert-channel-to-team'))) await waitFor(element(by[textMatcher]('Convert to Channel')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-actions-convert-channel-to-team')).tap(); await element(by[textMatcher]('Convert to Channel')).atIndex(0).tap();
await sleep(2000); await waitFor(element(by[textMatcher]('Converting Team to Channel')))
await waitFor(element(by.id('select-list-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await waitFor(element(by.id(`select-list-view-item-${toBeMoved}`))) await waitFor(element(by.id(`select-list-view-item-${toBeMoved}`)))
@ -157,10 +164,10 @@ describe('Move/Convert Team', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('select-list-view-submit')).tap(); await element(by.id('select-list-view-submit')).tap();
await waitFor(element(by.label('You are converting this Team to a Channel'))) await waitFor(element(by[textMatcher]('You are converting this Team to a Channel')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.text('Convert')).tap(); await element(by[textMatcher]('Convert').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
.toExist() .toExist()
.withTimeout(20000); .withTimeout(20000);

View File

@ -239,6 +239,18 @@
} }
} }
} }
},
"and.emu.debug": {
"device": "Pixel_API_28_AOSP",
"type": "android.emulator",
"binaryPath": "android/app/build/outputs/apk/e2ePlay/debug/app-e2e-play-debug.apk",
"build": "cd android && ./gradlew app:assembleE2ePlayDebug app:assembleE2ePlayDebugAndroidTest -DtestBuildType=debug && cd .."
},
"and.emu.release": {
"device": "Pixel_API_28_AOSP",
"type": "android.emulator",
"binaryPath": "android/app/build/outputs/apk/e2ePlay/release/app-e2e-play-release.apk",
"build": "cd android && ./gradlew app:assembleE2ePlayRelease app:assembleE2ePlayReleaseAndroidTest -DtestBuildType=release && cd .."
} }
} }
} }