[NEW] Show server history (#2421)
* Add dropdown Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * Adding new table to serverSchema Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * Saving if not exists Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * list of visited servers finished Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * Fix lint Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * Rename ServerLinks to ServersHistory * Refactor * Save username * Sort servers desc * ServerInput * Item * Refactor * Layout tweaks * Layout * query by text * Small refactor * Redirecting to login * Save username for oauth * Fix keyboard persist * Add tests * Unnecessary yield * Stop rendering FlatList logic when there's no servers on history * Dismiss keyboard and autocomplete when tapped outside server TextInput Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
334140df0d
commit
d37678b354
|
@ -23,11 +23,13 @@ export function selectServerFailure() {
|
|||
};
|
||||
}
|
||||
|
||||
export function serverRequest(server, certificate = null) {
|
||||
export function serverRequest(server, certificate = null, username = null, fromServerHistory = false) {
|
||||
return {
|
||||
type: SERVER.REQUEST,
|
||||
server,
|
||||
certificate
|
||||
certificate,
|
||||
username,
|
||||
fromServerHistory
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -23,14 +23,21 @@ export const FormContainerInner = ({ children }) => (
|
|||
</View>
|
||||
);
|
||||
|
||||
const FormContainer = ({ children, theme, testID }) => (
|
||||
const FormContainer = ({
|
||||
children, theme, testID, ...props
|
||||
}) => (
|
||||
<KeyboardView
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
contentContainerStyle={sharedStyles.container}
|
||||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
|
||||
<ScrollView
|
||||
style={sharedStyles.container}
|
||||
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}
|
||||
{...scrollPersistTaps}
|
||||
{...props}
|
||||
>
|
||||
<SafeAreaView testID={testID} theme={theme} style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
{children}
|
||||
<AppVersion theme={theme} />
|
||||
|
|
|
@ -16,6 +16,7 @@ import Permission from './model/Permission';
|
|||
import SlashCommand from './model/SlashCommand';
|
||||
import User from './model/User';
|
||||
import Server from './model/Server';
|
||||
import ServersHistory from './model/ServersHistory';
|
||||
|
||||
import serversSchema from './schema/servers';
|
||||
import appSchema from './schema/app';
|
||||
|
@ -71,7 +72,7 @@ class DB {
|
|||
schema: serversSchema,
|
||||
migrations: serversMigrations
|
||||
}),
|
||||
modelClasses: [Server, User],
|
||||
modelClasses: [Server, User, ServersHistory],
|
||||
actionsEnabled: true
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { Model } from '@nozbe/watermelondb';
|
||||
import { field, date, readonly } from '@nozbe/watermelondb/decorators';
|
||||
|
||||
export default class ServersHistory extends Model {
|
||||
static table = 'servers_history';
|
||||
|
||||
@field('url') url;
|
||||
|
||||
@field('username') username;
|
||||
|
||||
@readonly @date('updated_at') updatedAt
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { schemaMigrations, addColumns } from '@nozbe/watermelondb/Schema/migrations';
|
||||
import { schemaMigrations, addColumns, createTable } from '@nozbe/watermelondb/Schema/migrations';
|
||||
|
||||
export default schemaMigrations({
|
||||
migrations: [
|
||||
|
@ -70,6 +70,19 @@ export default schemaMigrations({
|
|||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 9,
|
||||
steps: [
|
||||
createTable({
|
||||
name: 'servers_history',
|
||||
columns: [
|
||||
{ name: 'url', type: 'string', isIndexed: true },
|
||||
{ name: 'username', type: 'string', isOptional: true },
|
||||
{ name: 'updated_at', type: 'number' }
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 8,
|
||||
version: 9,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'users',
|
||||
|
@ -34,6 +34,14 @@ export default appSchema({
|
|||
{ name: 'enterprise_modules', type: 'string', isOptional: true },
|
||||
{ name: 'e2e_enable', type: 'boolean', isOptional: true }
|
||||
]
|
||||
}),
|
||||
tableSchema({
|
||||
name: 'servers_history',
|
||||
columns: [
|
||||
{ name: 'url', type: 'string', isIndexed: true },
|
||||
{ name: 'username', type: 'string', isOptional: true },
|
||||
{ name: 'updated_at', type: 'number' }
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import moment from 'moment';
|
||||
import 'moment/min/locales';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import {
|
||||
|
@ -52,6 +53,26 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
|
|||
} else {
|
||||
const server = yield select(getServer);
|
||||
yield localAuthenticate(server);
|
||||
|
||||
// Saves username on server history
|
||||
const serversDB = database.servers;
|
||||
const serversHistoryCollection = serversDB.collections.get('servers_history');
|
||||
yield serversDB.action(async() => {
|
||||
try {
|
||||
const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch();
|
||||
if (serversHistory?.length) {
|
||||
const serverHistoryRecord = serversHistory[0];
|
||||
// this is updating on every login just to save `updated_at`
|
||||
// keeping this server as the most recent on autocomplete order
|
||||
await serverHistoryRecord.update((s) => {
|
||||
s.username = result.username;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
});
|
||||
|
||||
yield put(loginSuccess(result));
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { put, takeLatest } from 'redux-saga/effects';
|
||||
import { Alert } from 'react-native';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import semver from 'semver';
|
||||
|
||||
import Navigation from '../lib/Navigation';
|
||||
|
@ -131,18 +132,39 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
|||
}
|
||||
};
|
||||
|
||||
const handleServerRequest = function* handleServerRequest({ server, certificate }) {
|
||||
const handleServerRequest = function* handleServerRequest({
|
||||
server, certificate, username, fromServerHistory
|
||||
}) {
|
||||
try {
|
||||
if (certificate) {
|
||||
yield UserPreferences.setMapAsync(extractHostname(server), certificate);
|
||||
}
|
||||
|
||||
const serverInfo = yield getServerInfo({ server });
|
||||
const serversDB = database.servers;
|
||||
const serversHistoryCollection = serversDB.collections.get('servers_history');
|
||||
|
||||
if (serverInfo) {
|
||||
yield RocketChat.getLoginServices(server);
|
||||
yield RocketChat.getLoginSettings({ server });
|
||||
Navigation.navigate('WorkspaceView');
|
||||
|
||||
if (fromServerHistory) {
|
||||
Navigation.navigate('LoginView', { username });
|
||||
}
|
||||
|
||||
yield serversDB.action(async() => {
|
||||
try {
|
||||
const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch();
|
||||
if (!serversHistory?.length) {
|
||||
await serversHistoryCollection.create((s) => {
|
||||
s.url = server;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
});
|
||||
yield put(selectServerRequest(server, serverInfo.version, false));
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -56,6 +56,7 @@ class LoginView extends React.Component {
|
|||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
Site_Name: PropTypes.string,
|
||||
Accounts_RegistrationForm: PropTypes.string,
|
||||
Accounts_RegistrationForm_LinkReplacementText: PropTypes.string,
|
||||
|
@ -74,7 +75,7 @@ class LoginView extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: '',
|
||||
user: props.route.params?.username ?? '',
|
||||
password: ''
|
||||
};
|
||||
}
|
||||
|
@ -123,6 +124,7 @@ class LoginView extends React.Component {
|
|||
}
|
||||
|
||||
renderUserForm = () => {
|
||||
const { user } = this.state;
|
||||
const {
|
||||
Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_PasswordReset, Accounts_RegistrationForm_LinkReplacementText, isFetching, theme, Accounts_ShowFormLogin
|
||||
} = this.props;
|
||||
|
@ -146,6 +148,7 @@ class LoginView extends React.Component {
|
|||
textContentType='username'
|
||||
autoCompleteType='username'
|
||||
theme={theme}
|
||||
value={user}
|
||||
/>
|
||||
<TextInput
|
||||
label='Password'
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View, StyleSheet, Text
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import sharedStyles from '../../Styles';
|
||||
import Touch from '../../../utils/touch';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
height: 56,
|
||||
paddingHorizontal: 15,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
server: {
|
||||
...sharedStyles.textMedium,
|
||||
fontSize: 16
|
||||
}
|
||||
});
|
||||
|
||||
const Item = ({
|
||||
item, theme, onPress, onDelete
|
||||
}) => (
|
||||
<Touch style={styles.container} onPress={() => onPress(item.url)} theme={theme} testID={`server-history-${ item.url }`}>
|
||||
<View style={styles.content}>
|
||||
<Text style={[styles.server, { color: themes[theme].bodyText }]}>{item.url}</Text>
|
||||
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]}>{item.username}</Text>
|
||||
</View>
|
||||
<BorderlessButton onPress={() => onDelete(item)} testID={`server-history-delete-${ item.url }`}>
|
||||
<CustomIcon name='delete' size={24} color={themes[theme].auxiliaryText} />
|
||||
</BorderlessButton>
|
||||
</Touch>
|
||||
);
|
||||
|
||||
Item.propTypes = {
|
||||
item: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
export default Item;
|
|
@ -0,0 +1,87 @@
|
|||
import React, { useState } from 'react';
|
||||
import { View, FlatList, StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import TextInput from '../../../containers/TextInput';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import Item from './Item';
|
||||
import Separator from '../../../containers/Separator';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
zIndex: 1,
|
||||
marginTop: 24,
|
||||
marginBottom: 32
|
||||
},
|
||||
inputContainer: {
|
||||
marginTop: 0,
|
||||
marginBottom: 0
|
||||
},
|
||||
serverHistory: {
|
||||
maxHeight: 180,
|
||||
width: '100%',
|
||||
top: '100%',
|
||||
zIndex: 1,
|
||||
position: 'absolute',
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderRadius: 2,
|
||||
borderTopWidth: 0
|
||||
}
|
||||
});
|
||||
|
||||
const ServerInput = ({
|
||||
text,
|
||||
theme,
|
||||
serversHistory,
|
||||
onChangeText,
|
||||
onSubmit,
|
||||
onDelete,
|
||||
onPressServerHistory
|
||||
}) => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TextInput
|
||||
label='Enter workspace URL'
|
||||
placeholder='Ex. your-company.rocket.chat'
|
||||
containerStyle={styles.inputContainer}
|
||||
value={text}
|
||||
returnKeyType='send'
|
||||
onChangeText={onChangeText}
|
||||
testID='new-server-view-input'
|
||||
onSubmitEditing={onSubmit}
|
||||
clearButtonMode='while-editing'
|
||||
keyboardType='url'
|
||||
textContentType='URL'
|
||||
theme={theme}
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
/>
|
||||
{
|
||||
focused && serversHistory?.length
|
||||
? (
|
||||
<View style={[styles.serverHistory, { backgroundColor: themes[theme].backgroundColor, borderColor: themes[theme].separatorColor }]}>
|
||||
<FlatList
|
||||
data={serversHistory}
|
||||
renderItem={({ item }) => <Item item={item} theme={theme} onPress={() => onPressServerHistory(item)} onDelete={onDelete} />}
|
||||
ItemSeparatorComponent={() => <Separator theme={theme} />}
|
||||
keyExtractor={item => item.id}
|
||||
/>
|
||||
</View>
|
||||
) : null
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ServerInput.propTypes = {
|
||||
text: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
serversHistory: PropTypes.array,
|
||||
onChangeText: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onPressServerHistory: PropTypes.func
|
||||
};
|
||||
|
||||
export default ServerInput;
|
|
@ -1,42 +1,41 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text, Keyboard, StyleSheet, TouchableOpacity, View, Alert, BackHandler
|
||||
Text, Keyboard, StyleSheet, View, Alert, BackHandler
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import { Base64 } from 'js-base64';
|
||||
import parse from 'url-parse';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import UserPreferences from '../lib/userPreferences';
|
||||
import EventEmitter from '../utils/events';
|
||||
import { selectServerRequest, serverRequest } from '../actions/server';
|
||||
import { inviteLinksClear as inviteLinksClearAction } from '../actions/inviteLinks';
|
||||
import sharedStyles from './Styles';
|
||||
import Button from '../containers/Button';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import OrSeparator from '../containers/OrSeparator';
|
||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
import I18n from '../i18n';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { themes } from '../constants/colors';
|
||||
import log, { logEvent, events } from '../utils/log';
|
||||
import { animateNextTransition } from '../utils/layoutAnimation';
|
||||
import { withTheme } from '../theme';
|
||||
import { setBasicAuth, BASIC_AUTH_KEY } from '../utils/fetch';
|
||||
import { CloseModalButton } from '../containers/HeaderButton';
|
||||
import { showConfirmationAlert } from '../utils/info';
|
||||
import { TouchableOpacity } from 'react-native-gesture-handler';
|
||||
import UserPreferences from '../../lib/userPreferences';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import { selectServerRequest, serverRequest } from '../../actions/server';
|
||||
import { inviteLinksClear as inviteLinksClearAction } from '../../actions/inviteLinks';
|
||||
import sharedStyles from '../Styles';
|
||||
import Button from '../../containers/Button';
|
||||
import OrSeparator from '../../containers/OrSeparator';
|
||||
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
||||
import I18n from '../../i18n';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { themes } from '../../constants/colors';
|
||||
import log, { logEvent, events } from '../../utils/log';
|
||||
import { animateNextTransition } from '../../utils/layoutAnimation';
|
||||
import { withTheme } from '../../theme';
|
||||
import { setBasicAuth, BASIC_AUTH_KEY } from '../../utils/fetch';
|
||||
import { CloseModalButton } from '../../containers/HeaderButton';
|
||||
import { showConfirmationAlert } from '../../utils/info';
|
||||
import database from '../../lib/database';
|
||||
import ServerInput from './ServerInput';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
...sharedStyles.textBold,
|
||||
fontSize: 22
|
||||
},
|
||||
inputContainer: {
|
||||
marginTop: 24,
|
||||
marginBottom: 32
|
||||
},
|
||||
certificatePicker: {
|
||||
marginBottom: 32,
|
||||
alignItems: 'center',
|
||||
|
@ -84,12 +83,17 @@ class NewServerView extends React.Component {
|
|||
this.state = {
|
||||
text: '',
|
||||
connectingOpen: false,
|
||||
certificate: null
|
||||
certificate: null,
|
||||
serversHistory: []
|
||||
};
|
||||
EventEmitter.addEventListener('NewServer', this.handleNewServerEvent);
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.queryServerHistory();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { adding } = this.props;
|
||||
if (prevProps.adding !== adding) {
|
||||
|
@ -122,6 +126,29 @@ class NewServerView extends React.Component {
|
|||
|
||||
onChangeText = (text) => {
|
||||
this.setState({ text });
|
||||
this.queryServerHistory(text);
|
||||
}
|
||||
|
||||
queryServerHistory = async(text) => {
|
||||
const db = database.servers;
|
||||
try {
|
||||
const serversHistoryCollection = db.collections.get('servers_history');
|
||||
let whereClause = [
|
||||
Q.where('username', Q.notEq(null)),
|
||||
Q.experimentalSortBy('updated_at', Q.desc),
|
||||
Q.experimentalTake(3)
|
||||
];
|
||||
if (text) {
|
||||
whereClause = [
|
||||
...whereClause,
|
||||
Q.where('url', Q.like(`%${ Q.sanitizeLikeString(text) }%`))
|
||||
];
|
||||
}
|
||||
const serversHistory = await serversHistoryCollection.query(...whereClause).fetch();
|
||||
this.setState({ serversHistory });
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
close = () => {
|
||||
|
@ -138,7 +165,11 @@ class NewServerView extends React.Component {
|
|||
connectServer(server);
|
||||
}
|
||||
|
||||
submit = async() => {
|
||||
onPressServerHistory = (serverHistory) => {
|
||||
this.setState({ text: serverHistory?.url }, () => this.submit({ fromServerHistory: true, username: serverHistory?.username }));
|
||||
}
|
||||
|
||||
submit = async({ fromServerHistory = false, username }) => {
|
||||
logEvent(events.NEWSERVER_CONNECT_TO_WORKSPACE);
|
||||
const { text, certificate } = this.state;
|
||||
const { connectServer } = this.props;
|
||||
|
@ -164,9 +195,13 @@ class NewServerView extends React.Component {
|
|||
Keyboard.dismiss();
|
||||
const server = this.completeUrl(text);
|
||||
await this.basicAuth(server, text);
|
||||
if (fromServerHistory) {
|
||||
connectServer(server, cert, username, true);
|
||||
} else {
|
||||
connectServer(server, cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connectOpen = () => {
|
||||
logEvent(events.NEWSERVER_JOIN_OPEN_WORKSPACE);
|
||||
|
@ -251,6 +286,19 @@ class NewServerView extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
deleteServerHistory = async(item) => {
|
||||
const { serversHistory } = this.state;
|
||||
const db = database.servers;
|
||||
try {
|
||||
await db.action(async() => {
|
||||
await item.destroyPermanently();
|
||||
});
|
||||
this.setState({ serversHistory: serversHistory.filter(server => server.id !== item.id) });
|
||||
} catch {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
renderCertificatePicker = () => {
|
||||
const { certificate } = this.state;
|
||||
const { theme } = this.props;
|
||||
|
@ -283,24 +331,25 @@ class NewServerView extends React.Component {
|
|||
|
||||
render() {
|
||||
const { connecting, theme } = this.props;
|
||||
const { text, connectingOpen } = this.state;
|
||||
const {
|
||||
text, connectingOpen, serversHistory
|
||||
} = this.state;
|
||||
return (
|
||||
<FormContainer theme={theme} testID='new-server-view'>
|
||||
<FormContainer
|
||||
theme={theme}
|
||||
testID='new-server-view'
|
||||
keyboardShouldPersistTaps='never'
|
||||
>
|
||||
<FormContainerInner>
|
||||
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Join_your_workspace')}</Text>
|
||||
<TextInput
|
||||
label='Enter workspace URL'
|
||||
placeholder='Ex. your-company.rocket.chat'
|
||||
containerStyle={styles.inputContainer}
|
||||
value={text}
|
||||
returnKeyType='send'
|
||||
onChangeText={this.onChangeText}
|
||||
testID='new-server-view-input'
|
||||
onSubmitEditing={this.submit}
|
||||
clearButtonMode='while-editing'
|
||||
keyboardType='url'
|
||||
textContentType='URL'
|
||||
<ServerInput
|
||||
text={text}
|
||||
theme={theme}
|
||||
serversHistory={serversHistory}
|
||||
onChangeText={this.onChangeText}
|
||||
onSubmit={this.submit}
|
||||
onDelete={this.deleteServerHistory}
|
||||
onPressServerHistory={this.onPressServerHistory}
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Connect')}
|
||||
|
@ -338,7 +387,7 @@ const mapStateToProps = state => ({
|
|||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
connectServer: (server, certificate) => dispatch(serverRequest(server, certificate)),
|
||||
connectServer: (server, certificate, username, fromServerHistory) => dispatch(serverRequest(server, certificate, username, fromServerHistory)),
|
||||
selectServer: server => dispatch(selectServerRequest(server)),
|
||||
inviteLinksClear: () => dispatch(inviteLinksClearAction())
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
const {
|
||||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const { login, navigateToLogin, logout, tapBack } = require('../../helpers/app');
|
||||
const data = require('../../data');
|
||||
|
||||
describe('Server history', () => {
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
});
|
||||
|
||||
describe('Usage', () => {
|
||||
it('should login, save server as history and logout', async() => {
|
||||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password);
|
||||
await logout();
|
||||
await element(by.id('join-workspace')).tap();
|
||||
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
|
||||
})
|
||||
|
||||
it('should show servers history', async() => {
|
||||
await element(by.id('new-server-view-input')).tap();
|
||||
await waitFor(element(by.id(`server-history-${ data.server }`))).toBeVisible().withTimeout(2000);
|
||||
});
|
||||
|
||||
it('should tap on a server history and navigate to login', async() => {
|
||||
await element(by.id(`server-history-${ data.server }`)).tap();
|
||||
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('login-view-email'))).toHaveText(data.users.regular.username);
|
||||
});
|
||||
|
||||
it('should delete server from history', async() => {
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(2000);
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
|
||||
await element(by.id('new-server-view-input')).tap();
|
||||
await waitFor(element(by.id(`server-history-${ data.server }`))).toBeVisible().withTimeout(2000);
|
||||
await element(by.id(`server-history-delete-${ data.server }`)).tap();
|
||||
await element(by.id('new-server-view-input')).tap();
|
||||
await waitFor(element(by.id(`server-history-${ data.server }`))).toBeNotVisible().withTimeout(2000);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue