Detox tests E2E (#283)

This commit is contained in:
Guilherme Gazzo 2018-05-23 10:39:18 -03:00 committed by GitHub
parent a9acbec05c
commit 182ab69d6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 2485 additions and 205 deletions

View File

@ -35,6 +35,52 @@ jobs:
command: |
npx codecov
e2e-test:
macos:
xcode: "9.0"
environment:
BASH_ENV: "~/.nvm/nvm.sh"
steps:
- checkout
- run:
name: Install Node 8
command: |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
source ~/.nvm/nvm.sh
# https://github.com/creationix/nvm/issues/1394
set +e
nvm install 8
- run:
name: Install appleSimUtils
command: |
brew update
brew tap wix/brew
brew install applesimutils
- run:
name: Install NPM modules
command: |
rm -rf node_modules
npm install
npm install -g detox-cli
- run:
name: Build
command: |
detox build
- run:
name: Test
command: |
detox test
- store_artifacts:
path: /tmp/screenshots
android-build:
<<: *defaults
docker:
@ -215,10 +261,14 @@ workflows:
build-and-test:
jobs:
- lint-testunit
- e2e-test:
requires:
- lint-testunit
- ios-build:
requires:
- lint-testunit
- e2e-test
- ios-testflight:
requires:
- ios-build
@ -235,3 +285,4 @@ workflows:
- android-build:
requires:
- lint-testunit
- e2e-test

View File

@ -1,3 +1,4 @@
__tests__
node_modules
coverage
e2e

View File

@ -38,6 +38,19 @@ Follow the [React Native Getting Started Guide](https://facebook.github.io/react
$ npm run android
```
# Detox (end-to-end tests)
- Build your app
```bash
$ detox build
```
- Run tests
```bash
$ detox test
```
# Storybook
- General requirements
- Install storybook

View File

@ -12,5 +12,6 @@ if (__DEV__) {
.connect();
// Running on android device
// $ adb reverse tcp:9090 tcp:9090
console.warn = Reactotron.log;
// Reactotron.clear();
// console.warn = Reactotron.log;
}

View File

@ -60,7 +60,7 @@ export default class Button extends React.PureComponent {
render() {
const {
title, type, onPress, disabled
title, type, onPress, disabled, ...otherProps
} = this.props;
return (
<Touch
@ -68,6 +68,7 @@ export default class Button extends React.PureComponent {
accessibilityTraits='button'
style={Platform.OS === 'ios' && styles.margin}
disabled={disabled}
{...otherProps}
>
<View
style={[

View File

@ -24,7 +24,11 @@ export default class CloseModalButton extends React.PureComponent {
render() {
return (
<TouchableOpacity onPress={() => this.props.navigation.dispatch(NavigationActions.back())} style={styles.button}>
<TouchableOpacity
onPress={() => this.props.navigation.dispatch(NavigationActions.back())}
style={styles.button}
testID='close-modal-button'
>
<Icon
style={styles.icon}
name='close'

View File

@ -49,6 +49,7 @@ export default class EmojiCategory extends React.Component {
activeOpacity={0.7}
key={emoji.isCustom ? emoji.content : emoji}
onPress={() => this.props.onEmojiSelected(emoji)}
testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`}
>
{renderEmoji(emoji, size)}
</TouchableOpacity>);

View File

@ -20,6 +20,7 @@ export default class TabBar extends React.PureComponent {
key={tab}
onPress={() => this.props.goToPage(i)}
style={styles.tab}
testID={`reaction-picker-${ tab }`}
>
<Text style={[styles.tabEmoji, this.props.tabEmojiStyle]}>{tab}</Text>
{this.props.activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />}

View File

@ -337,6 +337,7 @@ export default class MessageActions extends React.Component {
<ActionSheet
ref={o => this.ActionSheet = o}
title='Messages actions'
testID='message-actions'
options={this.options}
cancelButtonIndex={this.CANCEL_INDEX}
destructiveButtonIndex={this.DELETE_INDEX}

View File

@ -13,7 +13,7 @@ export default class EmojiKeyboard extends React.PureComponent {
render() {
return (
<Provider store={store}>
<View style={styles.emojiKeyboardContainer}>
<View style={styles.emojiKeyboardContainer} testID='messagebox-keyboard-emoji'>
<EmojiPicker onEmojiSelected={emoji => this.onEmojiSelected(emoji)} />
</View>
</Provider>

View File

@ -100,7 +100,8 @@ export default class extends React.PureComponent {
render() {
return (
<SafeAreaView
key='messagebox'
key='messagebox-recording'
testID='messagebox-recording'
style={styles.textBox}
>
<View style={[styles.textArea, { backgroundColor: '#F6F7F9' }]}>

View File

@ -88,7 +88,6 @@ export default class MessageBox extends React.PureComponent {
const regexp = /(#|@|:)([a-z0-9._-]+)$/im;
const result = lastNativeText.substr(0, cursor).match(regexp);
if (!result) {
return this.stopTrackingMention();
}
@ -111,6 +110,7 @@ export default class MessageBox extends React.PureComponent {
accessibilityLabel='Cancel editing'
accessibilityTraits='button'
onPress={() => this.editCancel()}
testID='messagebox-cancel-editing'
/>);
}
return !this.state.showEmojiKeyboard ? (<Icon
@ -119,12 +119,14 @@ export default class MessageBox extends React.PureComponent {
accessibilityLabel='Open emoji selector'
accessibilityTraits='button'
name='mood'
testID='messagebox-open-emoji'
/>) : (<Icon
onPress={() => this.closeEmoji()}
style={styles.actionButtons}
accessibilityLabel='Close emoji selector'
accessibilityTraits='button'
name='keyboard'
testID='messagebox-close-emoji'
/>);
}
get rightButtons() {
@ -138,6 +140,7 @@ export default class MessageBox extends React.PureComponent {
accessibilityLabel='Send message'
accessibilityTraits='button'
onPress={() => this.submit(this.state.text)}
testID='messagebox-send-message'
/>);
return icons;
}
@ -148,6 +151,7 @@ export default class MessageBox extends React.PureComponent {
accessibilityLabel='Send audio message'
accessibilityTraits='button'
onPress={() => this.recordAudioMessage()}
testID='messagebox-send-audio'
/>);
icons.push(<MyIcon
style={[styles.actionButtons, { color: '#2F343D', fontSize: 16 }]}
@ -156,6 +160,7 @@ export default class MessageBox extends React.PureComponent {
accessibilityLabel='Message actions'
accessibilityTraits='button'
onPress={() => this.addFile()}
testID='messagebox-actions'
/>);
return icons;
}
@ -438,6 +443,7 @@ export default class MessageBox extends React.PureComponent {
<TouchableOpacity
style={styles.mentionItem}
onPress={() => this._onPressMention(item)}
testID={`mention-item-${ this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ? item.name || item : item.username || item.name }`}
>
{this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
[
@ -463,14 +469,15 @@ export default class MessageBox extends React.PureComponent {
return null;
}
return (
<View key='messagebox-container' testID='messagebox-container'>
<FlatList
key='messagebox-container'
style={styles.mentionList}
data={mentions}
renderItem={({ item }) => this.renderMentionItem(item)}
keyExtractor={item => item._id || item}
keyboardShouldPersistTaps='always'
/>
</View>
);
};
@ -481,7 +488,11 @@ export default class MessageBox extends React.PureComponent {
return (
[
this.renderMentions(),
<View key='messagebox' style={[styles.textArea, this.props.editing && styles.editing]}>
<View
key='messagebox'
style={[styles.textArea, this.props.editing && styles.editing]}
testID='messagebox'
>
{this.leftButtons}
<TextInput
ref={component => this.component = component}
@ -496,6 +507,7 @@ export default class MessageBox extends React.PureComponent {
defaultValue=''
multiline
placeholderTextColor='#9EA2A8'
testID='messagebox-input'
/>
{this.rightButtons}
</View>

View File

@ -67,7 +67,7 @@ export default class Sidebar extends Component {
onPressItem = (item) => {
this.props.selectServer(item.id);
this.props.navigation.dispatch(DrawerActions.closeDrawer());
this.closeDrawer();
}
getState = () => ({
@ -78,12 +78,17 @@ export default class Sidebar extends Component {
this.setState(this.getState());
}
closeDrawer = () => {
this.props.navigation.dispatch(DrawerActions.closeDrawer());
}
renderItem = ({ item, separators }) => (
<TouchableHighlight
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
onPress={() => { this.onPressItem(item); }}
testID={`sidebar-${ item.id }`}
>
<View style={[styles.serverItem, (item.id === this.props.server ? styles.selectedServer : null)]}>
<Text>
@ -96,14 +101,18 @@ export default class Sidebar extends Component {
render() {
return (
<ScrollView style={styles.scrollView}>
<View style={{ paddingBottom: 20 }}>
<View style={{ paddingBottom: 20 }} testID='sidebar'>
<FlatList
data={this.state.servers}
renderItem={this.renderItem}
keyExtractor={keyExtractor}
/>
<TouchableHighlight
onPress={() => { this.props.logout(); }}
onPress={() => {
this.closeDrawer();
this.props.logout();
}}
testID='sidebar-logout'
>
<View style={styles.serverItem}>
<Text>
@ -112,7 +121,11 @@ export default class Sidebar extends Component {
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => { this.props.navigation.navigate({ key: 'AddServer', routeName: 'AddServer' }); }}
onPress={() => {
this.closeDrawer();
this.props.navigation.navigate({ key: 'AddServer', routeName: 'AddServer' });
}}
testID='sidebar-add-server'
>
<View style={styles.serverItem}>
<Text>

View File

@ -75,22 +75,37 @@ export default class RCTextInput extends React.PureComponent {
showPassword: false
}
icon = ({ name, onPress, style }) => <Icon name={name} style={[styles.icon, style]} size={20} onPress={onPress} />
icon = ({
name,
onPress,
style,
testID
}) => <Icon name={name} style={[styles.icon, style]} size={20} onPress={onPress} testID={testID} />
iconLeft = name => this.icon({ name, onPress: null, style: { left: 0 } });
iconLeft = name => this.icon({
name,
onPress: null,
style: { left: 0 },
testID: this.props.testID ? `${ this.props.testID }-icon-left` : null
});
iconPassword = name => this.icon({ name, onPress: () => this.tooglePassword(), style: { right: 0 } });
iconPassword = name => this.icon({
name,
onPress: () => this.tooglePassword(),
style: { right: 0 },
testID: this.props.testID ? `${ this.props.testID }-icon-right` : null
});
tooglePassword = () => this.setState({ showPassword: !this.state.showPassword });
render() {
const {
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, ...inputProps
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, ...inputProps
} = this.props;
const { showPassword } = this.state;
return (
<View style={[styles.inputContainer, containerStyle]}>
{ label && <Text style={[styles.label, error.error && styles.labelError]}>{label}</Text> }
{label && <Text contentDescription={null} accessibilityLabel={null} style={[styles.label, error.error && styles.labelError]}>{label}</Text> }
<View style={styles.wrap}>
<TextInput
style={[
@ -105,6 +120,10 @@ export default class RCTextInput extends React.PureComponent {
autoCapitalize='none'
underlineColorAndroid='transparent'
secureTextEntry={secureTextEntry && !showPassword}
testID={testID}
accessibilityLabel={placeholder}
placeholder={placeholder}
contentDescription={placeholder}
{...inputProps}
/>
{iconLeft && this.iconLeft(iconLeft)}

View File

@ -270,6 +270,7 @@ export default class Message extends React.Component {
onPress={() => this.onReactionPress(reaction.emoji)}
onLongPress={() => this.onReactionLongPress()}
key={reaction.emoji}
testID={`message-reaction-${ reaction.emoji }`}
>
<View style={[styles.reactionContainer, reactedContainerStyle]}>
<Emoji
@ -292,7 +293,8 @@ export default class Message extends React.Component {
{this.props.item.reactions.map(this.renderReaction)}
<TouchableOpacity
onPress={() => this.props.toggleReactionPicker(this.parseMessage())}
key='add-reaction'
key='message-add-reaction'
testID='message-add-reaction'
style={styles.reactionContainer}
>
<Icon name='insert-emoticon' color='#aaaaaa' size={15} />

View File

@ -40,6 +40,7 @@ async function canOpenRoomDDP(...args) {
export default async function canOpenRoom({ rid, path }) {
const { database: db } = database;
const room = db.objects('subscriptions').filtered('rid == $0', rid);
if (room.length) {
return true;

View File

@ -15,7 +15,7 @@ export const merge = (subscription, room) => {
subscription.joinCodeRequired = room.joinCodeRequired;
if (room.muted && room.muted.length) {
subscription.muted = room.muted.filter(role => role).map(role => ({ value: role }));
subscription.muted = room.muted.filter(user => user).map(user => ({ value: user }));
}
}
if (subscription.roles && subscription.roles.length) {

View File

@ -21,9 +21,13 @@ export const getMessage = (rid, msg = {}) => {
username: reduxStore.getState().login.user.username
}
};
try {
database.write(() => {
database.create('messages', message, true);
});
} catch (error) {
console.warn('getMessage', error);
}
return message;
};

View File

@ -1,6 +1,8 @@
import Random from 'react-native-meteor/lib/Random';
import database from '../../realm';
import { merge } from '../helpers/mergeSubscriptionsRooms';
import protectedFunction from '../helpers/protectedFunction';
import messagesStatus from '../../../constants/messagesStatus';
import log from '../../../utils/log';
export default async function subscribeRooms(id) {
@ -26,7 +28,9 @@ export default async function subscribeRooms(id) {
}, 5000);
};
if (this.ddp) {
if (!this.ddp && this._login) {
loop();
} else {
this.ddp.on('logged', () => {
clearTimeout(timer);
timer = false;
@ -48,7 +52,7 @@ export default async function subscribeRooms(id) {
const [, ev] = ddpMessage.fields.eventName.split('/');
if (/subscriptions/.test(ev)) {
const tpm = merge(data);
return database.write(() => {
database.write(() => {
database.create('subscriptions', tpm, true);
});
}
@ -58,6 +62,25 @@ export default async function subscribeRooms(id) {
merge(sub, data);
});
}
if (/message/.test(ev)) {
const [args] = ddpMessage.fields.args;
const _id = Random.id();
const message = {
_id,
rid: args.rid,
msg: args.msg,
ts: new Date(),
_updatedAt: new Date(),
status: messagesStatus.SENT,
u: {
_id,
username: 'rocket.cat'
}
};
requestAnimationFrame(() => database.write(() => {
database.create('messages', message, true);
}));
}
}));
}

View File

@ -1,7 +1,6 @@
import { AsyncStorage, Platform } from 'react-native';
import { hashPassword } from 'react-native-meteor/lib/utils';
import foreach from 'lodash/forEach';
import Random from 'react-native-meteor/lib/Random';
import RNFetchBlob from 'react-native-fetch-blob';
import reduxStore from './createStore';
@ -23,8 +22,6 @@ import { someoneTyping, roomMessageReceived } from '../actions/room';
import { setRoles } from '../actions/roles';
import Ddp from './ddp';
import normalizeMessage from './methods/helpers/normalizeMessage';
import subscribeRooms from './methods/subscriptions/rooms';
import subscribeRoom from './methods/subscriptions/room';
@ -167,9 +164,11 @@ const RocketChat = {
this.ddp.on('disconnected', protectedFunction(() => {
reduxStore.dispatch(disconnect());
console.warn(this.ddp);
}));
this.ddp.on('stream-room-messages', (ddpMessage) => {
// TODO: debounce
const message = _buildMessage(ddpMessage.fields.args[0]);
requestAnimationFrame(() => reduxStore.dispatch(roomMessageReceived(message)));
});
@ -182,65 +181,66 @@ const RocketChat = {
return reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
}));
this.ddp.on('stream-notify-user', protectedFunction((ddpMessage) => {
const [type, data] = ddpMessage.fields.args;
const [, ev] = ddpMessage.fields.eventName.split('/');
if (/subscriptions/.test(ev)) {
if (data.roles) {
data.roles = data.roles.map(role => ({ value: role }));
}
if (data.blocker) {
data.blocked = true;
} else {
data.blocked = false;
}
if (data.mobilePushNotifications === 'nothing') {
data.notifications = true;
} else {
data.notifications = false;
}
database.write(() => {
database.create('subscriptions', data, true);
});
}
if (/rooms/.test(ev) && type === 'updated') {
const sub = database.objects('subscriptions').filtered('rid == $0', data._id)[0];
// this.ddp.on('stream-notify-user', protectedFunction((ddpMessage) => {
// console.warn('rc.stream-notify-user')
// const [type, data] = ddpMessage.fields.args;
// const [, ev] = ddpMessage.fields.eventName.split('/');
// if (/subscriptions/.test(ev)) {
// if (data.roles) {
// data.roles = data.roles.map(role => ({ value: role }));
// }
// if (data.blocker) {
// data.blocked = true;
// } else {
// data.blocked = false;
// }
// if (data.mobilePushNotifications === 'nothing') {
// data.notifications = true;
// } else {
// data.notifications = false;
// }
// database.write(() => {
// database.create('subscriptions', data, true);
// });
// }
// if (/rooms/.test(ev) && type === 'updated') {
// const sub = database.objects('subscriptions').filtered('rid == $0', data._id)[0];
database.write(() => {
sub.roomUpdatedAt = data._updatedAt;
sub.lastMessage = normalizeMessage(data.lastMessage);
sub.ro = data.ro;
sub.description = data.description;
sub.topic = data.topic;
sub.announcement = data.announcement;
sub.reactWhenReadOnly = data.reactWhenReadOnly;
sub.archived = data.archived;
sub.joinCodeRequired = data.joinCodeRequired;
if (data.muted) {
sub.muted = data.muted.map(m => ({ value: m }));
}
});
}
if (/message/.test(ev)) {
const [args] = ddpMessage.fields.args;
const _id = Random.id();
const message = {
_id,
rid: args.rid,
msg: args.msg,
ts: new Date(),
_updatedAt: new Date(),
status: messagesStatus.SENT,
u: {
_id,
username: 'rocket.cat'
}
};
requestAnimationFrame(() => database.write(() => {
database.create('messages', message, true);
}));
}
}));
// database.write(() => {
// sub.roomUpdatedAt = data._updatedAt;
// sub.lastMessage = normalizeMessage(data.lastMessage);
// sub.ro = data.ro;
// sub.description = data.description;
// sub.topic = data.topic;
// sub.announcement = data.announcement;
// sub.reactWhenReadOnly = data.reactWhenReadOnly;
// sub.archived = data.archived;
// sub.joinCodeRequired = data.joinCodeRequired;
// if (data.muted) {
// sub.muted = data.muted.map(m => ({ value: m }));
// }
// });
// }
// if (/message/.test(ev)) {
// const [args] = ddpMessage.fields.args;
// const _id = Random.id();
// const message = {
// _id,
// rid: args.rid,
// msg: args.msg,
// ts: new Date(),
// _updatedAt: new Date(),
// status: messagesStatus.SENT,
// u: {
// _id,
// username: 'rocket.cat'
// }
// };
// requestAnimationFrame(() => database.write(() => {
// database.create('messages', message, true);
// }));
// }
// }));
this.ddp.on('rocketchat_starred_message', protectedFunction((ddpMessage) => {
if (ddpMessage.msg === 'added') {

View File

@ -171,7 +171,8 @@ export default class RoomItem extends React.Component {
onLongPress: PropTypes.func,
user: PropTypes.object,
avatarSize: PropTypes.number,
statusStyle: ViewPropTypes.style
statusStyle: ViewPropTypes.style,
testID: PropTypes.string
}
static defaultProps = {
@ -238,7 +239,7 @@ export default class RoomItem extends React.Component {
render() {
const {
favorite, unread, userMentions, name, _updatedAt, alert, type
favorite, unread, userMentions, name, _updatedAt, alert, type, testID
} = this.props;
const date = this.formatDate(_updatedAt);
@ -266,6 +267,7 @@ export default class RoomItem extends React.Component {
activeOpacity={0.5}
accessibilityLabel={accessibilityLabel}
accessibilityTraits='selected'
testID={testID}
>
<View style={[styles.container, favorite && styles.favorite]}>
{this.icon}

View File

@ -164,7 +164,7 @@ const watchLoginOpen = function* watchLoginOpen() {
}
const sub = yield RocketChat.subscribe('meteor.loginServiceConfiguration');
yield take(types.LOGIN.CLOSE);
sub.unsubscribe();
yield sub.unsubscribe().catch(err => console.warn(err));
} catch (e) {
log('watchLoginOpen', e);
}

View File

@ -140,12 +140,16 @@ const updateLastOpen = function* updateLastOpen() {
const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) {
NavigationService.goRoomsList();
yield delay(1000);
try {
database.write(() => {
const messages = database.objects('messages').filtered('rid = $0', rid);
database.delete(messages);
const subscription = database.objects('subscriptions').filtered('rid = $0', rid);
database.delete(subscription);
});
} catch (error) {
console.warn('goRoomsListAndDelete', error);
}
};
const handleLeaveRoom = function* handleLeaveRoom({ rid }) {

View File

@ -62,7 +62,7 @@ export default class CreateChannelView extends LoggedView {
}
return (
<Text style={[styles.label_white, styles.label_error]}>
<Text style={[styles.label_white, styles.label_error]} testID='create-channel-error'>
{this.props.createChannel.error.reason}
</Text>
);
@ -75,6 +75,7 @@ export default class CreateChannelView extends LoggedView {
style={[{ flexGrow: 0, flexShrink: 1 }]}
value={this.state.type}
onValueChange={type => this.setState({ type })}
testID='create-channel-type'
/>
<Text style={[styles.label_white, styles.switchLabel]}>
{this.state.type ? 'Public' : 'Private'}
@ -90,7 +91,7 @@ export default class CreateChannelView extends LoggedView {
keyboardVerticalOffset={128}
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView>
<SafeAreaView testID='create-channel-view'>
<RCTextInput
label='Channel Name'
value={this.state.channelName}
@ -98,6 +99,7 @@ export default class CreateChannelView extends LoggedView {
placeholder='Type the channel name here'
returnKeyType='done'
autoFocus
testID='create-channel-name'
/>
{this.renderChannelNameError()}
{this.renderTypeSwitch()}
@ -126,6 +128,7 @@ export default class CreateChannelView extends LoggedView {
? styles.disabledButton
: styles.enabledButton
]}
testID='create-channel-submit'
>
<Text style={styles.button_white}>CREATE</Text>
</TouchableOpacity>

View File

@ -80,7 +80,7 @@ export default class ForgotPasswordView extends LoggedView {
keyboardVerticalOffset={128}
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView>
<SafeAreaView testID='forgot-password-view'>
<View style={styles.loginView}>
<View style={styles.formContainer}>
<TextInput
@ -91,6 +91,7 @@ export default class ForgotPasswordView extends LoggedView {
returnKeyType='next'
onChangeText={email => this.validate(email)}
onSubmitEditing={() => this.resetPassword()}
testID='forgot-password-view-email'
/>
<View style={styles.alignItemsFlexStart}>
@ -98,6 +99,7 @@ export default class ForgotPasswordView extends LoggedView {
title='Reset password'
type='primary'
onPress={this.resetPassword}
testID='forgot-password-view-submit'
/>
</View>

View File

@ -209,7 +209,7 @@ class ListServerView extends LoggedView {
render() {
return (
<SafeAreaView style={styles.view}>
<SafeAreaView style={styles.view} testID='list-server-view'>
<SectionList
style={styles.list}
sections={this.state.sections}

View File

@ -277,7 +277,7 @@ export default class LoginSignupView extends LoggedView {
style={[sharedStyles.container, sharedStyles.containerScrollView]}
{...scrollPersistTaps}
>
<SafeAreaView>
<SafeAreaView testID='welcome-view'>
<View style={styles.container}>
<Image
source={require('../static/images/logo.png')}
@ -294,11 +294,13 @@ export default class LoginSignupView extends LoggedView {
title='I have an account'
type='primary'
onPress={() => this.props.navigation.navigate({ key: 'Login', routeName: 'Login' })}
testID='welcome-view-login'
/>
<Button
title='Create account'
type='secondary'
onPress={() => this.props.navigation.navigate({ key: 'Register', routeName: 'Register' })}
testID='welcome-view-register'
/>
{this.renderServices()}
</View>

View File

@ -83,7 +83,7 @@ export default class LoginView extends LoggedView {
key='login-view'
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView>
<SafeAreaView testID='login-view'>
<CloseModalButton navigation={this.props.navigation} />
<Text style={[styles.loginText, styles.loginTitle]}>Login</Text>
<TextInput
@ -94,6 +94,7 @@ export default class LoginView extends LoggedView {
iconLeft='at'
onChangeText={username => this.setState({ username })}
onSubmitEditing={() => { this.password.focus(); }}
testID='login-view-email'
/>
<TextInput
@ -105,6 +106,7 @@ export default class LoginView extends LoggedView {
secureTextEntry
onSubmitEditing={this.submit}
onChangeText={password => this.setState({ password })}
testID='login-view-password'
/>
{this.renderTOTP()}
@ -114,17 +116,20 @@ export default class LoginView extends LoggedView {
title='Login'
type='primary'
onPress={this.submit}
testID='login-view-submit'
/>
<Text style={[styles.loginText, { marginTop: 10 }]}>New in Rocket.Chat? &nbsp;
<Text
style={{ color: COLOR_BUTTON_PRIMARY }}
style={[styles.loginText, { marginTop: 10 }]}
testID='login-view-register'
onPress={() => this.props.navigation.navigate('Register')}
>Sign Up
>New in Rocket.Chat? &nbsp;
<Text style={{ color: COLOR_BUTTON_PRIMARY }}>Sign Up
</Text>
</Text>
<Text
style={[styles.loginText, { marginTop: 20, fontSize: 13 }]}
onPress={() => this.props.navigation.navigate('ForgotPassword')}
testID='login-view-forgot-password'
>Forgot password
</Text>
</View>

View File

@ -73,7 +73,7 @@ export default class MentionedMessagesView extends LoggedView {
}
renderEmpty = () => (
<View style={styles.listEmptyContainer}>
<View style={styles.listEmptyContainer} testID='mentioned-messages-view'>
<Text>No mentioned messages</Text>
</View>
)
@ -99,8 +99,10 @@ export default class MentionedMessagesView extends LoggedView {
}
return (
[
<FlatList
key='mentioned-messages-view-list'
testID='mentioned-messages-view'
data={messages}
renderItem={this.renderItem}
style={styles.list}
@ -109,6 +111,7 @@ export default class MentionedMessagesView extends LoggedView {
ListHeaderComponent={loading && <RCActivityIndicator />}
ListFooterComponent={loadingMore && <RCActivityIndicator />}
/>
]
);
}
}

View File

@ -106,7 +106,7 @@ export default class NewServerView extends LoggedView {
keyboardVerticalOffset={128}
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView>
<SafeAreaView testID='new-server-view'>
<Text style={[styles.loginText, styles.loginTitle]}>Sign in your server</Text>
<TextInput
inputRef={e => this.input = e}
@ -115,6 +115,7 @@ export default class NewServerView extends LoggedView {
placeholder={this.state.defaultServer}
returnKeyType='done'
onChangeText={this.onChangeText}
testID='new-server-view-input'
onSubmitEditing={this.submit}
/>
{this.renderValidation()}
@ -124,6 +125,7 @@ export default class NewServerView extends LoggedView {
type='primary'
onPress={this.submit}
disabled={!validInstance}
testID='new-server-view-button'
/>
</View>
<Loading visible={this.props.addingServer} />

View File

@ -97,7 +97,7 @@ export default class PinnedMessagesView extends LoggedView {
}
renderEmpty = () => (
<View style={styles.listEmptyContainer}>
<View style={styles.listEmptyContainer} testID='pinned-messages-view'>
<Text>No pinned messages</Text>
</View>
)
@ -126,6 +126,7 @@ export default class PinnedMessagesView extends LoggedView {
[
<FlatList
key='pinned-messages-view-list'
testID='pinned-messages-view'
data={messages}
renderItem={this.renderItem}
style={styles.list}

View File

@ -108,6 +108,7 @@ export default class RegisterView extends LoggedView {
iconLeft='account'
onChangeText={name => this.setState({ name })}
onSubmitEditing={() => { this.email.focus(); }}
testID='register-view-name'
/>
<TextInput
inputRef={(e) => { this.email = e; }}
@ -119,6 +120,7 @@ export default class RegisterView extends LoggedView {
onChangeText={email => this.setState({ email })}
onSubmitEditing={() => { this.password.focus(); }}
error={this.invalidEmail()}
testID='register-view-email'
/>
<TextInput
inputRef={(e) => { this.password = e; }}
@ -129,6 +131,7 @@ export default class RegisterView extends LoggedView {
secureTextEntry
onChangeText={password => this.setState({ password })}
onSubmitEditing={() => { this.confirmPassword.focus(); }}
testID='register-view-password'
/>
<TextInput
inputRef={(e) => { this.confirmPassword = e; }}
@ -144,6 +147,7 @@ export default class RegisterView extends LoggedView {
secureTextEntry
onChangeText={confirmPassword => this.setState({ confirmPassword })}
onSubmitEditing={this.submit}
testID='register-view-repeat-password'
/>
<View style={styles.alignItemsFlexStart}>
@ -157,10 +161,9 @@ export default class RegisterView extends LoggedView {
title='Register'
type='primary'
onPress={this.submit}
testID='register-view-submit'
/>
</View>
{this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>}
</View>
);
}
@ -179,6 +182,7 @@ export default class RegisterView extends LoggedView {
iconLeft='at'
onChangeText={username => this.setState({ username })}
onSubmitEditing={() => { this.usernameSubmit(); }}
testID='register-view-username'
/>
<View style={styles.alignItemsFlexStart}>
@ -186,10 +190,9 @@ export default class RegisterView extends LoggedView {
title='Register'
type='primary'
onPress={this.usernameSubmit}
testID='register-view-submit-username'
/>
</View>
{this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>}
</View>
);
}
@ -198,11 +201,16 @@ export default class RegisterView extends LoggedView {
return (
<KeyboardView contentContainerStyle={styles.container}>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView>
<SafeAreaView testID='register-view'>
<CloseModalButton navigation={this.props.navigation} />
<Text style={[styles.loginText, styles.loginTitle]}>Sign Up</Text>
{this._renderRegister()}
{this._renderUsername()}
{this.props.login.failure &&
<Text style={styles.error} testID='register-view-error'>
{this.props.login.error.reason}
</Text>
}
<Loading visible={this.props.login.isFetching} />
</SafeAreaView>
</ScrollView>

View File

@ -134,13 +134,24 @@ export default class RoomActionsView extends LoggedView {
icon: 'ios-star',
name: 'USER',
route: 'RoomInfo',
params: { rid }
params: { rid },
testID: 'room-actions-info'
}],
renderItem: this.renderRoomInfo
}, {
data: [
{ icon: 'ios-call-outline', name: 'Voice call', disabled: true },
{ icon: 'ios-videocam-outline', name: 'Video call', disabled: true }
{
icon: 'ios-call-outline',
name: 'Voice call',
disabled: true,
testID: 'room-actions-voice'
},
{
icon: 'ios-videocam-outline',
name: 'Video call',
disabled: true,
testID: 'room-actions-video'
}
],
renderItem: this.renderItem
}, {
@ -149,43 +160,55 @@ export default class RoomActionsView extends LoggedView {
icon: 'ios-attach',
name: 'Files',
route: 'RoomFiles',
params: { rid }
params: { rid },
testID: 'room-actions-files'
},
{
icon: 'ios-at-outline',
name: 'Mentions',
route: 'MentionedMessages',
params: { rid }
params: { rid },
testID: 'room-actions-mentioned'
},
{
icon: 'ios-star-outline',
name: 'Starred',
route: 'StarredMessages',
params: { rid }
params: { rid },
testID: 'room-actions-starred'
},
{
icon: 'ios-search',
name: 'Search',
route: 'SearchMessages',
params: { rid }
params: { rid },
testID: 'room-actions-search'
},
{
icon: 'ios-share-outline',
name: 'Share',
disabled: true,
testID: 'room-actions-share'
},
{ icon: 'ios-share-outline', name: 'Share', disabled: true },
{
icon: 'ios-pin',
name: 'Pinned',
route: 'PinnedMessages',
params: { rid }
params: { rid },
testID: 'room-actions-pinned'
},
{
icon: 'ios-code',
name: 'Snippets',
route: 'SnippetedMessages',
params: { rid }
params: { rid },
testID: 'room-actions-snippeted'
},
{
icon: `ios-notifications${ notifications ? '' : '-off' }-outline`,
name: `${ notifications ? 'Enable' : 'Disable' } notifications`,
event: () => this.toggleNotifications()
event: () => this.toggleNotifications(),
testID: 'room-actions-notifications'
}
],
renderItem: this.renderItem
@ -198,7 +221,8 @@ export default class RoomActionsView extends LoggedView {
icon: 'block',
name: `${ blocked ? 'Unblock' : 'Block' } user`,
type: 'danger',
event: () => this.toggleBlockUser()
event: () => this.toggleBlockUser(),
testID: 'room-actions-block-user'
}
],
renderItem: this.renderItem
@ -209,7 +233,8 @@ export default class RoomActionsView extends LoggedView {
name: 'Members',
description: (onlineMembers.length === 1 ? `${ onlineMembers.length } member` : `${ onlineMembers.length } members`),
route: 'RoomMembers',
params: { rid, members: onlineMembers }
params: { rid, members: onlineMembers },
testID: 'room-actions-members'
}];
if (this.canAddUser) {
@ -229,7 +254,8 @@ export default class RoomActionsView extends LoggedView {
this.props.setLoadingInvite(false);
}
}
}
},
testID: 'room-actions-add-user'
});
}
sections[2].data = [...actions, ...sections[2].data];
@ -239,7 +265,8 @@ export default class RoomActionsView extends LoggedView {
icon: 'block',
name: 'Leave channel',
type: 'danger',
event: () => this.leaveChannel()
event: () => this.leaveChannel(),
testID: 'room-actions-leave-channel'
}
],
renderItem: this.renderItem
@ -248,7 +275,7 @@ export default class RoomActionsView extends LoggedView {
return sections;
}
toggleBlockUser = () => {
toggleBlockUser = async() => {
const { rid, blocked } = this.state.room;
const { member } = this.state;
try {
@ -316,6 +343,7 @@ export default class RoomActionsView extends LoggedView {
activeOpacity={0.5}
accessibilityLabel={item.name}
accessibilityTraits='button'
testID={item.testID}
>
<View style={[styles.sectionItem, item.disabled && styles.sectionItemDisabled]}>
{subview}
@ -348,6 +376,7 @@ export default class RoomActionsView extends LoggedView {
render() {
return (
<View testID='room-actions-view'>
<SectionList
style={styles.container}
stickySectionHeadersEnabled={false}
@ -355,7 +384,9 @@ export default class RoomActionsView extends LoggedView {
SectionSeparatorComponent={this.renderSectionSeparator}
ItemSeparatorComponent={renderSeparator}
keyExtractor={item => item.name}
testID='room-actions-list'
/>
</View>
);
}
}

View File

@ -73,7 +73,7 @@ export default class RoomFilesView extends LoggedView {
}
renderEmpty = () => (
<View style={styles.listEmptyContainer}>
<View style={styles.listEmptyContainer} testID='room-files-view'>
<Text>No files</Text>
</View>
)
@ -97,8 +97,10 @@ export default class RoomFilesView extends LoggedView {
const { loading, loadingMore } = this.state;
return (
[
<FlatList
key='room-files-view-list'
testID='room-files-view'
data={messages}
renderItem={this.renderItem}
style={styles.list}
@ -107,6 +109,7 @@ export default class RoomFilesView extends LoggedView {
ListHeaderComponent={loading && <RCActivityIndicator />}
ListFooterComponent={loadingMore && <RCActivityIndicator />}
/>
]
);
}
}

View File

@ -13,12 +13,13 @@ export default class SwitchContainer extends React.PureComponent {
leftLabelSecondary: PropTypes.string,
rightLabelPrimary: PropTypes.string,
rightLabelSecondary: PropTypes.string,
onValueChange: PropTypes.func
onValueChange: PropTypes.func,
testID: PropTypes.string
}
render() {
const {
value, disabled, onValueChange, leftLabelPrimary, leftLabelSecondary, rightLabelPrimary, rightLabelSecondary
value, disabled, onValueChange, leftLabelPrimary, leftLabelSecondary, rightLabelPrimary, rightLabelSecondary, testID
} = this.props;
return (
[
@ -32,6 +33,7 @@ export default class SwitchContainer extends React.PureComponent {
onValueChange={onValueChange}
value={value}
disabled={disabled}
testID={testID}
/>
<View style={styles.switchLabelContainer}>
<Text style={styles.switchLabelPrimary}>{rightLabelPrimary}</Text>

View File

@ -74,9 +74,9 @@ export default class RoomInfoEditView extends LoggedView {
this.rooms.removeAllListeners();
}
updateRoom = () => {
updateRoom = async() => {
const [room] = this.rooms;
this.setState({ room });
await this.setState({ room });
}
init = () => {
@ -259,8 +259,12 @@ export default class RoomInfoEditView extends LoggedView {
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView>
<ScrollView
contentContainerStyle={sharedStyles.containerScrollView}
testID='room-info-edit-view-list'
{...scrollPersistTaps}
>
<SafeAreaView testID='room-info-edit-view'>
<View style={sharedStyles.formContainer}>
<RCTextInput
inputRef={(e) => { this.name = e; }}
@ -269,6 +273,7 @@ export default class RoomInfoEditView extends LoggedView {
onChangeText={value => this.setState({ name: value })}
onSubmitEditing={() => { this.description.focus(); }}
error={nameError}
testID='room-info-edit-view-name'
/>
<RCTextInput
inputRef={(e) => { this.description = e; }}
@ -276,6 +281,7 @@ export default class RoomInfoEditView extends LoggedView {
value={description}
onChangeText={value => this.setState({ description: value })}
onSubmitEditing={() => { this.topic.focus(); }}
testID='room-info-edit-view-description'
/>
<RCTextInput
inputRef={(e) => { this.topic = e; }}
@ -283,6 +289,7 @@ export default class RoomInfoEditView extends LoggedView {
value={topic}
onChangeText={value => this.setState({ topic: value })}
onSubmitEditing={() => { this.announcement.focus(); }}
testID='room-info-edit-view-topic'
/>
<RCTextInput
inputRef={(e) => { this.announcement = e; }}
@ -290,6 +297,7 @@ export default class RoomInfoEditView extends LoggedView {
value={announcement}
onChangeText={value => this.setState({ announcement: value })}
onSubmitEditing={() => { this.joinCode.focus(); }}
testID='room-info-edit-view-announcement'
/>
<RCTextInput
inputRef={(e) => { this.joinCode = e; }}
@ -298,6 +306,7 @@ export default class RoomInfoEditView extends LoggedView {
onChangeText={value => this.setState({ joinCode: value })}
onSubmitEditing={this.submit}
secureTextEntry
testID='room-info-edit-view-password'
/>
<SwitchContainer
value={t}
@ -306,6 +315,7 @@ export default class RoomInfoEditView extends LoggedView {
rightLabelPrimary='Private'
rightLabelSecondary='Just invited people can access this channel'
onValueChange={value => this.setState({ t: value })}
testID='room-info-edit-view-t'
/>
<SwitchContainer
value={ro}
@ -315,6 +325,7 @@ export default class RoomInfoEditView extends LoggedView {
rightLabelSecondary='Only authorized users can write new messages'
onValueChange={value => this.setState({ ro: value })}
disabled={!this.permissions[PERMISSION_SET_READONLY]}
testID='room-info-edit-view-ro'
/>
{ro &&
<SwitchContainer
@ -325,12 +336,14 @@ export default class RoomInfoEditView extends LoggedView {
rightLabelSecondary='Reactions are enabled'
onValueChange={value => this.setState({ reactWhenReadOnly: value })}
disabled={!this.permissions[PERMISSION_SET_REACT_WHEN_READONLY]}
testID='room-info-edit-view-react-when-ro'
/>
}
<TouchableOpacity
style={[sharedStyles.buttonContainer, !this.formIsChanged() && styles.buttonContainerDisabled]}
onPress={this.submit}
disabled={!this.formIsChanged()}
testID='room-info-edit-view-submit'
>
<Text style={sharedStyles.button} accessibilityTraits='button'>SAVE</Text>
</TouchableOpacity>
@ -338,6 +351,7 @@ export default class RoomInfoEditView extends LoggedView {
<TouchableOpacity
style={[sharedStyles.buttonContainer_inverted, styles.buttonInverted, { flex: 1 }]}
onPress={this.reset}
testID='room-info-edit-view-reset'
>
<Text style={sharedStyles.button_inverted} accessibilityTraits='button'>RESET</Text>
</TouchableOpacity>
@ -350,6 +364,7 @@ export default class RoomInfoEditView extends LoggedView {
]}
onPress={this.toggleArchive}
disabled={!this.hasArchivePermission()}
testID='room-info-edit-view-archive'
>
<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>
{ room.archived ? 'UNARCHIVE' : 'ARCHIVE' }
@ -366,6 +381,7 @@ export default class RoomInfoEditView extends LoggedView {
]}
onPress={this.delete}
disabled={!this.hasDeletePermission()}
testID='room-info-edit-view-delete'
>
<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>DELETE</Text>
</TouchableOpacity>

View File

@ -20,7 +20,10 @@ import RoomTypeIcon from '../../containers/RoomTypeIcon';
const PERMISSION_EDIT_ROOM = 'edit-room';
const camelize = str => str.replace(/^(.)/, (match, chr) => chr.toUpperCase());
const getRoomTitle = room => (room.t === 'd' ? <Text>{room.fname}</Text> : <Text><RoomTypeIcon type={room.t} />&nbsp;{room.name}</Text>);
const getRoomTitle = room => (room.t === 'd' ?
<Text testID='room-info-view-name'>{room.fname}</Text> :
[<RoomTypeIcon type={room.t} />, <Text testID='room-info-view-name'>{room.name}</Text>]
);
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
user: state.login.user,
@ -52,6 +55,7 @@ export default class RoomInfoView extends LoggedView {
activeOpacity={0.5}
accessibilityLabel='edit'
accessibilityTraits='button'
testID='room-info-view-edit-button'
>
<View style={styles.headerButton}>
<MaterialIcon name='edit' size={20} />
@ -129,7 +133,11 @@ export default class RoomInfoView extends LoggedView {
renderItem = (key, room) => (
<View style={styles.item}>
<Text style={styles.itemLabel}>{camelize(key)}</Text>
<Text style={[styles.itemContent, !room[key] && styles.itemContent__empty]}>{ room[key] ? room[key] : `No ${ key } provided.` }</Text>
<Text
style={[styles.itemContent, !room[key] && styles.itemContent__empty]}
testID={`room-info-view-${ key }`}
>{ room[key] ? room[key] : `No ${ key } provided.` }
</Text>
</View>
);
@ -183,9 +191,9 @@ export default class RoomInfoView extends LoggedView {
}
return (
<ScrollView style={styles.container}>
<View style={styles.avatarContainer}>
<View style={styles.avatarContainer} testID='room-info-view'>
{this.renderAvatar(room, roomUser)}
<Text style={styles.roomTitle}>{ getRoomTitle(room) }</Text>
<View style={styles.roomTitle}>{ getRoomTitle(room) }</View>
</View>
{!this.isDirect() && this.renderItem('description', room)}
{!this.isDirect() && this.renderItem('topic', room)}

View File

@ -31,7 +31,8 @@ export default StyleSheet.create({
},
roomTitle: {
fontSize: 18,
paddingTop: 20
paddingTop: 20,
flexDirection: 'row'
},
roomDescription: {
fontSize: 14,

View File

@ -39,6 +39,7 @@ export default class MentionedMessagesView extends LoggedView {
accessibilityLabel={label}
accessibilityTraits='button'
style={styles.headerButtonTouchable}
testID='room-members-view-toggle-status'
>
<View style={styles.headerButton}>
<Text style={styles.headerButtonText}>{label}</Text>
@ -168,6 +169,7 @@ export default class MentionedMessagesView extends LoggedView {
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
testID='room-members-view-search'
/>
</View>
)
@ -185,6 +187,7 @@ export default class MentionedMessagesView extends LoggedView {
showLastMessage={false}
avatarSize={30}
statusStyle={styles.status}
testID={`room-members-view-item-${ item.username }`}
/>
)
@ -194,6 +197,7 @@ export default class MentionedMessagesView extends LoggedView {
[
<FlatList
key='room-members-view-list'
testID='room-members-view'
data={filtering ? membersFiltered : members}
renderItem={this.renderItem}
style={styles.list}

View File

@ -136,7 +136,8 @@ export default class RoomHeaderView extends React.PureComponent {
style={styles.titleContainer}
accessibilityLabel={accessibilityLabel}
accessibilityTraits='header'
onPress={() => this.props.navigation.navigate({ key: 'RoomInfo', routeName: 'RoomInfo', params: this.state.room })}
onPress={() => this.props.navigation.navigate({ key: 'RoomInfo', routeName: 'RoomInfo', params: { rid: this.state.room.rid } })}
testID='room-view-header-title'
>
<Avatar
@ -151,10 +152,12 @@ export default class RoomHeaderView extends React.PureComponent {
}
</Avatar>
<View style={styles.titleTextContainer}>
<Text style={styles.title} allowFontScaling={false}>
<RoomTypeIcon type={this.state.room.t} size={13} />&nbsp;
<View style={{ flexDirection: 'row' }}>
<RoomTypeIcon type={this.state.room.t} size={13} />
<Text style={styles.title} allowFontScaling={false} testID='room-view-title'>
{this.state.room.name}
</Text>
</View>
{ t && <Text style={styles.userStatus} allowFontScaling={false} numberOfLines={1}>{t}</Text>}
@ -176,6 +179,7 @@ export default class RoomHeaderView extends React.PureComponent {
}}
accessibilityLabel='Star room'
accessibilityTraits='button'
testID='room-view-header-star'
>
<Icon
name={`${ Platform.OS === 'ios' ? 'ios' : 'md' }-star${ this.state.room.f ? '' : '-outline' }`}
@ -189,6 +193,7 @@ export default class RoomHeaderView extends React.PureComponent {
onPress={() => this.props.navigation.navigate({ key: 'RoomActions', routeName: 'RoomActions', params: { rid: this.state.room.rid } })}
accessibilityLabel='Room actions'
accessibilityTraits='button'
testID='room-view-header-actions'
>
<Icon
name={Platform.OS === 'ios' ? 'ios-more' : 'md-more'}
@ -202,7 +207,7 @@ export default class RoomHeaderView extends React.PureComponent {
render() {
return (
<View style={styles.header}>
<View style={styles.header} testID='room-view-header'>
{this.renderLeft()}
{this.renderCenter()}
{this.renderRight()}

View File

@ -77,6 +77,7 @@ export class List extends React.Component {
renderRow={(item, previousItem) => this.props.renderRow(item, previousItem)}
initialListSize={20}
pageSize={20}
testID='room-view-messages'
{...scrollPersistTaps}
/>);
}

View File

@ -48,7 +48,10 @@ export default class ReactionPicker extends React.Component {
animationIn='fadeIn'
animationOut='fadeOut'
>
<View style={[styles.reactionPickerContainer, { width: width - margin, height: Math.min(width, height) - (margin * 2) }]}>
<View
style={[styles.reactionPickerContainer, { width: width - margin, height: Math.min(width, height) - (margin * 2) }]}
testID='reaction-picker'
>
<EmojiPicker
tabEmojiStyle={tabEmojiStyle}
width={Math.min(width, height) - margin}

View File

@ -74,8 +74,8 @@ export default class RoomView extends LoggedView {
this.onReactionPress = this.onReactionPress.bind(this);
}
async componentDidMount() {
await this.updateRoom();
componentDidMount() {
this.updateRoom();
this.rooms.addListener(this.updateRoom);
}
shouldComponentUpdate(nextProps, nextState) {
@ -197,7 +197,7 @@ export default class RoomView extends LoggedView {
}
render() {
return (
<View style={styles.container}>
<View style={styles.container} testID='room-view'>
<List
key='room-view-messages'
end={this.state.end}

View File

@ -121,7 +121,13 @@ export default class RoomsListHeaderView extends React.PureComponent {
}
return (
<View style={styles.left} accessible accessibilityLabel="Server's list" accessibilityTraits='button'>
<View
style={styles.left}
accessible
accessibilityLabel={`Connected to ${ this.props.baseUrl }. Tap to view servers list.`}
accessibilityTraits='button'
testID='rooms-list-view-sidebar'
>
<TouchableOpacity
style={styles.headerButton}
onPress={() => this.props.navigation.openDrawer()}
@ -150,10 +156,15 @@ export default class RoomsListHeaderView extends React.PureComponent {
const t = title(offline, connecting, authenticating, logged);
const accessibilityLabel = `${ user.username }, ${ this.getUserStatusLabel() }, double tap to change status`;
const accessibilityLabel = `${ user.username }, ${ this.getUserStatusLabel() }, tap to change status`;
return (
<TouchableOpacity style={styles.titleContainer} onPress={() => this.showModal()} accessibilityLabel={accessibilityLabel} accessibilityTraits='header'>
<TouchableOpacity
style={styles.titleContainer}
onPress={() => this.showModal()}
accessibilityLabel={accessibilityLabel}
accessibilityTraits='header'
testID='rooms-list-view-user'
>
<Avatar
text={user.username}
size={24}
@ -195,6 +206,7 @@ export default class RoomsListHeaderView extends React.PureComponent {
onPress={() => this.createChannel()}
accessibilityLabel='Create channel'
accessibilityTraits='button'
testID='rooms-list-view-create-channel'
>
<Icon
name='ios-add'
@ -215,6 +227,7 @@ export default class RoomsListHeaderView extends React.PureComponent {
<TouchableOpacity
style={styles.modalButton}
onPress={() => this.onPressModalButton(status)}
testID={`rooms-list-view-user-presence-${ status }`}
>
<View style={statusStyle} />
<Text style={textStyle}>
@ -251,7 +264,7 @@ export default class RoomsListHeaderView extends React.PureComponent {
render() {
return (
<View style={styles.header}>
<View style={styles.header} testID='rooms-list-view-header'>
{this.renderLeft()}
{this.renderCenter()}
{this.renderRight()}
@ -262,6 +275,7 @@ export default class RoomsListHeaderView extends React.PureComponent {
style={{ alignItems: 'center' }}
onModalHide={() => this.hideModal()}
onBackdropPress={() => this.hideModal()}
testID='rooms-list-view-user-presence-modal'
>
<View style={styles.modal}>
{this.renderModalButton('online')}

View File

@ -158,6 +158,7 @@ export default class RoomsListView extends LoggedView {
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
testID='rooms-list-view-search'
/>
</View>
);
@ -177,6 +178,7 @@ export default class RoomsListView extends LoggedView {
type={item.t}
baseUrl={this.props.Site_Url}
onPress={() => this._onPressItem(item)}
testID={`rooms-list-view-item-${ item.name }`}
/>);
}
@ -191,6 +193,7 @@ export default class RoomsListView extends LoggedView {
enableEmptySections
removeClippedSubviews
keyboardShouldPersistTaps='always'
testID='rooms-list-view-list'
/>
)
@ -203,7 +206,7 @@ export default class RoomsListView extends LoggedView {
);
render = () => (
<View style={styles.container}>
<View style={styles.container} testID='rooms-list-view'>
{this.renderList()}
{Platform.OS === 'android' && this.renderCreateButtons()}
</View>)

View File

@ -109,6 +109,7 @@ export default class SearchMessagesView extends LoggedView {
return (
<View
style={styles.container}
testID='search-messages-view'
>
<View style={styles.searchContainer}>
<RCTextInput
@ -116,6 +117,7 @@ export default class SearchMessagesView extends LoggedView {
label='Search'
onChangeText={this.onChangeSearch}
placeholder='Search Messages'
testID='search-message-view-input'
/>
<Markdown msg='You can search using RegExp. e.g. `/^text$/i`' />
<View style={styles.divider} />

View File

@ -103,6 +103,7 @@ export default class SelectedUsersView extends LoggedView {
onPress={() => params.nextAction()}
accessibilityLabel='Submit'
accessibilityTraits='button'
testID='selected-users-view-submit'
>
<Icon
name='ios-add'
@ -229,6 +230,7 @@ export default class SelectedUsersView extends LoggedView {
placeholder='Search'
clearButtonMode='while-editing'
blurOnSubmit
testID='select-users-view-search'
autoCorrect={false}
autoCapitalize='none'
/>
@ -255,6 +257,7 @@ export default class SelectedUsersView extends LoggedView {
key={item._id}
style={styles.selectItemView}
onPress={() => this._onPressSelectedItem(item)}
testID={`selected-user-${ item.name }`}
>
<Avatar text={item.name} size={40} />
<Text ellipsizeMode='tail' numberOfLines={1} style={{ fontSize: 10 }}>
@ -273,6 +276,7 @@ export default class SelectedUsersView extends LoggedView {
showLastMessage={false}
avatarSize={30}
statusStyle={styles.status}
testID={`select-users-view-item-${ item.name }`}
/>
);
renderList = () => (
@ -300,7 +304,7 @@ export default class SelectedUsersView extends LoggedView {
);
};
render = () => (
<View style={styles.container}>
<View style={styles.container} testID='select-users-view'>
<SafeAreaView style={styles.safeAreaView}>
{this.renderList()}
{this.renderCreateButton()}

View File

@ -73,7 +73,7 @@ export default class SnippetedMessagesView extends LoggedView {
}
renderEmpty = () => (
<View style={styles.listEmptyContainer}>
<View style={styles.listEmptyContainer} testID='snippeted-messages-view'>
<Text>No snippeted messages</Text>
</View>
)
@ -99,8 +99,10 @@ export default class SnippetedMessagesView extends LoggedView {
}
return (
[
<FlatList
key='snippet-messages-view-list'
key='snippeted-messages-view-list'
testID='snippeted-messages-view'
data={messages}
renderItem={this.renderItem}
style={styles.list}
@ -109,6 +111,7 @@ export default class SnippetedMessagesView extends LoggedView {
ListHeaderComponent={loading && <RCActivityIndicator />}
ListFooterComponent={loadingMore && <RCActivityIndicator />}
/>
]
);
}
}

View File

@ -97,7 +97,7 @@ export default class StarredMessagesView extends LoggedView {
}
renderEmpty = () => (
<View style={styles.listEmptyContainer}>
<View style={styles.listEmptyContainer} testID='starred-messages-view'>
<Text>No starred messages</Text>
</View>
)
@ -126,6 +126,7 @@ export default class StarredMessagesView extends LoggedView {
[
<FlatList
key='starred-messages-view-list'
testID='starred-messages-view'
data={messages}
renderItem={this.renderItem}
style={styles.list}

50
e2e/00-addserver.spec.js Normal file
View File

@ -0,0 +1,50 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
describe('Add server', () => {
before(async() => {
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', async() => {
it('should have add server screen', async() => {
await expect(element(by.id('new-server-view'))).toBeVisible();
});
it('should have server input', async() => {
await expect(element(by.id('new-server-view-input'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('new-server-view-button'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
it('should insert "invalidtest" and get an invalid instance', async() => {
await element(by.id('new-server-view-input')).replaceText('invalidtest');
await waitFor(element(by.text(' is not a valid Rocket.Chat instance'))).toBeVisible().withTimeout(60000);
await expect(element(by.text(' is not a valid Rocket.Chat instance'))).toBeVisible();
});
it('should have a button to add a new server', async() => {
await element(by.id('new-server-view-input')).replaceText(data.server);
await waitFor(element(by.text(' is a valid Rocket.Chat instance'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-server-view-button'))).toBeVisible();
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});
});

46
e2e/01-welcome.spec.js Normal file
View File

@ -0,0 +1,46 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
describe('Welcome screen', () => {
describe('Render', async() => {
it('should have welcome screen', async() => {
await expect(element(by.id('welcome-view'))).toBeVisible();
});
it('should have register button', async() => {
await expect(element(by.id('welcome-view-register'))).toBeVisible();
});
it('should have login button', async() => {
await expect(element(by.id('welcome-view-login'))).toBeVisible();
});
// TODO: oauth
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
it('should navigate to login', async() => {
await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('login-view'))).toBeVisible();
await element(by.id('close-modal-button')).tap();
});
it('should navigate to register', async() => {
await element(by.id('welcome-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
await element(by.id('close-modal-button')).tap();
});
afterEach(async() => {
takeScreenshot();
});
});
});

View File

@ -0,0 +1,45 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
describe('Forgot password screen', () => {
before(async() => {
await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await element(by.id('login-view-forgot-password')).tap();
await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', async() => {
it('should have forgot password screen', async() => {
await expect(element(by.id('forgot-password-view'))).toBeVisible();
});
it('should have email input', async() => {
await expect(element(by.id('forgot-password-view-email'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('forgot-password-view-submit'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
it('should reset password and navigate to login', async() => {
await element(by.id('forgot-password-view-email')).replaceText(data.email);
await element(by.id('forgot-password-view-submit')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('login-view'))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});
});

148
e2e/03-createuser.spec.js Normal file
View File

@ -0,0 +1,148 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const { logout } = require('./helpers/app');
const data = require('./data');
async function navigateToRegister() {
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await element(by.id('welcome-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
}
describe('Create user screen', () => {
before(async() => {
await device.reloadReactNative();
await navigateToRegister();
});
describe('Render', () => {
it('should have create user screen', async() => {
await expect(element(by.id('register-view'))).toBeVisible();
});
it('should have name input', async() => {
await expect(element(by.id('register-view-name'))).toBeVisible();
});
it('should have email input', async() => {
await expect(element(by.id('register-view-email'))).toBeVisible();
});
it('should have password input', async() => {
await expect(element(by.id('register-view-password'))).toBeVisible();
});
it('should have show password icon', async() => {
await expect(element(by.id('register-view-password-icon-right'))).toBeVisible();
});
it('should have repeat password input', async() => {
await expect(element(by.id('register-view-repeat-password'))).toBeVisible();
});
it('should have repeat password icon', async() => {
await expect(element(by.id('register-view-repeat-password-icon-right'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('register-view-submit'))).toBeVisible();
});
it('should have close modal', async() => {
await expect(element(by.id('close-modal-button'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', () => {
it('should navigate to welcome', async() => {
await element(by.id('close-modal-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible();
});
it('should submit empty form and raise error', async() => {
await navigateToRegister();
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.text('Some field is invalid or empty'))).toBeVisible().withTimeout(10000);
await expect(element(by.text('Some field is invalid or empty'))).toBeVisible();
});
it('should submit different passwords and raise error', async() => {
await element(by.id('register-view-name')).replaceText(data.user);
await element(by.id('register-view-email')).replaceText(data.email);
await element(by.id('register-view-password')).replaceText('abc');
await element(by.id('register-view-repeat-password')).replaceText('xyz');
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.text('Some field is invalid or empty'))).toBeVisible().withTimeout(10000);
await expect(element(by.text('Some field is invalid or empty'))).toBeVisible();
});
it('should submit invalid email and raise error', async() => {
await element(by.id('register-view-name')).replaceText(data.user);
await element(by.id('register-view-email')).replaceText('invalidemail');
await element(by.id('register-view-password')).replaceText(data.password);
await element(by.id('register-view-repeat-password')).replaceText(data.password);
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('register-view-error'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('register-view-error'))).toBeVisible();
await expect(element(by.id('register-view-error'))).toHaveText('Invalid email invalidemail');
});
it('should submit email already taken and raise error', async() => {
await element(by.id('register-view-name')).replaceText(data.user);
await element(by.id('register-view-email')).replaceText('diego.mello@rocket.chat');
await element(by.id('register-view-password')).replaceText(data.password);
await element(by.id('register-view-repeat-password')).replaceText(data.password);
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('register-view-error'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('register-view-error'))).toBeVisible();
await expect(element(by.id('register-view-error'))).toHaveText('Email already exists.');
});
it('should complete first part of register', async() => {
await element(by.id('register-view-name')).replaceText(data.user);
await element(by.id('register-view-email')).replaceText(data.email);
await element(by.id('register-view-password')).replaceText(data.password);
await element(by.id('register-view-repeat-password')).replaceText(data.password);
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('register-view-username'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('register-view-username'))).toBeVisible();
});
it('should submit empty username and raise error', async() => {
await element(by.id('register-view-submit-username')).tap();
await waitFor(element(by.text('Username is empty'))).toBeVisible().withTimeout(10000);
await expect(element(by.text('Username is empty'))).toBeVisible();
});
it('should submit already taken username and raise error', async() => {
await element(by.id('register-view-username')).replaceText('diego.mello');
await element(by.id('register-view-submit-username')).tap();
await waitFor(element(by.id('register-view-error'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('register-view-error'))).toBeVisible();
});
it('should finish register', async() => {
await element(by.id('register-view-username')).replaceText(data.user);
await element(by.id('register-view-submit-username')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
// TODO: terms and privacy
afterEach(async() => {
takeScreenshot();
});
after(async() => {
await logout();
});
});
});

92
e2e/04-login.spec.js Normal file
View File

@ -0,0 +1,92 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const { navigateToLogin } = require('./helpers/app');
const data = require('./data');
describe('Login screen', () => {
before(async() => {
await navigateToLogin();
});
describe('Render', () => {
it('should have login screen', async() => {
await expect(element(by.id('login-view'))).toBeVisible();
});
it('should have email input', async() => {
await expect(element(by.id('login-view-email'))).toBeVisible();
});
it('should have password input', async() => {
await expect(element(by.id('login-view-password'))).toBeVisible();
});
it('should have show password icon', async() => {
await expect(element(by.id('login-view-password-icon-right'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('login-view-submit'))).toBeVisible();
});
it('should have register button', async() => {
await expect(element(by.id('login-view-register'))).toBeVisible();
});
it('should have forgot password button', async() => {
await expect(element(by.id('login-view-forgot-password'))).toBeVisible();
});
it('should have close modal button', async() => {
await expect(element(by.id('close-modal-button'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', () => {
it('should navigate to register', async() => {
await element(by.id('login-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
await element(by.id('close-modal-button').withAncestor(by.id('register-view'))).tap();
});
it('should navigate to forgot password', async() => {
await element(by.id('login-view-forgot-password')).tap();
await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('forgot-password-view'))).toBeVisible();
await element(by.id('header-back')).tap();
});
it('should navigate to welcome', async() => {
await element(by.id('close-modal-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible();
await navigateToLogin();
});
it('should insert wrong password and get error', async() => {
await element(by.id('login-view-email')).replaceText(data.user);
await element(by.id('login-view-password')).replaceText('error');
await element(by.id('login-view-submit')).tap();
await waitFor(element(by.text('User or Password incorrect'))).toBeVisible().withTimeout(10000);
await expect(element(by.text('User or Password incorrect'))).toBeVisible();
});
it('should login with success', async() => {
await element(by.id('login-view-password')).replaceText(data.password);
await element(by.id('login-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});
});

112
e2e/05-roomslist.spec.js Normal file
View File

@ -0,0 +1,112 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const { login, navigateToLogin } = require('./helpers/app');
const data = require('./data');
describe('Rooms list screen', () => {
before(async() => {
await device.reloadReactNative(); // TODO: remove this after fix logout subscription
});
describe('Render', async() => {
it('should have rooms list screen', async() => {
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
it('should have rooms list', async() => {
await expect(element(by.id('rooms-list-view-list'))).toBeVisible();
});
it('should have room item', async() => {
await expect(element(by.id('rooms-list-view-item-general'))).toExist();
});
// Render - Header
describe('Header', async() => {
it('should have header', async() => {
await expect(element(by.id('rooms-list-view-header'))).toBeVisible();
});
it('should have create channel button', async() => {
await expect(element(by.id('rooms-list-view-create-channel'))).toBeVisible();
});
it('should have user', async() => {
await expect(element(by.id('rooms-list-view-user'))).toBeVisible();
});
it('should have sidebar button', async() => {
await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible();
await expect(element(by.id('rooms-list-view-sidebar'))).toHaveLabel(`Connected to ${ data.server }. Tap to view servers list.`);
});
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
it('should change user presence modal', async() => {
await waitFor(element(by.label(`${ data.user }, Online, tap to change status`))).toBeVisible().withTimeout(60000);
await expect(element(by.label(`${ data.user }, Online, tap to change status`))).toBeVisible();
await element(by.id('rooms-list-view-user')).tap();
await waitFor(element(by.id('rooms-list-view-user-presence-modal'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view-user-presence-modal'))).toBeVisible();
await element(by.id('rooms-list-view-user-presence-busy')).tap();
await waitFor(element(by.id('rooms-list-view-user-presence-modal'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.id('rooms-list-view-user-presence-modal'))).toBeNotVisible();
await waitFor(element(by.label(`${ data.user }, Busy, tap to change status`))).toBeVisible().withTimeout(60000);
await expect(element(by.label(`${ data.user }, Busy, tap to change status`))).toBeVisible();
});
it('should search room and navigate', async() => {
await element(by.id('rooms-list-view-list')).swipe('down');
await waitFor(element(by.id('rooms-list-view-search'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view-search'))).toBeVisible();
await element(by.id('rooms-list-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(10000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible();
await element(by.id('rooms-list-view-item-rocket.cat')).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(10000);
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.id('room-view-title'))).toHaveText('rocket.cat').withTimeout(60000);
await expect(element(by.id('room-view-title'))).toHaveText('rocket.cat');
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible();
});
// Usage - Sidebar
describe('Sidebar', async() => {
it('should navigate to add server', async() => {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-add-server')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('new-server-view'))).toBeVisible();
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
it('should logout', async() => {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-logout')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
await navigateToLogin();
await login();
});
});
afterEach(async() => {
takeScreenshot();
});
});
});

109
e2e/06-createroom.spec.js Normal file
View File

@ -0,0 +1,109 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
describe('Create room screen', () => {
before(async() => {
await device.reloadReactNative(); // TODO: remove this after fix logout subscription
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', async() => {
it('should have select users screen', async() => {
await expect(element(by.id('select-users-view'))).toBeVisible();
});
it('should have search input', async() => {
await expect(element(by.id('select-users-view-search'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
it('should back to rooms list', async() => {
await element(by.id('header-back')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
});
it('should search users', async() => {
await element(by.id('select-users-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id(`select-users-view-item-rocket.cat`))).toBeVisible().withTimeout(10000);
await expect(element(by.id(`select-users-view-item-rocket.cat`))).toBeVisible();
});
it('should select/unselect user', async() => {
await element(by.id('select-users-view-item-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(5000);
await expect(element(by.id('selected-user-rocket.cat'))).toBeVisible();
await expect(element(by.id('selected-users-view-submit'))).toBeVisible();
await element(by.id('selected-user-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeNotVisible().withTimeout(5000);
await expect(element(by.id('selected-user-rocket.cat'))).toBeNotVisible();
await expect(element(by.id('selected-users-view-submit'))).toBeNotVisible();
await element(by.id('select-users-view-item-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(5000);
});
it('should navigate to create channel view', async() => {
await element(by.id('selected-users-view-submit')).tap();
await waitFor(element(by.id('create-channel-view'))).toBeVisible().withTimeout(5000);
await expect(element(by.id('create-channel-view'))).toBeVisible();
await expect(element(by.id('create-channel-name'))).toBeVisible();
await expect(element(by.id('create-channel-type'))).toBeVisible();
await expect(element(by.id('create-channel-submit'))).toBeVisible();
});
it('should get invalid room', async() => {
await element(by.id('create-channel-name')).replaceText('general');
await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('create-channel-error'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('create-channel-error'))).toBeVisible();
});
it('should create private room', async() => {
await element(by.id('create-channel-name')).replaceText(`private${ data.random }`);
await element(by.id('create-channel-type')).tap();
await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.id('room-view-title'))).toHaveText(`private${ data.random }`).withTimeout(60000);
await expect(element(by.id('room-view-title'))).toHaveText(`private${ data.random }`);
await element(by.id('header-back')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible();
});
it('should create public room', async() => {
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
await element(by.id('select-users-view-item-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(5000);
await element(by.id('selected-users-view-submit')).tap();
await waitFor(element(by.id('create-channel-view'))).toBeVisible().withTimeout(5000);
await element(by.id('create-channel-name')).replaceText(`public${ data.random }`);
await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.id('room-view-title'))).toHaveText(`public${ data.random }`).withTimeout(60000);
await expect(element(by.id('room-view-title'))).toHaveText(`public${ data.random }`);
await element(by.id('header-back')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});
});

318
e2e/07-room.spec.js Normal file
View File

@ -0,0 +1,318 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
async function mockMessage(message) {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(`${ data.random }${ message }`);
await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by.text(`${ data.random }${ message }`))).toBeVisible().withTimeout(60000);
};
async function navigateToRoom() {
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000);
await element(by.id(`rooms-list-view-item-private${ data.random }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
}
describe('Room screen', () => {
before(async() => {
await navigateToRoom();
});
describe('Render', async() => {
it('should have room screen', async() => {
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
describe('Header', async() => {
it('should have room header', async() => {
await expect(element(by.id('room-view-header'))).toBeVisible();
});
it('should have back button', async() => {
await expect(element(by.id('header-back'))).toBeVisible();
});
it('should have title', async() => {
await expect(element(by.id('room-view-header-title'))).toBeVisible();
await expect(element(by.id('room-view-title'))).toHaveText(`private${ data.random }`);
});
it('should have star button', async() => {
await expect(element(by.id('room-view-header-star'))).toBeVisible();
});
it('should have actions button ', async() => {
await expect(element(by.id('room-view-header-actions'))).toBeVisible();
});
});
// Render - Messagebox
describe('Messagebox', async() => {
it('should have messagebox', async() => {
await expect(element(by.id('messagebox'))).toBeVisible();
});
it('should have open emoji button', async() => {
await expect(element(by.id('messagebox-open-emoji'))).toBeVisible();
});
it('should have message input', async() => {
await expect(element(by.id('messagebox-input'))).toBeVisible();
});
it('should have audio button', async() => {
await expect(element(by.id('messagebox-send-audio'))).toBeVisible();
});
it('should have actions button', async() => {
await expect(element(by.id('messagebox-actions'))).toBeVisible();
});
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
describe('Header', async() => {
it('should back to rooms list', async() => {
await element(by.id('header-back')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await navigateToRoom();
});
it('should tap on title and navigate to room info', async() => {
await element(by.id('room-view-header-title')).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-info-view'))).toBeVisible();
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
});
it('should tap on more and navigate to room actions', async() => {
await element(by.id('room-view-header-actions')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-actions-view'))).toBeVisible();
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
});
});
describe('Messagebox', async() => {
it('should send message', async() => {
await mockMessage('message');
await waitFor(element(by.text(`${ data.random }message`))).toBeVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }message`))).toBeVisible();
});
it('should show/hide emoji keyboard', async() => {
await element(by.id('messagebox-open-emoji')).tap();
await waitFor(element(by.id('messagebox-keyboard-emoji'))).toBeVisible().withTimeout(10000);
await expect(element(by.id('messagebox-keyboard-emoji'))).toBeVisible();
await expect(element(by.id('messagebox-close-emoji'))).toBeVisible();
await expect(element(by.id('messagebox-open-emoji'))).toBeNotVisible();
await element(by.id('messagebox-close-emoji')).tap();
await waitFor(element(by.id('messagebox-keyboard-emoji'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.id('messagebox-keyboard-emoji'))).toBeNotVisible();
await expect(element(by.id('messagebox-close-emoji'))).toBeNotVisible();
await expect(element(by.id('messagebox-open-emoji'))).toBeVisible();
});
it('should show/hide emoji autocomplete', async() => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).replaceText(':');
await element(by.id('messagebox-input')).typeText('joy'); // workaround for number keyboard
await waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(10000);
await expect(element(by.id('messagebox-container'))).toBeVisible();
await element(by.id('messagebox-input')).clearText();
await waitFor(element(by.id('messagebox-container'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.id('messagebox-container'))).toBeNotVisible();
});
it('should show and tap on emoji autocomplete', async() => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).replaceText(':');
await element(by.id('messagebox-input')).typeText('joy'); // workaround for number keyboard
await waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(10000);
await expect(element(by.id('messagebox-container'))).toBeVisible();
await element(by.id('mention-item-joy')).tap();
await expect(element(by.id('messagebox-input'))).toHaveText(':joy: ');
await element(by.id('messagebox-input')).clearText();
});
it('should show and tap on user autocomplete and send mention', async() => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(`@${ data.user }`);
await waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('messagebox-container'))).toBeVisible();
await element(by.id(`mention-item-${ data.user }`)).tap();
await expect(element(by.id('messagebox-input'))).toHaveText(`@${ data.user } `);
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText('test');
await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by.text(`@${ data.user } test`))).toBeVisible().withTimeout(60000);
});
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 waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('messagebox-container'))).toBeVisible();
await element(by.id('mention-item-general')).tap();
await expect(element(by.id('messagebox-input'))).toHaveText('#general ');
await element(by.id('messagebox-input')).clearText();
});
});
describe('Message', async() => {
before(async() => {
await mockMessage('reply');
await mockMessage('edit');
await mockMessage('quote');
});
it('should show message actions', async() => {
await element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Cancel')).tap();
await waitFor(element(by.text('Cancel'))).toBeNotVisible().withTimeout(2000);
});
it('should reply message', async() => {
await element(by.text(`${ data.random }reply`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Reply')).tap();
await element(by.id('messagebox-input')).typeText('replied');
await element(by.id('messagebox-send-message')).tap();
// TODO: test if reply was sent
});
it('should edit message', async() => {
await element(by.text(`${ data.random }edit`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Edit')).tap();
await element(by.id('messagebox-input')).typeText('ed');
await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by.text(`${ data.random }edited`))).toBeVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }edited`))).toBeVisible();
});
it('should copy permalink', async() => {
await element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Copy Permalink')).tap();
await expect(element(by.text('Permalink copied to clipboard!'))).toBeVisible();
await waitFor(element(by.text('Permalink copied to clipboard!'))).toBeNotVisible().withTimeout(5000);
// TODO: test clipboard
});
it('should copy message', async() => {
await element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Copy Message')).tap();
await expect(element(by.text('Copied to clipboard!'))).toBeVisible();
await waitFor(element(by.text('Copied to clipboard!'))).toBeNotVisible().withTimeout(5000);
// TODO: test clipboard
});
it('should quote message', async() => {
await element(by.text(`${ data.random }quote`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Quote')).tap();
await element(by.id('messagebox-input')).typeText(`${ data.random }quoted`);
await element(by.id('messagebox-send-message')).tap();
// TODO: test if quote was sent
});
it('should star message', async() => {
await element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Star')).tap();
await waitFor(element(by.text('Messages actions'))).toBeNotVisible().withTimeout(5000);
await element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Unstar'))).toBeVisible().withTimeout(2000);
await expect(element(by.text('Unstar'))).toBeVisible();
await element(by.text('Cancel')).tap();
await waitFor(element(by.text('Cancel'))).toBeNotVisible().withTimeout(2000);
});
it('should react to message', async() => {
await element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Add Reaction')).tap();
await waitFor(element(by.id('reaction-picker'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('reaction-picker'))).toBeVisible();
await element(by.id('reaction-picker-😃')).tap();
await waitFor(element(by.id('reaction-picker-grinning'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('reaction-picker-grinning'))).toBeVisible();
await element(by.id('reaction-picker-grinning')).tap();
await waitFor(element(by.id('message-reaction-:grinning:'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('message-reaction-:grinning:'))).toBeVisible();
});
it('should show reaction picker on add reaction button pressed and have frequently used emoji', async() => {
await element(by.id('message-add-reaction')).tap();
await waitFor(element(by.id('reaction-picker'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('reaction-picker'))).toBeVisible();
await waitFor(element(by.id('reaction-picker-grinning'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('reaction-picker-grinning'))).toBeVisible();
await element(by.id('reaction-picker-😃')).tap();
await waitFor(element(by.id('reaction-picker-grimacing'))).toBeVisible().withTimeout(2000);
await element(by.id('reaction-picker-grimacing')).tap();
await waitFor(element(by.id('message-reaction-:grimacing:'))).toBeVisible().withTimeout(60000);
});
it('should remove reaction', async() => {
await element(by.id('message-reaction-:grinning:')).tap();
await waitFor(element(by.id('message-reaction-:grinning:'))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id('message-reaction-:grinning:'))).toBeNotVisible();
});
it('should pin message', async() => {
await element(by.text(`${ data.random }edited`)).longPress();
await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Messages actions'))).toBeVisible();
await element(by.text('Pin')).tap();
await waitFor(element(by.text('Messages actions'))).toBeNotVisible().withTimeout(5000);
await waitFor(element(by.text(`${ data.random }edited`)).atIndex(1)).toBeVisible().withTimeout(60000);
await element(by.text(`${ data.random }edited`)).atIndex(0).longPress();
await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000);
await expect(element(by.text('Unpin'))).toBeVisible();
await element(by.text('Cancel')).tap();
await waitFor(element(by.text('Cancel'))).toBeNotVisible().withTimeout(2000);
});
// TODO: delete message - swipe on action sheet missing
});
afterEach(async() => {
takeScreenshot();
});
after(async() => {
await element(by.id('header-back')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
});
});

403
e2e/08-roomactions.spec.js Normal file
View File

@ -0,0 +1,403 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
const scrollDown = 200;
async function navigateToRoomActions(type) {
let room;
if (type === 'd') {
room = 'rocket.cat';
} else {
room = `private${ data.random }`;
}
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(2000);
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
await element(by.id('room-view-header-actions')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
}
async function backToActions() {
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-actions-view'))).toBeVisible().withTimeout(2000);
}
async function backToRoomsList() {
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
}
describe('Room actions screen', () => {
describe('Render', async() => {
describe('Direct', async() => {
before(async() => {
await navigateToRoomActions('d');
});
it('should have room actions screen', async() => {
await expect(element(by.id('room-actions-view'))).toBeVisible();
});
it('should have info', async() => {
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 files', async() => {
await expect(element(by.id('room-actions-files'))).toBeVisible();
});
it('should have mentions', async() => {
await expect(element(by.id('room-actions-mentioned'))).toBeVisible();
});
it('should have starred', async() => {
await expect(element(by.id('room-actions-starred'))).toBeVisible();
});
it('should have search', async() => {
await expect(element(by.id('room-actions-search'))).toBeVisible();
});
it('should have share', async() => {
await waitFor(element(by.id('room-actions-share'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-share'))).toBeVisible();
});
it('should have pinned', async() => {
await waitFor(element(by.id('room-actions-pinned'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
});
it('should have snippeted', async() => {
await waitFor(element(by.id('room-actions-snippeted'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
});
it('should have notifications', async() => {
await waitFor(element(by.id('room-actions-notifications'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-notifications'))).toBeVisible();
});
it('should have block user', async() => {
await waitFor(element(by.id('room-actions-block-user'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-block-user'))).toBeVisible();
});
after(async() => {
await backToRoomsList();
});
});
describe('Channel/Group', async() => {
before(async() => {
await navigateToRoomActions('c');
});
it('should have room actions screen', async() => {
await expect(element(by.id('room-actions-view'))).toBeVisible();
});
it('should have info', async() => {
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() => {
await expect(element(by.id('room-actions-members'))).toBeVisible();
});
it('should have add user', async() => {
await expect(element(by.id('room-actions-add-user'))).toBeVisible();
});
it('should have files', async() => {
await expect(element(by.id('room-actions-files'))).toBeVisible();
});
it('should have mentions', async() => {
await expect(element(by.id('room-actions-mentioned'))).toBeVisible();
});
it('should have starred', async() => {
await expect(element(by.id('room-actions-starred'))).toBeVisible();
});
it('should have search', async() => {
await expect(element(by.id('room-actions-search'))).toBeVisible();
});
it('should have share', async() => {
await waitFor(element(by.id('room-actions-share'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-share'))).toBeVisible();
});
it('should have pinned', async() => {
await waitFor(element(by.id('room-actions-pinned'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
});
it('should have snippeted', async() => {
await waitFor(element(by.id('room-actions-snippeted'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
});
it('should have notifications', async() => {
await waitFor(element(by.id('room-actions-notifications'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-notifications'))).toBeVisible();
});
it('should have leave channel', async() => {
await waitFor(element(by.id('room-actions-leave-channel'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-leave-channel'))).toBeVisible();
});
});
afterEach(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
describe('TDB', async() => {
it('should NOT navigate to voice call', async() => {
await waitFor(element(by.id('room-actions-voice'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'up');
await element(by.id('room-actions-voice')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-actions-view'))).toBeVisible();
});
it('should NOT navigate to video call', async() => {
await element(by.id('room-actions-video')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-actions-view'))).toBeVisible();
});
it('should NOT navigate to share messages', async() => {
await waitFor(element(by.id('room-actions-share'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await element(by.id('room-actions-share')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-actions-view'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Common', async() => {
it('should show mentioned messages', async() => {
await element(by.id('room-actions-mentioned')).tap();
await waitFor(element(by.id('mentioned-messages-view'))).toExist().withTimeout(2000);
await waitFor(element(by.text(`@${ data.user } test`).withAncestor(by.id('mentioned-messages-view')))).toBeVisible().withTimeout(60000);
await expect(element(by.text(`@${ data.user } test`).withAncestor(by.id('mentioned-messages-view')))).toBeVisible();
await backToActions();
});
it('should show starred message and unstar it', async() => {
await element(by.id('room-actions-starred')).tap();
await waitFor(element(by.id('starred-messages-view'))).toExist().withTimeout(2000);
await waitFor(element(by.text(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeVisible();
await element(by.text(`${ data.random }message`).withAncestor(by.id('starred-messages-view'))).longPress();
await waitFor(element(by.text('Unstar'))).toBeVisible().withTimeout(2000);
await expect(element(by.text('Unstar'))).toBeVisible();
await element(by.text('Unstar')).tap();
await waitFor(element(by.text(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeNotVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeNotVisible();
await backToActions();
});
it('should show pinned message and unpin it', async() => {
await waitFor(element(by.id('room-actions-pinned'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await element(by.id('room-actions-pinned')).tap();
await waitFor(element(by.id('pinned-messages-view'))).toExist().withTimeout(2000);
await waitFor(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view')))).toBeVisible();
await element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).longPress();
await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000);
await expect(element(by.text('Unpin'))).toBeVisible();
await element(by.text('Unpin')).tap();
await waitFor(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeNotVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible();
await backToActions();
});
it('should search and find a message', async() => {
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'))).toBeVisible();
await element(by.id('search-message-view-input')).tap();
await element(by.id('search-message-view-input')).replaceText(`/${ data.random }message/`);
await waitFor(element(by.text(`${ data.random }message`).withAncestor(by.id('search-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }message`).withAncestor(by.id('search-messages-view'))).atIndex(0)).toBeVisible();
await backToActions();
});
it('should enable/disable notifications', async() => {
await waitFor(element(by.id('room-actions-notifications'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.text('Disable notifications'))).toBeVisible();
await element(by.id('room-actions-notifications')).tap();
await waitFor(element(by.text('Enable notifications'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Enable notifications'))).toBeVisible();
await element(by.id('room-actions-notifications')).tap();
await waitFor(element(by.text('Disable notifications'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Disable notifications'))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});
describe('Channel/Group', async() => {
// Currently, there's no way to add more owners to the room
// So we test only for the 'You are the last owner...' message
it('should tap on leave channel and raise alert', async() => {
await waitFor(element(by.id('room-actions-leave-channel'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-leave-channel'))).toBeVisible();
await element(by.id('room-actions-leave-channel')).tap();
await waitFor(element(by.text('Yes, leave it!'))).toBeVisible().withTimeout(2000);
await expect(element(by.text('Yes, leave it!'))).toBeVisible();
await element(by.text('Yes, leave it!')).tap();
await waitFor(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toBeVisible();
await takeScreenshot();
await element(by.text('OK')).tap();
await waitFor(element(by.id('rooms-actions-view'))).toBeVisible().withTimeout(2000);
});
describe('Add User', async() => {
it('should add user to the room', async() => {
const user = 'detoxrn';
await waitFor(element(by.id('room-actions-add-user'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'up');
await element(by.id('room-actions-add-user')).tap();
await element(by.id('select-users-view-search')).tap();
await element(by.id('select-users-view-search')).replaceText(user);
await waitFor(element(by.id(`select-users-view-item-${ user }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`select-users-view-item-${ user }`))).toBeVisible();
await element(by.id(`select-users-view-item-${ user }`)).tap();
await waitFor(element(by.id(`selected-user-${ user }`))).toBeVisible().withTimeout(5000);
await expect(element(by.id(`selected-user-${ user }`))).toBeVisible();
await element(by.id('selected-users-view-submit')).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
await element(by.id('room-view-header-actions')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await element(by.id('room-actions-members')).tap();
await element(by.id('room-members-view-toggle-status')).tap();
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible();
await backToActions();
});
after(async() => {
takeScreenshot();
});
});
describe('Room Members', async() => {
const user = 'detoxrn';
before(async() => {
await element(by.id('room-actions-members')).tap();
await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000);
await expect(element(by.id('room-members-view'))).toExist();
});
it('should show/hide all users', async() => {
await element(by.id('room-members-view-toggle-status')).tap();
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible();
await element(by.id('room-members-view-toggle-status')).tap();
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeNotVisible();
});
it('should filter user', async() => {
await element(by.id('room-members-view-toggle-status')).tap();
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible();
await element(by.id('room-members-view-search')).replaceText('rocket');
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeNotVisible();
await element(by.id('room-members-view-search')).tap();
await element(by.id('room-members-view-search')).clearText('');
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible();
});
it('should mute user', async() => {
await element(by.id(`room-members-view-item-${ user }`)).longPress();
await waitFor(element(by.text('Mute'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Mute'))).toBeVisible();
await element(by.text('Mute')).tap();
await waitFor(element(by.text('User has been muted!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('User has been muted!'))).toBeVisible();
await waitFor(element(by.text('User has been muted!'))).toBeNotVisible().withTimeout(60000);
await expect(element(by.text('User has been muted!'))).toBeNotVisible();
await element(by.id(`room-members-view-item-${ user }`)).longPress();
await waitFor(element(by.text('Unmute'))).toBeVisible().withTimeout(2000);
await expect(element(by.text('Unmute'))).toBeVisible();
await element(by.text('Unmute')).tap();
await waitFor(element(by.text('User has been unmuted!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('User has been unmuted!'))).toBeVisible();
await waitFor(element(by.text('User has been unmuted!'))).toBeNotVisible().withTimeout(5000);
await expect(element(by.text('User has been unmuted!'))).toBeNotVisible();
});
it('should navigate to direct room', async() => {
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toExist().withTimeout(5000);
await element(by.id(`room-members-view-item-${ user }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.id('room-view-title'))).toHaveText(user).withTimeout(60000);
await expect(element(by.id('room-view-title'))).toHaveText(user);
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-list-view'))).toBeVisible().withTimeout(2000);
});
afterEach(async() => {
takeScreenshot();
});
});
})
describe('Direct', async() => {
before(async() => {
await navigateToRoomActions('d');
});
it('should block/unblock user', async() => {
await waitFor(element(by.id('room-actions-block-user'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await element(by.id('room-actions-block-user')).tap();
await waitFor(element(by.text('Unblock user'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Unblock user'))).toBeVisible();
await element(by.id('room-actions-block-user')).tap();
await waitFor(element(by.text('Block user'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Block user'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
});
});

326
e2e/09-roominfo.spec.js Normal file
View File

@ -0,0 +1,326 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
async function navigateToRoomInfo() {
const room = `private${ data.random }`;
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(2000);
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
await element(by.id('room-view-header-title')).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
}
async function backToRoomsList() {
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
}
describe('Room info screen', () => {
describe('Direct', async() => {
before(async() => {
// last test positioned simulator at rooms-list-actions on a direct room
await waitFor(element(by.id('room-actions-info'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(500, 'up');
await element(by.id('room-actions-info')).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
});
it('should navigate to room info', async() => {
await expect(element(by.id('room-info-view'))).toBeVisible();
await expect(element(by.id('room-info-view-name'))).toBeVisible();
});
after(async() => {
await takeScreenshot();
await backToRoomsList();
});
});
describe('Channel/Group', async() => {
before(async() => {
await navigateToRoomInfo();
});
describe('Render', async() => {
it('should have room info view', async() => {
await expect(element(by.id('room-info-view'))).toBeVisible();
});
it('should have name', async() => {
await expect(element(by.id('room-info-view-name'))).toBeVisible();
});
it('should have description', async() => {
await expect(element(by.id('room-info-view-description'))).toBeVisible();
});
it('should have topic', async() => {
await expect(element(by.id('room-info-view-topic'))).toBeVisible();
});
it('should have announcement', async() => {
await expect(element(by.id('room-info-view-announcement'))).toBeVisible();
});
it('should have edit button', async() => {
await expect(element(by.id('room-info-view-edit-button'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Render Edit', async() => {
before(async() => {
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
});
it('should have room info edit view', async() => {
await expect(element(by.id('room-info-edit-view'))).toExist();
});
it('should have name input', async() => {
await expect(element(by.id('room-info-edit-view-name'))).toBeVisible();
});
it('should have description input', async() => {
await expect(element(by.id('room-info-edit-view-description'))).toBeVisible();
});
it('should have topic input', async() => {
await expect(element(by.id('room-info-edit-view-topic'))).toBeVisible();
});
it('should have announcement input', async() => {
await expect(element(by.id('room-info-edit-view-announcement'))).toBeVisible();
});
it('should have password input', async() => {
await expect(element(by.id('room-info-edit-view-password'))).toBeVisible();
});
it('should have type switch', async() => {
await element(by.id('room-info-edit-view-list')).swipe('up');
await expect(element(by.id('room-info-edit-view-t'))).toBeVisible();
});
it('should have ready only switch', async() => {
await expect(element(by.id('room-info-edit-view-ro'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('room-info-edit-view-submit'))).toBeVisible();
});
it('should have reset button', async() => {
await expect(element(by.id('room-info-edit-view-reset'))).toBeVisible();
});
it('should have archive button', async() => {
await expect(element(by.id('room-info-edit-view-archive'))).toBeVisible();
});
it('should have delete button', async() => {
await expect(element(by.id('room-info-edit-view-delete'))).toBeVisible();
});
after(async() => {
await takeScreenshot();
await element(by.id('room-info-edit-view-list')).swipe('down');
});
});
describe('Usage', async() => {
const room = `private${ data.random }`;
// it('should enter "invalid name" and get error', async() => {
// await element(by.id('room-info-edit-view-list')).swipe('down');
// await element(by.id('room-info-edit-view-name')).replaceText('invalid name');
// await element(by.id('room-info-edit-view-list')).swipe('up');
// await element(by.id('room-info-edit-view-submit')).tap();
// await waitFor(element(by.text('There was an error while saving settings!'))).toBeVisible().withTimeout(60000);
// await expect(element(by.text('There was an error while saving settings!'))).toBeVisible();
// await element(by.text('OK')).tap();
// await waitFor(element(by.text('There was an error while saving settings!'))).toBeNotVisible().withTimeout(10000);
// await element(by.id('room-info-edit-view-list')).swipe('down');
// });
it('should change room name', async() => {
await element(by.id('room-info-edit-view-name')).replaceText(`${ room }new`);
await element(by.id('room-info-edit-view-list')).swipe('up');
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-name'))).toHaveText(`${ room }new`).withTimeout(60000);
await expect(element(by.id('room-info-view-name'))).toHaveText(`${ room }new`);
// change name to original
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
await element(by.id('room-info-edit-view-name')).replaceText(`${ room }`);
await element(by.id('room-info-edit-view-list')).swipe('up');
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await element(by.id('room-info-edit-view-list')).swipe('down');
});
it('should reset form', async() => {
await element(by.id('room-info-edit-view-name')).replaceText('abc');
await element(by.id('room-info-edit-view-description')).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-password')).replaceText('abc');
await element(by.id('room-info-edit-view-list')).swipe('up');
await element(by.id('room-info-edit-view-t')).tap();
await element(by.id('room-info-edit-view-ro')).tap();
await element(by.id('room-info-edit-view-react-when-ro')).tap();
await element(by.id('room-info-edit-view-reset')).tap();
// after reset
await expect(element(by.id('room-info-edit-view-name'))).toHaveText(room);
await expect(element(by.id('room-info-edit-view-description'))).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-password'))).toHaveText('');
await expect(element(by.id('room-info-edit-view-t'))).toHaveValue('1');
await expect(element(by.id('room-info-edit-view-ro'))).toHaveValue('0');
await expect(element(by.id('room-info-edit-view-react-when-ro'))).toBeNotVisible();
await element(by.id('room-info-edit-view-list')).swipe('down');
});
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-list')).swipe('up');
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-description'))).toHaveText('new description').withTimeout(60000);
await expect(element(by.id('room-info-view-description'))).toHaveText('new description');
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
});
it('should change room topic', async() => {
await element(by.id('room-info-edit-view-topic')).replaceText('new topic');
await element(by.id('room-info-edit-view-list')).swipe('up');
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-topic'))).toHaveText('new topic').withTimeout(60000);
await expect(element(by.id('room-info-view-topic'))).toHaveText('new topic');
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
});
it('should change room announcement', async() => {
await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement');
await element(by.id('room-info-edit-view-list')).swipe('up');
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-announcement'))).toHaveText('new announcement').withTimeout(60000);
await expect(element(by.id('room-info-view-announcement'))).toHaveText('new announcement');
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
});
it('should change room password', async() => {
await element(by.id('room-info-edit-view-list')).swipe('up');
await element(by.id('room-info-edit-view-password')).replaceText('password');
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
});
it('should change room type', async() => {
await element(by.id('room-info-edit-view-t')).tap();
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await element(by.id('room-info-edit-view-t')).tap();
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
});
it('should change room read only and allow reactions', async() => {
await element(by.id('room-info-edit-view-ro')).tap();
await waitFor(element(by.id('room-info-edit-view-react-when-ro'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-info-edit-view-react-when-ro'))).toBeVisible();
await element(by.id('room-info-edit-view-react-when-ro')).tap();
await element(by.id('room-info-edit-view-submit')).tap();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
// TODO: test if it's possible to react
});
it('should archive room', async() => {
await element(by.id('room-info-edit-view-archive')).tap();
await waitFor(element(by.text('Yes, archive it!'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Yes, archive it!'))).toBeVisible();
await element(by.text('Yes, archive it!')).tap();
await waitFor(element(by.text('UNARCHIVE'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('UNARCHIVE'))).toBeVisible();
// TODO: needs permission to unarchive
// await element(by.id('room-info-edit-view-archive')).tap();
// await waitFor(element(by.text('Yes, unarchive it!'))).toBeVisible().withTimeout(5000);
// await expect(element(by.text('Yes, unarchive it!'))).toBeVisible();
// await element(by.text('Yes, unarchive it!')).tap();
// await waitFor(element(by.text('ARCHIVE'))).toBeVisible().withTimeout(60000);
// await expect(element(by.text('ARCHIVE'))).toBeVisible();
});
it('should delete room', async() => {
await element(by.id('room-info-edit-view-list')).swipe('up');
await element(by.id('room-info-edit-view-delete')).tap();
await waitFor(element(by.text('Yes, delete it!'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Yes, delete it!'))).toBeVisible();
await element(by.text('Yes, delete it!')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();
});
after(async() => {
takeScreenshot();
});
});
});
});

View File

@ -0,0 +1,60 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
describe('Change server', () => {
before(async() => {
await device.reloadReactNative();
});
it('should add server and create new user', async() => {
// Navigate to add server
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-add-server')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
// Add server
await element(by.id('new-server-view-input')).replaceText(data.alternateServer);
await waitFor(element(by.text(' is a valid Rocket.Chat instance'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-button')).tap();
// Navigate to register
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await element(by.id('welcome-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
// Register new user
await element(by.id('register-view-name')).replaceText(data.user);
await element(by.id('register-view-email')).replaceText(data.email);
await element(by.id('register-view-password')).replaceText(data.password);
await element(by.id('register-view-repeat-password')).replaceText(data.password);
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('register-view-username'))).toBeVisible().withTimeout(60000);
await element(by.id('register-view-username')).replaceText(data.user);
await element(by.id('register-view-submit-username')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await expect(element(by.id('rooms-list-view-sidebar'))).toHaveLabel(`Connected to ${ data.alternateServer }. Tap to view servers list.`);
// For a sanity test, to make sure roomslist is showing correct rooms
// app CANNOT show public room created on previous tests
await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible();
});
it('should change server', async() => {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
await element(by.id(`sidebar-${ data.server }`)).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await waitFor(element(by.id('rooms-list-view-sidebar').and(by.label(`Connected to ${ data.server }. Tap to view servers list.`)))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view-sidebar'))).toHaveLabel(`Connected to ${ data.server }. Tap to view servers list.`);
// For a sanity test, to make sure roomslist is showing correct rooms
// app MUST show public room created on previous tests
await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});

11
e2e/data.js Normal file
View File

@ -0,0 +1,11 @@
const random = require('./helpers/random');
const value = random(20);
const data = {
server: 'https://unstable.rocket.chat',
alternateServer: 'https://stable.rocket.chat',
user: `user${ value }`,
password: `password${ value }`,
email: `detoxrn+${ value }@rocket.chat`,
random: value
}
module.exports = data;

41
e2e/helpers/app.js Normal file
View File

@ -0,0 +1,41 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('../data');
async function addServer() {
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
await element(by.id('new-server-view-input')).replaceText(data.server);
await waitFor(element(by.text(' is a valid Rocket.Chat instance'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('new-server-view-button'))).toBeVisible().withTimeout(2000);
await element(by.id('new-server-view-button')).tap();
}
async function navigateToLogin() {
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
}
async function login() {
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await element(by.id('login-view-email')).replaceText(data.user);
await element(by.id('login-view-password')).replaceText(data.password);
await element(by.id('login-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
}
async function logout() {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-logout')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible();
}
module.exports = {
addServer,
navigateToLogin,
login,
logout
};

9
e2e/helpers/random.js Normal file
View File

@ -0,0 +1,9 @@
function random(length) {
let text = '';
const possible = 'abcdefghijklmnopqrstuvwxyz';
for (let i = 0; i < length; i += 1) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
module.exports = random;

23
e2e/helpers/screenshot.js Normal file
View File

@ -0,0 +1,23 @@
const { execSync } = require('child_process');
const { existsSync, mkdirSync } = require('fs');
const SCREENSHOT_DIR = '/tmp/screenshots';
const SCREENSHOT_OPTIONS = {
timeout: 2000,
killSignal: 'SIGKILL'
};
let screenshotIndex = 0;
const takeScreenshot = () => {
if (!existsSync(SCREENSHOT_DIR)) { mkdirSync(SCREENSHOT_DIR); }
const screenshotFilename = `${ SCREENSHOT_DIR }/screenshot-${ screenshotIndex++ }.png`;
try {
execSync(`xcrun simctl io booted screenshot ${ screenshotFilename }`, SCREENSHOT_OPTIONS);
} catch (error) {
console.log('erro');
}
};
module.exports = { takeScreenshot };

11
e2e/init.js Normal file
View File

@ -0,0 +1,11 @@
const detox = require('detox');
const config = require('../package.json').detox;
before(async() => {
await detox.init(config);
await device.launchApp({ permissions: { notifications: 'YES' } });
});
after(async() => {
await detox.cleanup();
});

1
e2e/mocha.opts Normal file
View File

@ -0,0 +1 @@
--recursive --timeout 120000

178
package-lock.json generated
View File

@ -3619,6 +3619,12 @@
}
}
},
"browser-stdout": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
@ -3941,6 +3947,29 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I="
},
"child-process-promise": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz",
"integrity": "sha1-RzChHvYQ+tRQuPIjx50x172tgHQ=",
"dev": true,
"requires": {
"cross-spawn": "4.0.2",
"node-version": "1.1.3",
"promise-polyfill": "6.1.0"
},
"dependencies": {
"cross-spawn": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
"integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
"dev": true,
"requires": {
"lru-cache": "4.1.1",
"which": "1.3.0"
}
}
}
},
"chokidar": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
@ -5534,6 +5563,59 @@
"defined": "1.0.0"
}
},
"detox": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/detox/-/detox-7.3.5.tgz",
"integrity": "sha512-qFAlpFAR7KOZLFoVyGHUfvvfeC6ULzdFCMnM/qlCt2rRkCC4hB1KEtzsPng92N+Si+ZNeByJ+mjWt68nbamsUQ==",
"dev": true,
"requires": {
"child-process-promise": "2.2.1",
"commander": "2.15.1",
"detox-server": "7.0.0",
"fs-extra": "4.0.3",
"get-port": "2.1.0",
"ini": "1.3.5",
"lodash": "4.17.10",
"npmlog": "4.1.2",
"shell-utils": "1.0.9",
"tail": "1.2.3",
"telnet-client": "0.15.3",
"ws": "1.1.5"
},
"dependencies": {
"fs-extra": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"jsonfile": "4.0.0",
"universalify": "0.1.1"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11"
}
}
}
},
"detox-server": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/detox-server/-/detox-server-7.0.0.tgz",
"integrity": "sha512-zs9ZP/MgeEmaZD/+MCl5PVcYHRjUtFBkBx3xQRPcsjJ/PmpCKy/BvygjLO6tRsR/2SC9UYay6W+BdguEYeft8g==",
"dev": true,
"requires": {
"lodash": "4.17.10",
"npmlog": "4.1.2",
"ws": "1.1.5"
}
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
@ -7901,6 +7983,15 @@
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U="
},
"get-port": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-2.1.0.tgz",
"integrity": "sha1-h4P53OvR7qSVozThpqJR54iHqxo=",
"dev": true,
"requires": {
"pinkie-promise": "2.0.1"
}
},
"get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
@ -8063,6 +8154,12 @@
"lodash": "4.17.10"
}
},
"growl": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
"integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
"dev": true
},
"growly": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
@ -11452,6 +11549,51 @@
"resolved": "https://registry.npmjs.org/mobx/-/mobx-2.7.0.tgz",
"integrity": "sha1-zz2C0YwMp/RY2PKiQIF7PcflSgE="
},
"mocha": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz",
"integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==",
"dev": true,
"requires": {
"browser-stdout": "1.3.1",
"commander": "2.11.0",
"debug": "3.1.0",
"diff": "3.5.0",
"escape-string-regexp": "1.0.5",
"glob": "7.1.2",
"growl": "1.10.3",
"he": "1.1.1",
"minimatch": "3.0.4",
"mkdirp": "0.5.1",
"supports-color": "4.4.0"
},
"dependencies": {
"commander": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
"dev": true
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"supports-color": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
"integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
"dev": true,
"requires": {
"has-flag": "2.0.0"
}
}
}
},
"moment": {
"version": "2.22.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz",
@ -11890,6 +12032,12 @@
}
}
},
"node-version": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/node-version/-/node-version-1.1.3.tgz",
"integrity": "sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==",
"dev": true
},
"nopt": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
@ -13769,6 +13917,12 @@
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"promise-polyfill": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz",
"integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=",
"dev": true
},
"prop-types": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz",
@ -16137,6 +16291,15 @@
"jsonify": "0.0.0"
}
},
"shell-utils": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/shell-utils/-/shell-utils-1.0.9.tgz",
"integrity": "sha512-JbTHnKpMyj9TUUbL+Us2Rx2iVHFvH5QyQoke9SN1L0pueiZeO2Gzlzopmloi7oqObL4qtvdSuZPE3UfdIzmlag==",
"dev": true,
"requires": {
"lodash": "4.17.10"
}
},
"shelljs": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz",
@ -17243,6 +17406,12 @@
}
}
},
"tail": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/tail/-/tail-1.2.3.tgz",
"integrity": "sha1-sI1vp5+5KIaWMaNBpRwUSXwcQlU=",
"dev": true
},
"tapable": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz",
@ -17287,6 +17456,15 @@
"xtend": "4.0.1"
}
},
"telnet-client": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-0.15.3.tgz",
"integrity": "sha512-GSfdzQV0BKIYsmeXq7bJFJ2wHeJud6icaIxCUf6QCGQUD6R0BBGbT1+yLDhq67JRdgRpwyPwUbV7JxFeRrZomQ==",
"dev": true,
"requires": {
"bluebird": "3.5.1"
}
},
"temp": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",

View File

@ -87,6 +87,7 @@
"babel-preset-es2015": "^6.24.1",
"babel-preset-react-native": "^4.0.0",
"codecov": "^3.0.2",
"detox": "^7.3.5",
"eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.12.0",
@ -96,6 +97,7 @@
"identity-obj-proxy": "^3.0.0",
"jest": "^22.4.4",
"jest-cli": "^22.4.4",
"mocha": "^5.1.1",
"react-dom": "^16.3.2",
"react-native-bundle-visualizer": "^1.2.0",
"react-test-renderer": "^16.3.2",
@ -104,6 +106,9 @@
"reactotron-redux-saga": "^1.13.0"
},
"jest": {
"testPathIgnorePatterns": [
"e2e"
],
"preset": "react-native",
"coverageDirectory": "./coverage/",
"collectCoverage": true,
@ -115,5 +120,15 @@
"engines": {
"node": ">=8.x",
"npm": ">=4.x"
},
"detox": {
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/RocketChatRN.app",
"build": "xcodebuild -project ios/RocketChatRN.xcodeproj -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 7"
}
}
}
}