diff --git a/.eslintignore b/.eslintignore index 398578ec4..9594dcce6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,6 @@ __tests__ node_modules coverage -e2e +e2e/docker android ios \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 31146aefe..409efb70a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -29,7 +29,8 @@ module.exports = { "commonjs": true, "es6": true, "node": true, - "jquery": true + "jquery": true, + "mocha": true }, "rules": { "react/jsx-filename-extension": [1, { @@ -155,5 +156,23 @@ module.exports = { }, "globals": { "__DEV__": true - } + }, + overrides: [ + { + files: ['e2e/**'], + globals: { + by: true, + detox: true, + device: true, + element: true, + expect: true, + waitFor: true + }, + rules: { + 'import/no-extraneous-dependencies': 0, + 'no-await-in-loop': 0, + 'no-restricted-syntax': 0 + } + } + ] }; diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 3e3106d94..f7291f458 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -11490,7 +11490,7 @@ exports[`Storyshots LoadMore black theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -11593,7 +11593,7 @@ exports[`Storyshots LoadMore black theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -11696,7 +11696,7 @@ exports[`Storyshots LoadMore black theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -11973,7 +11973,7 @@ exports[`Storyshots LoadMore black theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -12076,7 +12076,7 @@ exports[`Storyshots LoadMore black theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -12179,7 +12179,7 @@ exports[`Storyshots LoadMore black theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -12420,7 +12420,7 @@ exports[`Storyshots LoadMore black theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -12692,7 +12692,7 @@ exports[`Storyshots LoadMore dark theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -12795,7 +12795,7 @@ exports[`Storyshots LoadMore dark theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -12898,7 +12898,7 @@ exports[`Storyshots LoadMore dark theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -13175,7 +13175,7 @@ exports[`Storyshots LoadMore dark theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -13278,7 +13278,7 @@ exports[`Storyshots LoadMore dark theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -13381,7 +13381,7 @@ exports[`Storyshots LoadMore dark theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -13622,7 +13622,7 @@ exports[`Storyshots LoadMore dark theme 1`] = ` }, undefined, Object { - "color": "#e8ebed", + "color": "#cbced1", }, ] } @@ -67631,7 +67631,7 @@ Array [ "textAlign": "left", }, Object { - "color": "#e8ebed", + "color": "#cbced1", }, Object { "backgroundColor": "transparent", @@ -67791,7 +67791,7 @@ Array [ "textAlign": "left", }, Object { - "color": "#e8ebed", + "color": "#cbced1", }, Object { "backgroundColor": "transparent", @@ -74309,7 +74309,7 @@ exports[`Storyshots Thread Messages.Item themes 1`] = ` "textAlign": "left", }, Object { - "color": "#e8ebed", + "color": "#cbced1", }, Object { "flex": 1, @@ -74661,7 +74661,7 @@ exports[`Storyshots Thread Messages.Item themes 1`] = ` "textAlign": "left", }, Object { - "color": "#e8ebed", + "color": "#cbced1", }, Object { "flex": 1, diff --git a/android/app/build.gradle b/android/app/build.gradle index ad78638fc..008f1be71 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,7 +144,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.17.0" + versionName "4.18.0" vectorDrawables.useSupportLibrary = true if (!isFoss) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 1fd8679bc..0790b7bad 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -66,9 +66,10 @@ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [ 'CLEAR', ...defaultTypes ]); -export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']); +export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD', 'UPDATE']); export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']); export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']); export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']); -export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET']); +export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET', 'UPDATE']); +export const ROLES = createRequestTypes('ROLES', ['SET', 'UPDATE', 'REMOVE']); diff --git a/app/actions/login.js b/app/actions/login.js index daf13d3b4..e0f4c1e28 100644 --- a/app/actions/login.js +++ b/app/actions/login.js @@ -1,10 +1,11 @@ import * as types from './actionsTypes'; -export function loginRequest(credentials, logoutOnError) { +export function loginRequest(credentials, logoutOnError, isFromWebView) { return { type: types.LOGIN.REQUEST, credentials, - logoutOnError + logoutOnError, + isFromWebView }; } diff --git a/app/actions/permissions.js b/app/actions/permissions.js index 88179c34f..444d9f11c 100644 --- a/app/actions/permissions.js +++ b/app/actions/permissions.js @@ -6,3 +6,10 @@ export function setPermissions(permissions) { permissions }; } + +export function updatePermission(id, roles) { + return { + type: types.PERMISSIONS.UPDATE, + payload: { id, roles } + }; +} diff --git a/app/actions/roles.js b/app/actions/roles.js new file mode 100644 index 000000000..8ee30425b --- /dev/null +++ b/app/actions/roles.js @@ -0,0 +1,20 @@ +import * as types from './actionsTypes'; + +export function setRoles(roles) { + return { + type: types.ROLES.SET, + roles + }; +} +export function updateRoles(id, desc) { + return { + type: types.ROLES.UPDATE, + payload: { id, desc } + }; +} +export function removeRoles(id) { + return { + type: types.ROLES.REMOVE, + payload: { id } + }; +} diff --git a/app/actions/room.js b/app/actions/room.js index 4ad7e87f6..2753d7b9a 100644 --- a/app/actions/room.js +++ b/app/actions/room.js @@ -23,11 +23,12 @@ export function leaveRoom(roomType, room, selected) { }; } -export function deleteRoom(rid, t) { +export function deleteRoom(roomType, room, selected) { return { type: types.ROOM.DELETE, - rid, - t + room, + roomType, + selected }; } diff --git a/app/actions/settings.js b/app/actions/settings.js index 381958c54..6fae375bc 100644 --- a/app/actions/settings.js +++ b/app/actions/settings.js @@ -7,6 +7,13 @@ export function addSettings(settings) { }; } +export function updateSettings(id, value) { + return { + type: SETTINGS.UPDATE, + payload: { id, value } + }; +} + export function clearSettings() { return { type: SETTINGS.CLEAR diff --git a/app/constants/colors.js b/app/constants/colors.js index 554d7f8d3..1172b4bc9 100644 --- a/app/constants/colors.js +++ b/app/constants/colors.js @@ -74,7 +74,7 @@ export const themes = { auxiliaryBackground: '#07101e', bannerBackground: '#0e1f38', titleText: '#f9f9f9', - bodyText: '#e8ebed', + bodyText: '#cbced1', backdropColor: '#000000', dangerColor: '#f5455c', successColor: '#2de0a5', @@ -121,7 +121,7 @@ export const themes = { auxiliaryBackground: '#080808', bannerBackground: '#1f2329', titleText: '#f9f9f9', - bodyText: '#e8ebed', + bodyText: '#cbced1', backdropColor: '#000000', dangerColor: '#f5455c', successColor: '#2de0a5', diff --git a/app/constants/settings.js b/app/constants/settings.js index d684a11c1..14ad8fb36 100644 --- a/app/constants/settings.js +++ b/app/constants/settings.js @@ -196,5 +196,11 @@ export default { }, Accounts_AllowInvisibleStatusOption: { type: 'valueAsString' + }, + Jitsi_Enable_Teams: { + type: 'valueAsBoolean' + }, + Jitsi_Enable_Channels: { + type: 'valuesAsBoolean' } }; diff --git a/app/containers/InAppNotification/NotifierComponent.js b/app/containers/InAppNotification/NotifierComponent.js index b69704359..6268cd098 100644 --- a/app/containers/InAppNotification/NotifierComponent.js +++ b/app/containers/InAppNotification/NotifierComponent.js @@ -76,7 +76,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => { const { title = name, avatar = name } = notification; const onPress = () => { - const { prid } = payload; + const { prid, _id } = payload; if (!rid) { return; } @@ -89,7 +89,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => { } else { Navigation.navigate('RoomsListView'); } - goRoom({ item, isMasterDetail }); + goRoom({ item, isMasterDetail, jumpToMessageId: _id }); hideNotification(); }; diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json index 6f188ca55..8b08c047d 100644 --- a/app/i18n/locales/ar.json +++ b/app/i18n/locales/ar.json @@ -387,7 +387,6 @@ "Preferences_saved": "تم حفظ التفضيلات", "Privacy_Policy": "سياسة الخصوصية", "Private_Channel": "قناة خاصة", - "Private_Groups": "مجموعات خاصة", "Private": "خاص", "Processing": "جار معالجة...", "Profile_saved_successfully": "تم حفظ الملف الشخصي بنجاح!", diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index 0a7f7e1ff..cf61aac86 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -400,7 +400,6 @@ "Preferences_saved": "Einstellungen gespeichert!", "Privacy_Policy": " Datenschutzbestimmungen", "Private_Channel": "Privater Kanal", - "Private_Groups": "Private Gruppen", "Private": "Privat", "Processing": "Bearbeite …", "Profile_saved_successfully": "Profil erfolgreich gespeichert!", @@ -685,7 +684,7 @@ "Following": "verfolgte", "Threads_displaying_all": "zeige alle", "Threads_displaying_following": "zeige gefolgte", - "Threads_displaying_unread": "zeige ungelesene", + "Threads_displaying_unread": "Zeige ungelesene", "No_threads": "Es gibt keine Threads", "No_threads_following": "Du folgst keinen Threads", "No_threads_unread": "Es gibt keine ungelesenen Threads", @@ -724,6 +723,7 @@ "creating_team": "Team erstellen", "team-name-already-exists": "Ein Team mit diesem Namen existiert bereits", "Add_Channel_to_Team": "Kanal zum Team hinzufügen", + "Left_The_Team_Successfully": "Das Team erfolgreich verlassen", "Create_New": "Neu erstellen", "Add_Existing": "Vorhandenes hinzufügen", "Add_Existing_Channel": "Vorhandenen Kanal hinzufügen", diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 926b0c674..194031660 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -400,7 +400,6 @@ "Preferences_saved": "Preferences saved!", "Privacy_Policy": " Privacy Policy", "Private_Channel": "Private Channel", - "Private_Groups": "Private Groups", "Private": "Private", "Processing": "Processing...", "Profile_saved_successfully": "Profile saved successfully!", @@ -756,11 +755,18 @@ "member-does-not-exist": "Member does not exist", "Convert": "Convert", "Convert_to_Team": "Convert to Team", - "Convert_to_Team_Warning": "This can't be undone. Once you convert a channel to a team, you can not turn it back to a channel.", + "Convert_to_Team_Warning": "You are converting this Channel to a Team. All Members will be kept.", "Move_to_Team": "Move to Team", "Move_Channel_Paragraph": "Moving a channel inside a team means that this channel will be added in the team’s context, however, all channel’s members, which are not members of the respective team, will still have access to this channel, but will not be added as team’s members. \n\nAll channel’s management will still be made by the owners of this channel.\n\nTeam’s members and even team’s owners, if not a member of this channel, can not have access to the channel’s content. \n\nPlease notice that the Team’s owner will be able remove members from the Channel.", "Move_to_Team_Warning": "After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?", "Load_More": "Load More", "Load_Newer": "Load Newer", - "Load_Older": "Load Older" + "Load_Older": "Load Older", + "Left_The_Room_Successfully": "Left the room successfully", + "Deleted_The_Team_Successfully": "Team deleted successfully", + "Deleted_The_Room_Successfully": "Room deleted successfully", + "Convert_to_Channel": "Convert to Channel", + "Converting_Team_To_Channel": "Converting Team to Channel", + "Select_Team_Channels_To_Delete": "Select the Team’s Channels you would like to delete, the ones you do not select will be moved to the Workspace. \n\nNotice that public Channels will be public and visible to everyone.", + "You_are_converting_the_team": "You are converting this Team to a Channel" } \ No newline at end of file diff --git a/app/i18n/locales/es-ES.json b/app/i18n/locales/es-ES.json index ac6657042..25a65523d 100644 --- a/app/i18n/locales/es-ES.json +++ b/app/i18n/locales/es-ES.json @@ -280,7 +280,6 @@ "Preferences_saved": "¡Preferencias guardadas!", "Privacy_Policy": "Política de privacidad", "Private_Channel": "Canal privado", - "Private_Groups": "Grupos privados", "Private": "Privado", "Processing": "Procesando...", "Profile_saved_successfully": "¡Perfil guardado correctamente!", diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json index 91388e2de..07d441eed 100644 --- a/app/i18n/locales/fr.json +++ b/app/i18n/locales/fr.json @@ -400,7 +400,6 @@ "Preferences_saved": "Préférences sauvegardées !", "Privacy_Policy": " Politique de confidentialité", "Private_Channel": "Canal privé", - "Private_Groups": "Groupes privés", "Private": "Privé", "Processing": "Traitement...", "Profile_saved_successfully": "Profil enregistré avec succès !", @@ -724,6 +723,7 @@ "creating_team": "création de l'équipe", "team-name-already-exists": "Une équipe portant ce nom existe déjà", "Add_Channel_to_Team": "Ajouter un canal à l'équipe", + "Left_The_Team_Successfully": "A quitté l'équipe avec succès", "Create_New": "Créer un nouveau", "Add_Existing": "Ajouter existant", "Add_Existing_Channel": "Ajouter un canal existant", @@ -755,11 +755,18 @@ "member-does-not-exist": "Le membre n'existe pas", "Convert": "Convertir", "Convert_to_Team": "Convertir en équipe", - "Convert_to_Team_Warning": "Ceci ne peut pas être annulé. Une fois que vous avez converti un canal en équipe, vous ne pouvez pas le retransformer en canal.", + "Convert_to_Team_Warning": "Vous convertissez ce canal en équipe. Tous les membres seront conservés.", "Move_to_Team": "Déplacer vers l'équipe", "Move_Channel_Paragraph": "Le déplacement d'un canal dans une équipe signifie que ce canal sera ajouté dans le contexte d'équipe. Cependant, tous les membres du canal, qui ne sont pas membres de l'équipe respective, auront toujours accès à ce canal, mais ne seront pas ajoutés comme membres de l'équipe.\n\nLa gestion de tout le canal sera toujours assurée par les propriétaires de ce canal.\n\nLes membres de l'équipe et même les propriétaires de l'équipe, s'ils ne sont pas membres de ce canal, ne peuvent pas avoir accès au contenu du canal.\n\nVeuillez noter que le propriétaire de l'équipe pourra supprimer des membres du canal.", "Move_to_Team_Warning": "Après avoir lu les instructions précédentes sur ce comportement, voulez-vous toujours déplacer ce canal vers l'équipe sélectionnée ?", "Load_More": "Charger plus", "Load_Newer": "Charger plus récent", - "Load_Older": "Charger plus ancien" + "Load_Older": "Charger plus ancien", + "Left_The_Room_Successfully": "A quitté le salon avec succès", + "Deleted_The_Team_Successfully": "Equipe supprimée avec succès", + "Deleted_The_Room_Successfully": "Salon supprimé avec succès", + "Convert_to_Channel": "Convertir en canal", + "Converting_Team_To_Channel": "Conversion de l’équipe en canal", + "Select_Team_Channels_To_Delete": "Sélectionnez les canaux de l'équipe que vous souhaitez supprimer, ceux que vous ne sélectionnez pas, seront déplacés vers l'espace de travail. \n\n\nNotez que les canaux publics seront publics et visibles par tous.", + "You_are_converting_the_team": "Vous convertissez cette équipe en canal" } \ No newline at end of file diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json index 505282dd5..e8618f28e 100644 --- a/app/i18n/locales/it.json +++ b/app/i18n/locales/it.json @@ -392,7 +392,6 @@ "Preferences_saved": "Impostazioni salvate!", "Privacy_Policy": " Privacy Policy", "Private_Channel": "Canale privato", - "Private_Groups": "Gruppi privati", "Private": "Privato", "Processing": "Elaborazione...", "Profile_saved_successfully": "Profilo salvato correttamente!", diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json index 994753fbb..61accdd79 100644 --- a/app/i18n/locales/ja.json +++ b/app/i18n/locales/ja.json @@ -296,7 +296,6 @@ "Preferences_saved": "設定が保存されました。", "Privacy_Policy": " プライバシーポリシー", "Private_Channel": "プライベートチャンネル", - "Private_Groups": "プライベートグループ", "Private": "プライベート", "Processing": "処理中...", "Profile_saved_successfully": "プロフィールが保存されました!", diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json index 8a48d3c74..7c823c9ba 100644 --- a/app/i18n/locales/nl.json +++ b/app/i18n/locales/nl.json @@ -400,7 +400,6 @@ "Preferences_saved": "Voorkeuren opgeslagen!", "Privacy_Policy": " Privacybeleid", "Private_Channel": "Privékanaal", - "Private_Groups": "Privé groepen", "Private": "Privé", "Processing": "Verwerking...", "Profile_saved_successfully": "Profiel succesvol opgeslagen!", @@ -724,6 +723,7 @@ "creating_team": "team maken", "team-name-already-exists": "Er bestaat al een team met die naam", "Add_Channel_to_Team": "Kanaal toevoegen aan team", + "Left_The_Team_Successfully": "Het team met succes verlaten", "Create_New": "Maak nieuw", "Add_Existing": "Voeg bestaande", "Add_Existing_Channel": "Bestaand kanaal toevoegen", @@ -755,11 +755,18 @@ "member-does-not-exist": "Lid bestaat niet", "Convert": "Converteren", "Convert_to_Team": "Converteren naar team", - "Convert_to_Team_Warning": "Dit kan niet ongedaan worden gemaakt. Eens je een kanaal naar een team hebt geconverteerd, kun je het niet meer naar een kanaal terugzetten.", + "Convert_to_Team_Warning": "Je converteert dit kanaal naar een team. Alle leden blijven behouden.", "Move_to_Team": "Verplaats naar team", "Move_Channel_Paragraph": "Het verplaatsen van een kanaal binnen een team betekent dat dit kanaal wordt toegevoegd in de context van het team. Maar, alle leden van dit kanaal, die geen lid zijn van het respectieve team, zullen nog steeds toegang hebben tot dit kanaal, maar worden niet als teamleden toegevoegd.\n\nHet volledige beheer van dit kanaal wordt nog steeds door de eigenaren van dit kanaal gedaan.\n\nTeamleden en zelfs teameigenaren, wanneer ze geen lid zijn van dit kanaal, hebben geen toegang tot de content van het kanaal.\n\nHou er rekening mee dat de eigenaar van het team de leden uit het kanaal kan verwijderen.", "Move_to_Team_Warning": "Wil je na het lezen van de vorige instructies over dit gedrag, dit kanaal nog steeds naar het geselecteerde team verplaatsen?", "Load_More": "Meer laden", "Load_Newer": "Nieuwer laden", - "Load_Older": "Ouder laden" + "Load_Older": "Ouder laden", + "Left_The_Room_Successfully": "Heeft kamer met succes verlaten", + "Deleted_The_Team_Successfully": "Team succesvol verwijderd", + "Deleted_The_Room_Successfully": "Kamer succesvol verwijderd", + "Convert_to_Channel": "Converteren naar kanaal", + "Converting_Team_To_Channel": "Team converteren naar kanaal", + "Select_Team_Channels_To_Delete": "Selecteer de teamkanalen die je wilt verwijderen, de kanalen die u niet selecteert, worden naar de werkruimte verplaatst.\n\nMerk op dat openbare kanalen openbaar en voor iedereen zichtbaar zullen zijn.", + "You_are_converting_the_team": "Je converteert dit team naar een kanaal" } \ No newline at end of file diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 9b34d87d3..a6fed1be2 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -370,7 +370,6 @@ "Preferences_saved": "Preferências salvas!", "Privacy_Policy": " Política de Privacidade", "Private_Channel": "Canal Privado", - "Private_Groups": "Grupo Privado", "Private": "Privado", "Processing": "Processando...", "Profile_saved_successfully": "Perfil salvo com sucesso!", @@ -664,6 +663,11 @@ "No_team_channels_found": "Nenhum canal encontrado", "Team_not_found": "Time não encontrado", "Private_Team": "Equipe Privada", + "Left_The_Team_Successfully": "Saiu do time com sucesso", "Add_Existing_Channel": "Adicionar Canal Existente", - "invalid-room": "Sala inválida" + "invalid-room": "Sala inválida", + "Left_The_Room_Successfully": "Saiu da sala com sucesso", + "Deleted_The_Team_Successfully": "Time deletado com sucesso", + "Deleted_The_Room_Successfully": "Sala deletada com sucesso", + "Convert_to_Channel": "Converter para um Canal" } \ No newline at end of file diff --git a/app/i18n/locales/pt-PT.json b/app/i18n/locales/pt-PT.json index ca7d4dc59..fa10dcd36 100644 --- a/app/i18n/locales/pt-PT.json +++ b/app/i18n/locales/pt-PT.json @@ -10,19 +10,22 @@ "error-could-not-change-email": "Não foi possível alterar o e-mail", "error-could-not-change-name": "Não foi possível alterar o nome", "error-could-not-change-username": "Não foi possível alterar o nome de utilizador", + "error-could-not-change-status": "Impossível mudar estado", "error-delete-protected-role": "Não é possível eliminar uma função protegida", "error-department-not-found": "Departamento não encontrado", "error-direct-message-file-upload-not-allowed": "Partilha de ficheiros não permitido em mensagens diretas", - "error-duplicate-channel-name": "Um canal com o nome {{channel_name}} existe", + "error-duplicate-channel-name": "Existe um canal com o nome {{room_name}}", "error-email-domain-blacklisted": "O domínio de e-mail está na lista negra", "error-email-send-failed": "Erro ao tentar enviar e-mail: {{message}}", + "error-save-image": "Erro ao salvar imagem", + "error-save-video": "Erro ao salvar vídeo", "error-field-unavailable": "{{field}} já está em uso :(", "error-file-too-large": "Ficheiro demasiado grande", "error-importer-not-defined": "O importador não foi definido correctamente, a classe Import está em falta.", "error-input-is-not-a-valid-field": "{{input}} não é um {{field}} válido", "error-invalid-actionlink": "Link de acção inválido", "error-invalid-arguments": "Argumentos inválidos", - "error-invalid-asset": "Ficheiro inválida", + "error-invalid-asset": "Ficheiro inválido", "error-invalid-channel": "Canal inválido.", "error-invalid-channel-start-with-chars": "Canal inválido. Começa por @ ou #", "error-invalid-custom-field": "Campo personalizado inválido", @@ -58,6 +61,7 @@ "error-message-editing-blocked": "A edição de mensagens está bloqueada", "error-message-size-exceeded": "O tamanho da mensagem excede Message_MaxAllowedSize", "error-missing-unsubscribe-link": "Você deve fornecer o link para cancelar a subscrição: [unsubscribe].", + "error-no-owner-channel": "Você não é dono do canal", "error-no-tokens-for-this-user": "Não há tokens para este utilizador", "error-not-allowed": "Não permitido", "error-not-authorized": "Não autorizado", @@ -75,33 +79,46 @@ "error-user-registration-disabled": "O registo de utilizadores está desactivado", "error-user-registration-secret": "O registo de utilizadores só é permitido por meio de um URL secreto", "error-you-are-last-owner": "Você é o último proprietário. Por favor, defina novo proprietário antes de sair da sala.", + "error-status-not-allowed": "O estado invisível está desactivado", "Actions": "Acções", "activity": "actividade", "Activity": "Actividade", "Add_Reaction": "Adicionar Reacção", "Add_Server": "Adicionar Servidor", "Add_users": "Adicionar utilizadores", + "Admin_Panel": "Painel de Administração", + "Agent": "Agente", "Alert": "Alerta", "alert": "alerta", "alerts": "alertas", "All_users_in_the_channel_can_write_new_messages": "Todos os utilizadores no canal podem escrever novas mensagens", + "All_users_in_the_team_can_write_new_messages": "Todos os usuários da equipa podem escrever novas mensagens", + "A_meaningful_name_for_the_discussion_room": "Um nome significativo para a sala de discussão", "All": "Todos", + "All_Messages": "Todas as Mensagens", "Allow_Reactions": "Permitir Reacções", "Alphabetical": "Alfabética", "and_more": "e mais", "and": "e", "announcement": "anúncio", "Announcement": "Anúncio", + "Apply_Your_Certificate": "Aplique o seu Certificado", "ARCHIVE": "ARQUIVAR", "archive": "arquivar", "are_typing": "estão a escrever", "Are_you_sure_question_mark": "Tem a certeza?", "Are_you_sure_you_want_to_leave_the_room": "Tem certeza de que quer sair da sala {{room}}?", + "Audio": "Áudio", "Authenticating": "Autenticando", + "Automatic": "Automático", + "Auto_Translate": "Auto-Tradução", "Avatar_changed_successfully": "Avatar alterado com sucesso!", "Avatar_Url": "URL do Avatar", "Away": "Ausente", + "Back": "Voltar", + "Black": "Preto", "Block_user": "Bloquear utilizador", + "Browser": "Navegador", "Broadcast_channel_Description": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder", "Broadcast_Channel": "Canal de Transmissão", "Busy": "Ocupado", @@ -111,80 +128,187 @@ "Cancel": "Cancelar", "changing_avatar": "a alterar avatar", "creating_channel": "a criar canal", + "creating_invite": "a criar convite", "Channel_Name": "Nome do Canal", "Channels": "Canais", "Chats": "Chats", + "Call_already_ended": "Chamada já terminada!", + "Clear_cookies_alert": "Quer limpar todas as cookies?", + "Clear_cookies_desc": "Esta acção irá limpar todos os cookies de login, permitindo que você faça login em outras contas.", + "Clear_cookies_yes": "Sim, limpar cookies", + "Clear_cookies_no": "Não, guardar cookies", + "Click_to_join": "Clique para Entrar!", "Close": "Fechar", "Close_emoji_selector": "Fechar selector de emoticons", + "Closing_chat": "A fechar o chat", + "Change_language_loading": "Mudança de idioma.", + "Chat_closed_by_agent": "Chat fechado por agente", "Choose": "Escolher", "Choose_from_library": "Escolher da biblioteca", + "Choose_file": "Escolher arquivo", + "Choose_where_you_want_links_be_opened": "Escolha onde você quer que os links sejam abertos", "Code": "Código", + "Code_or_password_invalid": "Código ou senha inválidos", "Collaborative": "Colaborativa", "Confirm": "Confirmar", "Connect": "Ligar", "Connected": "Ligado", + "connecting_server": "conexão ao servidor", "Connecting": "A ligar...", + "Contact_us": "Contacte-nos", + "Contact_your_server_admin": "Contacte o administrador do seu servidor.", "Continue_with": "Continuar com", "Copied_to_clipboard": "Copiado para a área de transferência!", "Copy": "Copiar", + "Conversation": "Conversa", "Permalink": "Link permanente", + "Certificate_password": "Senha do Certificado", + "Clear_cache": "Limpar a cache do servidor local", + "Clear_cache_loading": "A limpar a cache.", + "Whats_the_password_for_your_certificate": "Qual é a senha para o seu certificado?", "Create_account": "Criar uma conta", "Create_Channel": "Criar Canal", + "Create_Direct_Messages": "Criar Mensagens Diretas", + "Create_Discussion": "Criar Discussão", "Created_snippet": "criado um extracto", "Create_a_new_workspace": "Criar um novo espaço de trabalho", "Create": "Criar", + "Custom_Status": "Status Personalizado", + "Dark": "Escuro", + "Dark_level": "Nível Escuro", + "Default": "Predefinição", + "Default_browser": "Navegador predefinido", "Delete_Room_Warning": "Apagar uma sala irá remover todas as mensagens contidas nela. Isto não pode ser desfeito.", + "Department": "Departamento", "delete": "apagar", "Delete": "Apagar", "DELETE": "APAGAR", + "move": "mover", "deleting_room": "apagando sala", "description": "descrição", "Description": "Descrição", + "Desktop_Options": "Opções da área de trabalho", + "Desktop_Notifications": "Notificações da área de trabalho", + "Desktop_Alert_info": "Estas notificações são entregues na área de trabalho", + "Directory": "Directório", "Direct_Messages": "Mensagens Directas", "Disable_notifications": "Desactivar notificações", + "Discussions": "Discussões", + "Discussion_Desc": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, é criado um sub-canal do que você selecionou e ambos estão ligados.", + "Discussion_name": "Nome da discussão", + "Done": "Feito", "Dont_Have_An_Account": "Não tem uma conta?", + "Do_you_have_an_account": "Você tem uma conta?", + "Do_you_have_a_certificate": "Você tem um certificado?", "Do_you_really_want_to_key_this_room_question_mark": "Você quer mesmo {{key}} esta sala?", + "E2E_Encryption": "Encriptação E2E", + "E2E_How_It_Works_info1": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.", + "E2E_How_It_Works_info2": "Isto é *criptografia ponto a ponto* portanto a chave para codificar/descodificar as suas mensagens não será salva no servidor. Por essa razão *você precisa armazenar esta senha em algum lugar seguro* que você posa aceder mais tarde, se precisar.", + "E2E_How_It_Works_info3": "Se você prosseguir, uma senha E2E será gerada automaticamente.", + "E2E_How_It_Works_info4": "Você também pode configurar uma nova senha para sua chave de criptografia a qualquer momento a partir de qualquer navegador que você tenha inserido a senha existente do E2E.", "edit": "editar", + "edited": "editado", "Edit": "Editar", + "Edit_Status": "Editar Status", + "Edit_Invite": "Editar Convite", + "End_to_end_encrypted_room": "Sala encriptada de ponta a ponta", + "end_to_end_encryption": "encriptação de ponta a ponta", + "Email_Notification_Mode_All": "Cada Menção/DM", + "Email_Notification_Mode_Disabled": "Desactivado", "Email_or_password_field_is_empty": "O campo de e-mail ou palavra-passe está vazio", "Email": "E-mail", "email": "e-mail", + "Empty_title": "Título vazio", + "Enable_Auto_Translate": "Activar Auto-Tradução", "Enable_notifications": "Activar notificações", + "Encrypted": "Encriptado", + "Encrypted_message": "Mensagem encriptada", + "Enter_Your_E2E_Password": "Digite a sua senha E2E", + "Enter_Your_Encryption_Password_desc1": "Isto permitir-lhe-á aceder aos seus grupos privados encriptados e às suas mensagens directas.", + "Enter_Your_Encryption_Password_desc2": "Você precisa digitar a senha para codificar/descodificar mensagens em cada lugar que você usar o chat.", + "Encryption_error_title": "A sua senha de encriptação parece errada", + "Encryption_error_desc": "Não foi possível descodificar a sua chave de encriptação para ser importada.", "Everyone_can_access_this_channel": "Todos podem aceder a este canal", + "Everyone_can_access_this_team": "Todos podem aceder a esta equipa", "Error_uploading": "Erro ao fazer o envio", + "Expiration_Days": "Validade (Dias)", + "Favorite": "Favorito", "Favorites": "Favoritos", "Files": "Ficheiros", "File_description": "Descrição do ficheiro", "File_name": "Nome do ficheiro", "Finish_recording": "Terminar a gravação", + "Following_thread": "Seguir discussão", "For_your_security_you_must_enter_your_current_password_to_continue": "Para sua segurança, você deve escrever a sua palavra-passe actual para continuar", "Forgot_password_If_this_email_is_registered": "Se este e-mail estiver registado, enviaremos instruções sobre como repor a sua palavra-passe. Se você não receber um e-mail em breve, volte e tente novamente.", "Forgot_password": "Esquecer palavra-passe", "Forgot_Password": "Esquecer Palavra-passe", + "Forward": "Reencaminhar", + "Forward_Chat": "Reencaminhar Chat", + "Forward_to_department": "Reencaminhar para o departamento", + "Forward_to_user": "Reencaminhar para o utilizador", + "Full_table": "Clique para ver a tabela completa", + "Generate_New_Link": "Gerar Novo Link", "Group_by_favorites": "Agrupar por favoritos", "Group_by_type": "Agrupar por tipo", + "Hide": "Esconder", "Has_joined_the_channel": "entrou no canal", "Has_joined_the_conversation": "entrou na conversa", "Has_left_the_channel": "saiu do canal", + "Hide_System_Messages": "Esconder mensagens do sistema", + "Hide_type_messages": "Esconder mensagens \"{{type}}\"", + "How_It_Works": "Como Funciona", + "Message_HideType_uj": "Utilizador entrou", + "Message_HideType_ul": "Utilizador saiu", + "Message_HideType_ru": "Utilizador removido", + "Message_HideType_au": "Utilizador adicionado", + "Message_HideType_mute_unmute": "Utilizador silenciado/de-silenciado", + "Message_HideType_r": "Nome da sala alterado", + "Message_HideType_ut": "Utilizador entrou na conversação", + "Message_HideType_wm": "Bem-vindo", + "Message_HideType_rm": "Mensagem Removida", + "Message_HideType_subscription_role_added": "Foi definido o estatuto", + "Message_HideType_subscription_role_removed": "Definição de estatuto removida", + "Message_HideType_room_archived": "Sala arquivada", + "Message_HideType_room_unarchived": "Sala desarquivada", + "I_Saved_My_E2E_Password": "Guardei a minha senha E2E", + "IP": "IP", + "In_app": "Na aplicação", + "In_App_And_Desktop": "Na aplicação e área de trabalho", + "In_App_and_Desktop_Alert_info": "Exibe um banner no topo da tela quando a aplicação está aberto, e exibe uma notificação na área de trabalho", "Invisible": "Invisível", "Invite": "Convidar", "is_a_valid_RocketChat_instance": "é uma instância válida do Rocket.Chat", "is_not_a_valid_RocketChat_instance": "is not a valid Rocket.Chat instance", "is_typing": "está a escrever", + "Invalid_or_expired_invite_token": "Token de convite invalido ou expirado", "Invalid_server_version": "O servidor ao qual esta tentando ligar-se, utiliza uma versão que não é suporta pela aplicação: {{currentVersion}}.\n\nA versão mínima requerida é {{minVersion}}", + "Invite_Link": "Link de convite", + "Invite_users": "Convidar utilizadores", "Join": "Entrar", + "Join_Code": "Código de entrada", + "Insert_Join_Code": "Insira o código de entrada", + "Join_our_open_workspace": "Junte-se ao nosso espaço de trabalho aberto", + "Join_your_workspace": "Junte-se ao seu espaço de trabalho", "Just_invited_people_can_access_this_channel": "Apenas utilizadores convidados podem aceder a este canal", + "Just_invited_people_can_access_this_team": "Apenas pessoas convidadas podem aceder a esta equipa", "Language": "Idioma", "last_message": "última mensagem", "Leave_channel": "Sair do canal", "leaving_room": "a sair da sala", + "Leave": "Sair", "leave": "sair", "Legal": "Legal", + "Light": "Luz", + "License": "Licença", "Livechat": "Livechat", "Login": "Entrar", "Login_error": "As suas credenciais foram rejeitadas! Por favor, tente novamente.", "Login_with": "Entrar com", + "Logging_out": "A terminar a sessão.", "Logout": "Sair", + "Max_number_of_uses": "Número máximo de utilizações", + "Max_number_of_users_allowed_is_number": "O número máximo de utilizadores permitido é {{maxUsers}}", "members": "membros", "Members": "Membros", "Mentioned_Messages": "Mensagens Mencionadas", @@ -194,7 +318,13 @@ "Message_actions": "Acções de mensagem", "Message_pinned": "Mensagem afixada", "Message_removed": "Mensagem removida", + "Message_starred": "Mensagem estrelada", + "Message_unstarred": "Mensagem não estrelada", + "message": "mensagem", + "messages": "mensagens", + "Message": "Mensagem", "Messages": "Mensagens", + "Message_Reported": "Mensagem reportada", "Microphone_Permission_Message": "O Rocket.Chat necessita de acesso ao seu microfone para que você possa enviar mensagens de áudio.", "Microphone_Permission": "Permissão de Microfone", "Mute": "Silenciar", @@ -202,52 +332,91 @@ "My_servers": "Meus servidores", "N_people_reacted": "{{n}} pessoas reagiram", "N_users": "{{n}} utilizadores", + "N_channels": "{{n}} canais", "name": "nome", "Name": "Nome", + "Navigation_history": "Histórico de navegação", + "Never": "Nunca", "New_Message": "Nova Mensagem", "New_Password": "Nova Palavra-passe", "New_Server": "Novo Servidor", "Next": "Próximo", "No_files": "Nenhum ficheiro", + "No_limit": "Sem limite", "No_mentioned_messages": "Nenhuma mensagem mencionada", "No_pinned_messages": "Nenhuma mensagem afixada", "No_results_found": "Nenhum resultado encontrado", "No_starred_messages": "Nenhuma mensagem marcada com estrela", + "No_thread_messages": "Sem mensagens de discussão ", + "No_label_provided": "{{label}} não fornecida/o", "No_Message": "Nenhuma mensagem", + "No_messages_yet": "Ainda sem mensagens", "No_Reactions": "Nenhuma reação", + "No_Read_Receipts": "Sem recibos de leitura", "Not_logged": "Não ligado", + "Not_RC_Server": "Isto não é um servidor Rocket.Chat.\n{{contact}}", + "Nothing": "Nada", "Nothing_to_save": "Nada para guardar!", "Notify_active_in_this_room": "Notifica utilizadores activos nesta sala", "Notify_all_in_this_room": "Notifica todos os utilizadores nesta sala", + "Notifications": "Notificações", + "Notification_Duration": "Duração da Notificação", + "Notification_Preferences": "Preferências de Notificação", + "No_available_agents_to_transfer": "Não há agentes disponíveis para transferir", "Offline": "Desligado", "Oops": "Oops!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Chats em andamento", + "Omnichannel_enable_alert": "Você não está disponível no Omnichannel. Você gostaria de estar disponível?", + "Onboarding_description": "Um espaço de trabalho é o espaço da sua equipa ou organização para colaborar. Peça ao administrador do espaço de trabalho um endereço para se juntar ou criar um para a sua equipa.", + "Onboarding_join_workspace": "Junte-se a um espaço de trabalho", + "Onboarding_subtitle": "Além da Colaboração da Equipe", "Onboarding_title": "Bem vindo(a) ao Rocket.Chat", + "Onboarding_join_open_description": "Junte-se ao nosso espaço de trabalho aberto para conversar com a equipa e comunidade Rocket.Chat.", + "Onboarding_agree_terms": "Ao continuar, você concorda com Rocket.Chat", + "Onboarding_less_options": "Menos opções", + "Onboarding_more_options": "Mais opções", "Online": "Ligado", "Only_authorized_users_can_write_new_messages": "Apenas utilizadores autorizados podem escrever novas mensagens", "Open_emoji_selector": "Abra o selector de emoticons", "Open_Source_Communication": "Comunicação Open Source", + "Open_your_authentication_app_and_enter_the_code": "Abra o seu aplicativo de autenticação e digite o código.", + "OR": "OU", + "OS": "OS", + "Overwrites_the_server_configuration_and_use_room_config": "Sobrescreve a configuração do servidor e a configuração da sala de uso", "Password": "Palavra-passe", + "Parent_channel_or_group": "Canal de origem ou grupo", "Permalink_copied_to_clipboard": "Link permanente copiado para a área de transferência!", + "Phone": "Telefone", "Pin": "Afixar", "Pinned_Messages": "Mensagens Afixadas", "pinned": "afixada", "Pinned": "Afixada", + "Please_add_a_comment": "Por favor, acrescente um comentário", "Please_enter_your_password": "Por favor, introduza a sua palavra-passe", + "Please_wait": "Por favor, espere.", + "Preferences": "Preferências", "Preferences_saved": "Preferências guardadas!", "Privacy_Policy": " Política de Privacidade", "Private_Channel": "Canal Privado", - "Private_Groups": "Grupos Privados", "Private": "Privado", + "Processing": "A processar...", "Profile_saved_successfully": "Perfil actualizado com sucesso!", "Profile": "Perfil", "Public_Channel": "Canal Público", "Public": "Público", + "Push_Notifications": "Notificações Push", + "Push_Notifications_Alert_Info": "Estas notificações são entregues quando o aplicativo não está aberto", "Quote": "Citar", "Reactions_are_disabled": "Reacções desactivadas", "Reactions_are_enabled": "Reacções activadas", "Reactions": "Reacções", + "Read": "Ler", + "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, média e arquivos em seu dispositivo", + "Read_External_Permission": "Permissão de leitura da média", "Read_Only_Channel": "Canal só de leitura", "Read_Only": "Só de Leitura", + "Read_Receipt": "Recibos de leitura", "Register": "Registar", "Repeat_Password": "Repita a palavra-passe", "Reply": "Responder", diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json index e5fca9504..58174b8ea 100644 --- a/app/i18n/locales/ru.json +++ b/app/i18n/locales/ru.json @@ -400,7 +400,6 @@ "Preferences_saved": "Настройки сохранены!", "Privacy_Policy": " Политика конфиденциальности", "Private_Channel": "Приватный канал", - "Private_Groups": "Приватные группы", "Private": "Приватный", "Processing": "Обработка...", "Profile_saved_successfully": "Профиль успешно сохранен!", @@ -724,6 +723,7 @@ "creating_team": "создание Команды", "team-name-already-exists": "Команда с таким названием уже существует", "Add_Channel_to_Team": "Добавить канал в Команду", + "Left_The_Team_Successfully": "Успешно покинул команду", "Create_New": "Создать", "Add_Existing": "Добавить существующее", "Add_Existing_Channel": "Добавить существующий канал", diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json index 82959de8b..bc58ee92b 100644 --- a/app/i18n/locales/tr.json +++ b/app/i18n/locales/tr.json @@ -393,7 +393,6 @@ "Preferences_saved": "Tercihler kaydedildi!", "Privacy_Policy": " Privacy Policy", "Private_Channel": "Özel Kanal", - "Private_Groups": "Özel Gruplar", "Private": "Özel", "Processing": "İşleniyor...", "Profile_saved_successfully": "Profil başarıyla kaydedildi!", diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json index 1b6fe974c..4836d9a83 100644 --- a/app/i18n/locales/zh-CN.json +++ b/app/i18n/locales/zh-CN.json @@ -390,7 +390,6 @@ "Preferences_saved": "偏好已保存!", "Privacy_Policy": "隐私政策", "Private_Channel": "私人频道", - "Private_Groups": "私人群组", "Private": "私有的", "Processing": "处理中", "Profile_saved_successfully": "个人资料保存成功!", diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json index 6f60d155a..29626191c 100644 --- a/app/i18n/locales/zh-TW.json +++ b/app/i18n/locales/zh-TW.json @@ -391,7 +391,6 @@ "Preferences_saved": "偏好設定已被儲存!", "Privacy_Policy": "隱私政策", "Private_Channel": "私人頻道", - "Private_Groups": "私人群組", "Private": "私有的", "Processing": "處理中", "Profile_saved_successfully": "個人資料儲存成功!", diff --git a/app/index.js b/app/index.js index 048323a5b..efcf1557e 100644 --- a/app/index.js +++ b/app/index.js @@ -75,7 +75,7 @@ export default class Root extends React.Component { theme: defaultTheme(), themePreferences: { currentTheme: supportSystemTheme() ? 'automatic' : 'light', - darkLevel: 'dark' + darkLevel: 'black' }, width, height, diff --git a/app/lib/database/model/servers/User.js b/app/lib/database/model/servers/User.js index 5b4d3d65a..6d78c27fa 100644 --- a/app/lib/database/model/servers/User.js +++ b/app/lib/database/model/servers/User.js @@ -22,7 +22,7 @@ export default class User extends Model { @field('avatar_etag') avatarETag; - @field('login_email_password') loginEmailPassword; - @field('show_message_in_main_thread') showMessageInMainThread; + + @field('is_from_webview') isFromWebView; } diff --git a/app/lib/database/model/servers/migrations.js b/app/lib/database/model/servers/migrations.js index 782700a37..80954d475 100644 --- a/app/lib/database/model/servers/migrations.js +++ b/app/lib/database/model/servers/migrations.js @@ -95,6 +95,16 @@ export default schemaMigrations({ ] }) ] + }, { + toVersion: 11, + steps: [ + addColumns({ + table: 'users', + columns: [ + { name: 'is_from_webview', type: 'boolean', isOptional: true } + ] + }) + ] } ] }); diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js index 0c81a72fc..1105cf165 100644 --- a/app/lib/database/schema/servers.js +++ b/app/lib/database/schema/servers.js @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ - version: 10, + version: 11, tables: [ tableSchema({ name: 'users', @@ -15,7 +15,8 @@ export default appSchema({ { name: 'roles', type: 'string', isOptional: true }, { name: 'login_email_password', type: 'boolean', isOptional: true }, { name: 'show_message_in_main_thread', type: 'boolean', isOptional: true }, - { name: 'avatar_etag', type: 'string', isOptional: true } + { name: 'avatar_etag', type: 'string', isOptional: true }, + { name: 'is_from_webview', type: 'boolean', isOptional: true } ] }), tableSchema({ diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index 2b7da4765..1c4c56b46 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -6,6 +6,7 @@ import { compareServerVersion, methods } from '../utils'; import database from '../database'; import log from '../../utils/log'; import reduxStore from '../createStore'; +import RocketChat from '../rocketchat'; import protectedFunction from './helpers/protectedFunction'; import { setPermissions as setPermissionsAction } from '../../actions/permissions'; @@ -46,7 +47,8 @@ const PERMISSIONS = [ 'view-statistics', 'view-user-administration', 'view-all-teams', - 'view-all-team-channels' + 'view-all-team-channels', + 'convert-team' ]; export async function setPermissions() { @@ -128,7 +130,7 @@ export function getPermissions() { const db = database.active; const permissionsCollection = db.get('permissions'); const allRecords = await permissionsCollection.query().fetch(); - + RocketChat.subscribe('stream-notify-logged', 'permissions-changed'); // if server version is lower than 0.73.0, fetches from old api if (compareServerVersion(serverVersion, '0.73.0', methods.lowerThan)) { // RC 0.66.0 diff --git a/app/lib/methods/getRoles.js b/app/lib/methods/getRoles.js index df22d382b..98a3895e3 100644 --- a/app/lib/methods/getRoles.js +++ b/app/lib/methods/getRoles.js @@ -2,9 +2,66 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import database from '../database'; import log from '../../utils/log'; +import reduxStore from '../createStore'; import protectedFunction from './helpers/protectedFunction'; +import { + removeRoles, setRoles as setRolesAction, updateRoles +} from '../../actions/roles'; -export default function() { +export async function setRoles() { + const db = database.active; + const rolesCollection = db.collections.get('roles'); + const allRoles = await rolesCollection.query().fetch(); + const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {}); + reduxStore.dispatch(setRolesAction(parsed)); +} + +export async function onRolesChanged(ddpMessage) { + const { type, _id, description } = ddpMessage.fields.args[0]; + if (/changed/.test(type)) { + const db = database.active; + const rolesCollection = db.get('roles'); + try { + const rolesRecord = await rolesCollection.find(_id); + try { + await db.action(async() => { + await rolesRecord.update((u) => { + u.description = description; + }); + }); + } catch (e) { + log(e); + } + reduxStore.dispatch(updateRoles(_id, description)); + } catch (err) { + try { + await db.action(async() => { + await rolesCollection.create((post) => { + post._raw = sanitizedRaw({ id: _id, description }, rolesCollection.schema); + }); + }); + } catch (e) { + log(e); + } + reduxStore.dispatch(updateRoles(_id, description || _id)); + } + } + if (/removed/.test(type)) { + const db = database.active; + const rolesCollection = db.get('roles'); + try { + const rolesRecord = await rolesCollection.find(_id); + await db.action(async() => { + await rolesRecord.destroyPermanently(); + }); + reduxStore.dispatch(removeRoles(_id)); + } catch (err) { + console.log(err); + } + } +} + +export function getRoles() { const db = database.active; return new Promise(async(resolve) => { try { @@ -50,6 +107,7 @@ export default function() { } catch (e) { log(e); } + setRoles(); return allRecords.length; }); return resolve(); diff --git a/app/lib/methods/getSettings.js b/app/lib/methods/getSettings.js index 6935631ef..c51ac4068 100644 --- a/app/lib/methods/getSettings.js +++ b/app/lib/methods/getSettings.js @@ -131,6 +131,10 @@ export async function setSettings() { reduxStore.dispatch(addSettings(RocketChat.parseSettings(parsed.slice(0, parsed.length)))); } +export function subscribeSettings() { + return RocketChat.subscribe('stream-notify-all', 'public-settings-changed'); +} + export default async function() { try { const db = database.active; diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 6e7b3bcb2..59ee94fc5 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -28,7 +28,7 @@ import getUsersPresence, { getUserPresence, subscribeUsersPresence } from './met import protectedFunction from './methods/helpers/protectedFunction'; import readMessages from './methods/readMessages'; -import getSettings, { getLoginSettings, setSettings } from './methods/getSettings'; +import getSettings, { getLoginSettings, setSettings, subscribeSettings } from './methods/getSettings'; import getRooms from './methods/getRooms'; import { setPermissions, getPermissions } from './methods/getPermissions'; @@ -37,7 +37,7 @@ import { getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable } from './methods/enterpriseModules'; import getSlashCommands from './methods/getSlashCommands'; -import getRoles from './methods/getRoles'; +import { getRoles, setRoles, onRolesChanged } from './methods/getRoles'; import canOpenRoom from './methods/canOpenRoom'; import triggerBlockAction, { triggerSubmitView, triggerCancel } from './methods/actions'; @@ -63,7 +63,9 @@ import UserPreferences from './userPreferences'; import { Encryption } from './encryption'; import EventEmitter from '../utils/events'; import { sanitizeLikeString } from './database/utils'; +import { updatePermission } from '../actions/permissions'; import { TEAM_TYPE } from '../definition/ITeam'; +import { updateSettings } from '../actions/settings'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; const CURRENT_SERVER = 'currentServer'; @@ -225,6 +227,14 @@ const RocketChat = { this.usersListener.then(this.stopListener); } + if (this.notifyAllListener) { + this.notifyAllListener.then(this.stopListener); + } + + if (this.rolesListener) { + this.rolesListener.then(this.stopListener); + } + if (this.notifyLoggedListener) { this.notifyLoggedListener.then(this.stopListener); } @@ -274,6 +284,31 @@ const RocketChat = { this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage))); + this.notifyAllListener = this.sdk.onStreamData('stream-notify-all', protectedFunction(async(ddpMessage) => { + const { eventName } = ddpMessage.fields; + if (/public-settings-changed/.test(eventName)) { + const { _id, value } = ddpMessage.fields.args[1]; + const db = database.active; + const settingsCollection = db.get('settings'); + try { + const settingsRecord = await settingsCollection.find(_id); + const { type } = defaultSettings[_id]; + if (type) { + await db.action(async() => { + await settingsRecord.update((u) => { + u[type] = value; + }); + }); + } + reduxStore.dispatch(updateSettings(_id, value)); + } catch (e) { + log(e); + } + } + })); + + this.rolesListener = this.sdk.onStreamData('stream-roles', protectedFunction(ddpMessage => onRolesChanged(ddpMessage))); + this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction(async(ddpMessage) => { const { eventName } = ddpMessage.fields; if (/user-status/.test(eventName)) { @@ -310,6 +345,21 @@ const RocketChat = { } catch { // We can't create a new record since we don't receive the user._id } + } else if (/permissions-changed/.test(eventName)) { + const { _id, roles } = ddpMessage.fields.args[1]; + const db = database.active; + const permissionsCollection = db.get('permissions'); + try { + const permissionsRecord = await permissionsCollection.find(_id); + await db.action(async() => { + await permissionsRecord.update((u) => { + u.roles = roles; + }); + }); + reduxStore.dispatch(updatePermission(_id, roles)); + } catch (err) { + // + } } else if (/Users:NameChanged/.test(eventName)) { const userNameChanged = ddpMessage.fields.args[0]; const db = database.active; @@ -476,10 +526,10 @@ const RocketChat = { return this.post('users.forgotPassword', { email }, false); }, - loginTOTP(params, loginEmailPassword) { + loginTOTP(params, loginEmailPassword, isFromWebView = false) { return new Promise(async(resolve, reject) => { try { - const result = await this.login(params, loginEmailPassword); + const result = await this.login(params, isFromWebView); return resolve(result); } catch (e) { if (e.data?.error && (e.data.error === 'totp-required' || e.data.error === 'totp-invalid')) { @@ -542,15 +592,15 @@ const RocketChat = { return this.loginTOTP(params, true); }, - async loginOAuthOrSso(params) { - const result = await this.loginTOTP(params); - reduxStore.dispatch(loginRequest({ resume: result.token })); + async loginOAuthOrSso(params, isFromWebView = true) { + const result = await this.loginTOTP(params, false, isFromWebView); + reduxStore.dispatch(loginRequest({ resume: result.token }, false, isFromWebView)); }, - async login(params, loginEmailPassword) { + async login(credentials, isFromWebView = false) { const sdk = this.shareSDK || this.sdk; // RC 0.64.0 - await sdk.login(params); + await sdk.login(credentials); const { result } = sdk.currentLogin; const user = { id: result.userId, @@ -565,7 +615,7 @@ const RocketChat = { emails: result.me.emails, roles: result.me.roles, avatarETag: result.me.avatarETag, - loginEmailPassword, + isFromWebView, showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true }; return user; @@ -816,6 +866,13 @@ const RocketChat = { }; return this.sdk.post(type === 'c' ? 'channels.convertToTeam' : 'groups.convertToTeam', params); }, + convertTeamToChannel({ teamId, selected }) { + const params = { + teamId, + ...(selected.length && { roomsToRemove: selected }) + }; + return this.sdk.post('teams.convertToChannel', params); + }, joinRoom(roomId, joinCode, type) { // TODO: join code // RC 0.48.0 @@ -833,6 +890,7 @@ const RocketChat = { getSettings, getLoginSettings, setSettings, + subscribeSettings, getPermissions, setPermissions, getCustomEmojis, @@ -843,6 +901,7 @@ const RocketChat = { isOmnichannelModuleAvailable, getSlashCommands, getRoles, + setRoles, parseSettings: settings => settings.reduce((ret, item) => { ret[item._id] = defaultSettings[item._id] && item[defaultSettings[item._id].type]; if (item._id === 'Hide_System_Messages') { diff --git a/app/reducers/index.js b/app/reducers/index.js index dfee5f3eb..eb8f09d82 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -19,6 +19,7 @@ import createDiscussion from './createDiscussion'; import enterpriseModules from './enterpriseModules'; import encryption from './encryption'; import permissions from './permissions'; +import roles from './roles'; import inquiry from '../ee/omnichannel/reducers/inquiry'; @@ -43,5 +44,6 @@ export default combineReducers({ inquiry, enterpriseModules, encryption, - permissions + permissions, + roles }); diff --git a/app/reducers/permissions.js b/app/reducers/permissions.js index 1b3a14ec2..034c51420 100644 --- a/app/reducers/permissions.js +++ b/app/reducers/permissions.js @@ -1,13 +1,16 @@ import { PERMISSIONS } from '../actions/actionsTypes'; -const initialState = { - permissions: {} -}; +const initialState = {}; export default function permissions(state = initialState, action) { switch (action.type) { case PERMISSIONS.SET: return action.permissions; + case PERMISSIONS.UPDATE: + return { + ...state, + [action.payload.id]: action.payload.roles + }; default: return state; } diff --git a/app/reducers/roles.js b/app/reducers/roles.js new file mode 100644 index 000000000..93cbffcb0 --- /dev/null +++ b/app/reducers/roles.js @@ -0,0 +1,22 @@ +import { ROLES } from '../actions/actionsTypes'; + +const initialState = {}; + +export default function permissions(state = initialState, action) { + switch (action.type) { + case ROLES.SET: + return action.roles; + case ROLES.UPDATE: + return { + ...state, + [action.payload.id]: action.payload.desc || action.payload.id + }; + case ROLES.REMOVE: { + const newState = { ...state }; + delete newState[action.payload.id]; + return newState; + } + default: + return state; + } +} diff --git a/app/reducers/room.js b/app/reducers/room.js index de47fcede..980715051 100644 --- a/app/reducers/room.js +++ b/app/reducers/room.js @@ -28,7 +28,7 @@ export default function(state = initialState, action) { case ROOM.DELETE: return { ...state, - rid: action.rid, + rid: action.room.rid, isDeleting: true }; case ROOM.CLOSE: diff --git a/app/reducers/settings.js b/app/reducers/settings.js index 9e037b1d0..6e9ab5005 100644 --- a/app/reducers/settings.js +++ b/app/reducers/settings.js @@ -9,6 +9,11 @@ export default (state = initialState, action) => { ...state, ...action.payload }; + case SETTINGS.UPDATE: + return { + ...state, + [action.payload.id]: action.payload.value + }; case SETTINGS.CLEAR: return initialState; default: diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index a768916c8..9493d5405 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -41,10 +41,10 @@ const handleRequest = function* handleRequest({ data }) { encrypted } = data; logEvent(events.CT_CREATE, { - type, - readOnly, - broadcast, - encrypted + type: `${ type }`, + readOnly: `${ readOnly }`, + broadcast: `${ broadcast }`, + encrypted: `${ encrypted }` }); const result = yield call(createTeam, data); sub = { diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 556573a54..a406f56a9 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -97,7 +97,7 @@ const fallbackNavigation = function* fallbackNavigation() { const handleOAuth = function* handleOAuth({ params }) { const { credentialToken, credentialSecret } = params; try { - yield RocketChat.loginOAuthOrSso({ oauth: { credentialToken, credentialSecret } }); + yield RocketChat.loginOAuthOrSso({ oauth: { credentialToken, credentialSecret } }, false); } catch (e) { log(e); } diff --git a/app/sagas/login.js b/app/sagas/login.js index 77eda7611..9381e0459 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -30,15 +30,15 @@ import Navigation from '../lib/Navigation'; const getServer = state => state.server.server; const loginWithPasswordCall = args => RocketChat.loginWithPassword(args); -const loginCall = args => RocketChat.login(args); +const loginCall = (credentials, isFromWebView) => RocketChat.login(credentials, isFromWebView); const logoutCall = args => RocketChat.logout(args); -const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false }) { +const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false, isFromWebView = false }) { logEvent(events.LOGIN_DEFAULT_LOGIN); try { let result; if (credentials.resume) { - result = yield call(loginCall, credentials); + result = yield loginCall(credentials, isFromWebView); } else { result = yield call(loginWithPasswordCall, credentials); } @@ -68,7 +68,6 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE log(e); } }); - yield put(loginSuccess(result)); } } catch (e) { @@ -81,6 +80,10 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE } }; +const subscribeSettings = function* subscribeSettings() { + yield RocketChat.subscribeSettings(); +}; + const fetchPermissions = function* fetchPermissions() { yield RocketChat.getPermissions(); }; @@ -90,6 +93,7 @@ const fetchCustomEmojis = function* fetchCustomEmojis() { }; const fetchRoles = function* fetchRoles() { + RocketChat.subscribe('stream-roles', 'roles'); yield RocketChat.getRoles(); }; @@ -133,6 +137,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { yield fork(registerPushToken); yield fork(fetchUsersPresence); yield fork(fetchEnterpriseModules, { user }); + yield fork(subscribeSettings); yield put(encryptionInit()); setLanguage(user?.language); @@ -147,14 +152,13 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { status: user.status, statusText: user.statusText, roles: user.roles, - loginEmailPassword: user.loginEmailPassword, + isFromWebView: user.isFromWebView, showMessageInMainThread: user.showMessageInMainThread, avatarETag: user.avatarETag }; yield serversDB.action(async() => { try { const userRecord = await usersCollection.find(user.id); - u.loginEmailPassword = userRecord?.loginEmailPassword; await userRecord.update((record) => { record._raw = sanitizedRaw({ id: user.id, ...record._raw }, usersCollection.schema); Object.assign(record, u); diff --git a/app/sagas/room.js b/app/sagas/room.js index e17a06da9..70a0bc4da 100644 --- a/app/sagas/room.js +++ b/app/sagas/room.js @@ -32,7 +32,7 @@ const watchUserTyping = function* watchUserTyping({ rid, status }) { } }; -const handleRemovedRoom = function* handleRemovedRoom(roomType) { +const handleRemovedRoom = function* handleRemovedRoom(roomType, actionType) { const isMasterDetail = yield select(state => state.app.isMasterDetail); if (isMasterDetail) { yield Navigation.navigate('DrawerNavigator'); @@ -40,9 +40,13 @@ const handleRemovedRoom = function* handleRemovedRoom(roomType) { yield Navigation.navigate('RoomsListView'); } - if (roomType === 'team') { - EventEmitter.emit(LISTENER, { message: I18n.t('Left_The_Team_Successfully') }); + if (actionType === 'leave') { + EventEmitter.emit(LISTENER, { message: roomType === 'team' ? I18n.t('Left_The_Team_Successfully') : I18n.t('Left_The_Room_Successfully') }); } + if (actionType === 'delete') { + EventEmitter.emit(LISTENER, { message: roomType === 'team' ? I18n.t('Deleted_The_Team_Successfully') : I18n.t('Deleted_The_Room_Successfully') }); + } + // types.ROOM.REMOVE is triggered by `subscriptions-changed` with `removed` arg const { timeout } = yield race({ @@ -66,7 +70,7 @@ const handleLeaveRoom = function* handleLeaveRoom({ room, roomType, selected }) } if (result?.success) { - yield handleRemovedRoom(roomType); + yield handleRemovedRoom(roomType, 'leave'); } } catch (e) { logEvent(events.RA_LEAVE_F); @@ -80,16 +84,23 @@ const handleLeaveRoom = function* handleLeaveRoom({ room, roomType, selected }) } }; -const handleDeleteRoom = function* handleDeleteRoom({ rid, t }) { +const handleDeleteRoom = function* handleDeleteRoom({ room, roomType, selected }) { logEvent(events.RI_EDIT_DELETE); try { - const result = yield RocketChat.deleteRoom(rid, t); - if (result.success) { - yield handleRemovedRoom(); + let result = {}; + + if (roomType === 'channel') { + result = yield RocketChat.deleteRoom(room.rid, room.t); + } else if (roomType === 'team') { + result = yield RocketChat.deleteTeam({ teamId: room.teamId, ...(selected && { roomsToRemove: selected }) }); + } + + if (result?.success) { + yield handleRemovedRoom(roomType, 'delete'); } } catch (e) { logEvent(events.RI_EDIT_DELETE_F); - Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_room') })); + Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: roomType === 'team' ? I18n.t('deleting_team') : I18n.t('deleting_room') })); } }; diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index d367274a3..2abf47061 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -125,6 +125,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch RocketChat.setSettings(); RocketChat.setCustomEmojis(); RocketChat.setPermissions(); + RocketChat.setRoles(); RocketChat.setEnterpriseModules(); let serverInfo; diff --git a/app/share.js b/app/share.js index bfe011264..dbf0bfbff 100644 --- a/app/share.js +++ b/app/share.js @@ -124,7 +124,7 @@ class Root extends React.Component { theme: defaultTheme(), themePreferences: { currentTheme: supportSystemTheme() ? 'automatic' : 'light', - darkLevel: 'dark' + darkLevel: 'black' }, root: '', width, diff --git a/app/utils/log/events.js b/app/utils/log/events.js index a183735ba..2ab3f539b 100644 --- a/app/utils/log/events.js +++ b/app/utils/log/events.js @@ -259,6 +259,8 @@ export default { RA_LEAVE_TEAM_F: 'ra_leave_team_f', RA_CONVERT_TO_TEAM: 'ra_convert_to_team', RA_CONVERT_TO_TEAM_F: 'ra_convert_to_team_f', + RA_CONVERT_TEAM_TO_CHANNEL: 'ra_convert_team_to_channel', + RA_CONVERT_TEAM_TO_CHANNEL_F: 'ra_convert_team_to_channel_f', RA_MOVE_TO_TEAM: 'ra_move_to_team', RA_MOVE_TO_TEAM_F: 'ra_move_to_team_f', RA_SEARCH_TEAM: 'ra_search_team', diff --git a/app/utils/openLink.js b/app/utils/openLink.js index 36f72c0e2..703625497 100644 --- a/app/utils/openLink.js +++ b/app/utils/openLink.js @@ -39,16 +39,16 @@ const openLink = async(url, theme = 'light') => { try { const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY); - if (browser) { - const schemeUrl = appSchemeURL(url, browser.replace(':', '')); - await Linking.openURL(schemeUrl); - } else { + if (browser === 'inApp') { await WebBrowser.openBrowserAsync(url, { toolbarColor: themes[theme].headerBackground, controlsColor: themes[theme].headerTintColor, collapseToolbar: true, showTitle: true }); + } else { + const schemeUrl = appSchemeURL(url, browser.replace(':', '')); + await Linking.openURL(schemeUrl); } } catch { try { diff --git a/app/views/DefaultBrowserView.js b/app/views/DefaultBrowserView.js index 119ce9fa8..568802360 100644 --- a/app/views/DefaultBrowserView.js +++ b/app/views/DefaultBrowserView.js @@ -83,7 +83,7 @@ class DefaultBrowserView extends React.Component { isSelected = (value) => { const { browser } = this.state; - if (!browser && value === 'inApp') { + if (!browser && value === 'systemDefault:') { return true; } return browser === value; @@ -92,7 +92,7 @@ class DefaultBrowserView extends React.Component { changeDefaultBrowser = async(newBrowser) => { logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser }); try { - const browser = newBrowser !== 'inApp' ? newBrowser : null; + const browser = newBrowser !== 'systemDefault:' ? newBrowser : null; await UserPreferences.setStringAsync(DEFAULT_BROWSER_KEY, browser); this.setState({ browser }); } catch { diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index d16ea5768..914a42913 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -50,6 +50,8 @@ class RoomActionsView extends React.Component { route: PropTypes.object, leaveRoom: PropTypes.func, jitsiEnabled: PropTypes.bool, + jitsiEnableTeams: PropTypes.bool, + jitsiEnableChannels: PropTypes.bool, encryptionEnabled: PropTypes.bool, setLoadingInvite: PropTypes.func, closeRoom: PropTypes.func, @@ -65,7 +67,8 @@ class RoomActionsView extends React.Component { viewBroadcastMemberListPermission: PropTypes.array, transferLivechatGuestPermission: PropTypes.array, createTeamPermission: PropTypes.array, - addTeamChannelPermission: PropTypes.array + addTeamChannelPermission: PropTypes.array, + convertTeamPermission: PropTypes.array } constructor(props) { @@ -89,7 +92,8 @@ class RoomActionsView extends React.Component { canEdit: false, canToggleEncryption: false, canCreateTeam: false, - canAddChannelToTeam: false + canAddChannelToTeam: false, + canConvertTeam: false }; if (room && room.observe && room.rid) { this.roomObservable = room.observe(); @@ -140,9 +144,10 @@ class RoomActionsView extends React.Component { const canViewMembers = await this.canViewMembers(); const canCreateTeam = await this.canCreateTeam(); const canAddChannelToTeam = await this.canAddChannelToTeam(); + const canConvertTeam = await this.canConvertTeam(); this.setState({ - canAutoTranslate, canAddUser, canInviteUser, canEdit, canToggleEncryption, canViewMembers, canCreateTeam, canAddChannelToTeam + canAutoTranslate, canAddUser, canInviteUser, canEdit, canToggleEncryption, canViewMembers, canCreateTeam, canAddChannelToTeam, canConvertTeam }); // livechat permissions @@ -238,6 +243,16 @@ class RoomActionsView extends React.Component { return canAddChannelToTeam; } + canConvertTeam = async() => { + const { room } = this.state; + const { convertTeamPermission } = this.props; + const { rid } = room; + const permissions = await RocketChat.hasPermission([convertTeamPermission], rid); + + const canConvertTeam = permissions[0]; + return canConvertTeam; + } + canToggleEncryption = async() => { const { room } = this.state; const { toggleRoomE2EEncryptionPermission } = this.props; @@ -431,6 +446,59 @@ class RoomActionsView extends React.Component { }); } + convertTeamToChannel = async() => { + const { room } = this.state; + const { navigation } = this.props; + + try { + const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id }); + + if (result.rooms?.length) { + const teamChannels = result.rooms.map(r => ({ + rid: r._id, + name: r.name, + teamId: r.teamId + })); + navigation.navigate('SelectListView', { + title: 'Converting_Team_To_Channel', + data: teamChannels, + infoText: 'Select_Team_Channels_To_Delete', + nextAction: data => this.convertTeamToChannelConfirmation(data) + }); + } else { + this.convertTeamToChannelConfirmation(); + } + } catch (e) { + this.convertTeamToChannelConfirmation(); + } + } + + handleConvertTeamToChannel = async(selected) => { + logEvent(events.RA_CONVERT_TEAM_TO_CHANNEL); + try { + const { room } = this.state; + const { navigation } = this.props; + + const result = await RocketChat.convertTeamToChannel({ teamId: room.teamId, selected }); + + if (result.success) { + navigation.navigate('RoomView'); + } + } catch (e) { + logEvent(events.RA_CONVERT_TEAM_TO_CHANNEL_F); + log(e); + } + } + + convertTeamToChannelConfirmation = (selected = []) => { + showConfirmationAlert({ + title: I18n.t('Confirmation'), + message: I18n.t('You_are_converting_the_team'), + confirmationText: I18n.t('Convert'), + onPress: () => this.handleConvertTeamToChannel(selected) + }); + } + leaveTeam = async() => { const { room } = this.state; const { navigation, leaveRoom } = this.props; @@ -655,10 +723,15 @@ class RoomActionsView extends React.Component { renderJitsi = () => { const { room } = this.state; - const { jitsiEnabled } = this.props; - if (!jitsiEnabled || room.teamMain) { + const { jitsiEnabled, jitsiEnableTeams, jitsiEnableChannels } = this.props; + + const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams; + const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels; + + if (!jitsiEnabled || isJitsiDisabledForTeams || isJitsiDisabledForChannels) { return null; } + return ( @@ -799,6 +872,32 @@ class RoomActionsView extends React.Component { ); } + teamToChannelActions = (t, room) => { + const { canEdit, canConvertTeam } = this.state; + const canConvertTeamToChannel = canEdit && canConvertTeam && !!room?.teamMain; + + return ( + <> + {['c', 'p'].includes(t) && canConvertTeamToChannel + ? ( + <> + this.onPressTouchable({ + event: this.convertTeamToChannel + })} + testID='room-actions-convert-channel-to-team' + left={() => } + showActionIndicator + /> + + + ) + : null} + + ); + } + render() { const { room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue @@ -1000,7 +1099,9 @@ class RoomActionsView extends React.Component { ) : null} + { this.teamChannelActions(t, room) } + {this.teamToChannelActions(t, room)} {['l'].includes(t) && !this.isOmnichannelPreview ? ( @@ -1078,6 +1179,8 @@ class RoomActionsView extends React.Component { const mapStateToProps = state => ({ jitsiEnabled: state.settings.Jitsi_Enabled || false, + jitsiEnableTeams: state.settings.Jitsi_Enable_Teams || false, + jitsiEnableChannels: state.settings.Jitsi_Enable_Channels || false, encryptionEnabled: state.encryption.enabled, serverVersion: state.server.version, isMasterDetail: state.app.isMasterDetail, @@ -1090,7 +1193,8 @@ const mapStateToProps = state => ({ viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'], transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'], createTeamPermission: state.permissions['create-team'], - addTeamChannelPermission: state.permissions['add-team-channel'] + addTeamChannelPermission: state.permissions['add-team-channel'], + convertTeamPermission: state.permissions['convert-team'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.js index d5dd7f0c5..28666b2ca 100644 --- a/app/views/RoomInfoEditView/index.js +++ b/app/views/RoomInfoEditView/index.js @@ -292,34 +292,11 @@ class RoomInfoEditView extends React.Component { }, 100); } - handleDeleteTeam = async(selected) => { - logEvent(events.RI_EDIT_DELETE_TEAM); - const { navigation, isMasterDetail } = this.props; - const { room } = this.state; - try { - const result = await RocketChat.deleteTeam({ teamId: room.teamId, ...(selected && { roomsToRemove: selected }) }); - if (result.success) { - if (isMasterDetail) { - navigation.navigate('DrawerNavigator'); - } else { - navigation.navigate('RoomsListView'); - } - } - } catch (e) { - logEvent(events.RI_EDIT_DELETE_TEAM_F); - log(e); - showErrorAlert( - e.data.error - ? I18n.t(e.data.error) - : I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }), - I18n.t('Cannot_delete') - ); - } - } - deleteTeam = async() => { const { room } = this.state; - const { navigation } = this.props; + const { + navigation, deleteCPermission, deletePPermission, deleteRoom + } = this.props; try { const db = database.active; @@ -329,16 +306,27 @@ class RoomInfoEditView extends React.Component { Q.where('team_main', Q.notEq(true)) ); - if (teamChannels.length) { + const teamChannelOwner = []; + for (let i = 0; i < teamChannels.length; i += 1) { + const permissionType = teamChannels[i].t === 'c' ? deleteCPermission : deletePPermission; + // eslint-disable-next-line no-await-in-loop + const permissions = await RocketChat.hasPermission([ + permissionType + ], teamChannels[i].rid); + + if (permissions[0]) { teamChannelOwner.push(teamChannels[i]); } + } + + if (teamChannelOwner.length) { navigation.navigate('SelectListView', { title: 'Delete_Team', - data: teamChannels, + data: teamChannelOwner, infoText: 'Select_channels_to_delete', nextAction: (selected) => { showConfirmationAlert({ message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), - onPress: () => this.handleDeleteTeam(selected) + onPress: () => deleteRoom('team', room, selected) }); } }); @@ -346,7 +334,7 @@ class RoomInfoEditView extends React.Component { showConfirmationAlert({ message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), - onPress: () => this.handleDeleteTeam() + onPress: () => deleteRoom('team', room) }); } } catch (e) { @@ -375,7 +363,7 @@ class RoomInfoEditView extends React.Component { { text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), style: 'destructive', - onPress: () => deleteRoom(room.rid, room.t) + onPress: () => deleteRoom('channel', room) } ], { cancelable: false } @@ -767,7 +755,7 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ - deleteRoom: (rid, t) => dispatch(deleteRoomAction(rid, t)) + deleteRoom: (roomType, room, selected) => dispatch(deleteRoomAction(roomType, room, selected)) }); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RoomInfoEditView)); diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 182651b9b..02454cd53 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -6,7 +6,6 @@ import { connect } from 'react-redux'; import UAParser from 'ua-parser-js'; import isEmpty from 'lodash/isEmpty'; -import database from '../../lib/database'; import { CustomIcon } from '../../lib/Icons'; import Status from '../../containers/Status'; import Avatar from '../../containers/Avatar'; @@ -55,7 +54,8 @@ class RoomInfoView extends React.Component { theme: PropTypes.string, isMasterDetail: PropTypes.bool, jitsiEnabled: PropTypes.bool, - editRoomPermission: PropTypes.array + editRoomPermission: PropTypes.array, + roles: PropTypes.array } constructor(props) { @@ -133,18 +133,9 @@ class RoomInfoView extends React.Component { return room.t === 'l'; } - getRoleDescription = async(id) => { - const db = database.active; - try { - const rolesCollection = db.get('roles'); - const role = await rolesCollection.find(id); - if (role) { - return role.description; - } - return null; - } catch (e) { - return null; - } + getRoleDescription = (id) => { + const { roles } = this.props; + return roles[id]; }; loadVisitor = async() => { @@ -378,7 +369,8 @@ const mapStateToProps = state => ({ rooms: state.room.rooms, isMasterDetail: state.app.isMasterDetail, jitsiEnabled: state.settings.Jitsi_Enabled || false, - editRoomPermission: state.permissions['edit-room'] + editRoomPermission: state.permissions['edit-room'], + roles: state.roles }); export default connect(mapStateToProps)(withTheme(RoomInfoView)); diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index 5f0deb326..aaa0c98f9 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -49,7 +49,8 @@ class RoomMembersView extends React.Component { room: PropTypes.object, user: PropTypes.shape({ id: PropTypes.string, - token: PropTypes.string + token: PropTypes.string, + roles: PropTypes.array }), showActionSheet: PropTypes.func, theme: PropTypes.string, diff --git a/app/views/RoomView/List/index.js b/app/views/RoomView/List/index.js index 19a8ccb90..cbf02879b 100644 --- a/app/views/RoomView/List/index.js +++ b/app/views/RoomView/List/index.js @@ -269,7 +269,7 @@ class ListContainer extends React.Component { const { listRef } = this.props; const index = messages.findIndex(item => item.id === messageId); if (index > -1) { - listRef.current.getNode().scrollToIndex({ index, viewPosition: 0.5 }); + listRef.current.getNode().scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 }); await new Promise(res => setTimeout(res, 300)); if (!this.viewableItems.map(vi => vi.key).includes(messageId)) { if (!this.jumping) { diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 19df2c3b0..214f213e4 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -71,7 +71,6 @@ const DISCUSSIONS_HEADER = 'Discussions'; const TEAMS_HEADER = 'Teams'; const CHANNELS_HEADER = 'Channels'; const DM_HEADER = 'Direct_Messages'; -const GROUPS_HEADER = 'Private_Groups'; const OMNICHANNEL_HEADER = 'Open_Livechats'; const QUERY_SIZE = 20; @@ -480,13 +479,11 @@ class RoomsListView extends React.Component { if (groupByType) { const teams = chats.filter(s => filterIsTeam(s)); const discussions = chats.filter(s => filterIsDiscussion(s)); - const channels = chats.filter(s => s.t === 'c' && !filterIsDiscussion(s) && !filterIsTeam(s)); - const privateGroup = chats.filter(s => s.t === 'p' && !filterIsDiscussion(s) && !filterIsTeam(s)); + const channels = chats.filter(s => (s.t === 'c' || s.t === 'p') && !filterIsDiscussion(s) && !filterIsTeam(s)); const direct = chats.filter(s => s.t === 'd' && !filterIsDiscussion(s) && !filterIsTeam(s)); tempChats = this.addRoomsGroup(teams, TEAMS_HEADER, tempChats); tempChats = this.addRoomsGroup(discussions, DISCUSSIONS_HEADER, tempChats); tempChats = this.addRoomsGroup(channels, CHANNELS_HEADER, tempChats); - tempChats = this.addRoomsGroup(privateGroup, GROUPS_HEADER, tempChats); tempChats = this.addRoomsGroup(direct, DM_HEADER, tempChats); } else if (showUnread || showFavorites || isOmnichannelAgent) { tempChats = this.addRoomsGroup(chats, CHATS_HEADER, tempChats); diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js index 55687cf0f..5e696144b 100644 --- a/app/views/SettingsView/index.js +++ b/app/views/SettingsView/index.js @@ -65,7 +65,7 @@ class SettingsView extends React.Component { const usersCollection = db.get('users'); try { const userRecord = await usersCollection.find(user.id); - if (!userRecord.loginEmailPassword) { + if (userRecord.isFromWebView) { showConfirmationAlert({ title: I18n.t('Clear_cookies_alert'), message: I18n.t('Clear_cookies_desc'), diff --git a/app/views/ThemeView.js b/app/views/ThemeView.js index e62d7ca58..1152a920c 100644 --- a/app/views/ThemeView.js +++ b/app/views/ThemeView.js @@ -30,14 +30,14 @@ const THEMES = [ label: 'Dark', value: 'dark', group: THEME_GROUP - }, { - label: 'Dark', - value: 'dark', - group: DARK_GROUP }, { label: 'Black', value: 'black', group: DARK_GROUP + }, { + label: 'Dark', + value: 'dark', + group: DARK_GROUP } ]; diff --git a/e2e/data.js b/e2e/data.js index 98257f71d..4dc093cc6 100644 --- a/e2e/data.js +++ b/e2e/data.js @@ -1,75 +1,76 @@ const random = require('./helpers/random'); + const value = random(20); const data = { - server: 'https://mobile.rocket.chat', - adminUser: 'e2e_admin', - adminPassword: 'p7mFh4yLwCRXSnMvG', - alternateServer: 'https://stable.rocket.chat', - users: { - regular: { - username: `userone${ value }`, - password: '123', - email: `mobile+regular${ value }@rocket.chat` - }, - alternate: { - username: `usertwo${ value }`, - password: '123', - email: `mobile+alternate${ value }@rocket.chat`, - totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' - }, - profileChanges: { - username: `userthree${ value }`, - password: '123', - email: `mobile+profileChanges${ value }@rocket.chat` - }, - existing: { - username: `existinguser${ value }`, - password: '123', - email: `mobile+existing${ value }@rocket.chat` - } - }, - channels: { - detoxpublic: { - name: 'detox-public' - }, - detoxpublicprotected: { - name: 'detox-public-protected', - joinCode: '123' - } - }, - groups: { - private: { - name: `detox-private-${ value }` - }, - alternate: { - name: `detox-alternate-${ value }` - } - }, - teams: { - private: { - name: `detox-team-${ value }` - } - }, - registeringUser: { - username: `newuser${ value }`, - password: `password${ value }`, - email: `mobile+registering${ value }@rocket.chat` - }, - registeringUser2: { - username: `newusertwo${ value }`, - password: `passwordtwo${ value }`, - email: `mobile+registeringtwo${ value }@rocket.chat` - }, - registeringUser3: { - username: `newuserthree${ value }`, - password: `passwordthree${ value }`, - email: `mobile+registeringthree${ value }@rocket.chat` - }, - registeringUser4: { - username: `newuserfour${ value }`, - password: `passwordfour${ value }`, - email: `mobile+registeringfour${ value }@rocket.chat` - }, - random: value -} + server: 'https://mobile.rocket.chat', + adminUser: 'e2e_admin', + adminPassword: 'p7mFh4yLwCRXSnMvG', + alternateServer: 'https://stable.rocket.chat', + users: { + regular: { + username: `userone${ value }`, + password: '123', + email: `mobile+regular${ value }@rocket.chat` + }, + alternate: { + username: `usertwo${ value }`, + password: '123', + email: `mobile+alternate${ value }@rocket.chat`, + totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' + }, + profileChanges: { + username: `userthree${ value }`, + password: '123', + email: `mobile+profileChanges${ value }@rocket.chat` + }, + existing: { + username: `existinguser${ value }`, + password: '123', + email: `mobile+existing${ value }@rocket.chat` + } + }, + channels: { + detoxpublic: { + name: 'detox-public' + }, + detoxpublicprotected: { + name: 'detox-public-protected', + joinCode: '123' + } + }, + groups: { + private: { + name: `detox-private-${ value }` + }, + alternate: { + name: `detox-alternate-${ value }` + } + }, + teams: { + private: { + name: `detox-team-${ value }` + } + }, + registeringUser: { + username: `newuser${ value }`, + password: `password${ value }`, + email: `mobile+registering${ value }@rocket.chat` + }, + registeringUser2: { + username: `newusertwo${ value }`, + password: `passwordtwo${ value }`, + email: `mobile+registeringtwo${ value }@rocket.chat` + }, + registeringUser3: { + username: `newuserthree${ value }`, + password: `passwordthree${ value }`, + email: `mobile+registeringthree${ value }@rocket.chat` + }, + registeringUser4: { + username: `newuserfour${ value }`, + password: `passwordfour${ value }`, + email: `mobile+registeringfour${ value }@rocket.chat` + }, + random: value +}; module.exports = data; diff --git a/e2e/data/data.cloud.js b/e2e/data/data.cloud.js index c69b72515..df98fdd7a 100644 --- a/e2e/data/data.cloud.js +++ b/e2e/data/data.cloud.js @@ -1,72 +1,74 @@ +// eslint-disable-next-line import/no-unresolved const random = require('./helpers/random'); + const value = random(20); const data = { - server: 'https://mobile.rocket.chat', - adminUser: 'e2e_admin', - adminPassword: 'p7mFh4yLwCRXSnMvG', - alternateServer: 'https://stable.rocket.chat', - users: { - regular: { - username: `userone${ value }`, - password: '123', - email: `mobile+regular${ value }@rocket.chat` - }, - alternate: { - username: `usertwo${ value }`, - password: '123', - email: `mobile+alternate${ value }@rocket.chat`, - totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' - }, - profileChanges: { - username: `userthree${ value }`, - password: '123', - email: `mobile+profileChanges${ value }@rocket.chat` - }, - existing: { - username: `existinguser${ value }`, - password: '123', - email: `mobile+existing${ value }@rocket.chat` - } - }, - channels: { - detoxpublic: { - name: 'detox-public' - }, - detoxpublicprotected: { - name: 'detox-public-protected', - joinCode: '123' - } - }, - groups: { - private: { - name: `detox-private-${ value }` - } - }, - teams: { - private: { - name: `detox-team-${ value }` - } - }, - registeringUser: { - username: `newuser${ value }`, - password: `password${ value }`, - email: `mobile+registering${ value }@rocket.chat` - }, - registeringUser2: { - username: `newusertwo${ value }`, - password: `passwordtwo${ value }`, - email: `mobile+registeringtwo${ value }@rocket.chat` - }, - registeringUser3: { - username: `newuserthree${ value }`, - password: `passwordthree${ value }`, - email: `mobile+registeringthree${ value }@rocket.chat` - }, - registeringUser4: { - username: `newuserfour${ value }`, - password: `passwordfour${ value }`, - email: `mobile+registeringfour${ value }@rocket.chat` - }, - random: value -} + server: 'https://mobile.rocket.chat', + adminUser: 'e2e_admin', + adminPassword: 'p7mFh4yLwCRXSnMvG', + alternateServer: 'https://stable.rocket.chat', + users: { + regular: { + username: `userone${ value }`, + password: '123', + email: `mobile+regular${ value }@rocket.chat` + }, + alternate: { + username: `usertwo${ value }`, + password: '123', + email: `mobile+alternate${ value }@rocket.chat`, + totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' + }, + profileChanges: { + username: `userthree${ value }`, + password: '123', + email: `mobile+profileChanges${ value }@rocket.chat` + }, + existing: { + username: `existinguser${ value }`, + password: '123', + email: `mobile+existing${ value }@rocket.chat` + } + }, + channels: { + detoxpublic: { + name: 'detox-public' + }, + detoxpublicprotected: { + name: 'detox-public-protected', + joinCode: '123' + } + }, + groups: { + private: { + name: `detox-private-${ value }` + } + }, + teams: { + private: { + name: `detox-team-${ value }` + } + }, + registeringUser: { + username: `newuser${ value }`, + password: `password${ value }`, + email: `mobile+registering${ value }@rocket.chat` + }, + registeringUser2: { + username: `newusertwo${ value }`, + password: `passwordtwo${ value }`, + email: `mobile+registeringtwo${ value }@rocket.chat` + }, + registeringUser3: { + username: `newuserthree${ value }`, + password: `passwordthree${ value }`, + email: `mobile+registeringthree${ value }@rocket.chat` + }, + registeringUser4: { + username: `newuserfour${ value }`, + password: `passwordfour${ value }`, + email: `mobile+registeringfour${ value }@rocket.chat` + }, + random: value +}; module.exports = data; diff --git a/e2e/data/data.docker.js b/e2e/data/data.docker.js index 31fb5c8e2..6c3ce1925 100644 --- a/e2e/data/data.docker.js +++ b/e2e/data/data.docker.js @@ -1,75 +1,77 @@ +// eslint-disable-next-line import/no-unresolved const random = require('./helpers/random'); + const value = random(20); const data = { - server: 'http://localhost:3000', - adminUser: 'admin', - adminPassword: 'password', - alternateServer: 'https://stable.rocket.chat', - users: { - regular: { - username: `userone${ value }`, - password: '123', - email: `mobile+regular${ value }@rocket.chat` - }, - alternate: { - username: `usertwo${ value }`, - password: '123', - email: `mobile+alternate${ value }@rocket.chat`, - totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' - }, - profileChanges: { - username: `userthree${ value }`, - password: '123', - email: `mobile+profileChanges${ value }@rocket.chat` - }, - existing: { - username: `existinguser${ value }`, - password: '123', - email: `mobile+existing${ value }@rocket.chat` - } - }, - channels: { - detoxpublic: { - name: 'detox-public' - }, - detoxpublicprotected: { - name: 'detox-public-protected', - joinCode: '123' - } - }, - groups: { - private: { - name: `detox-private-${ value }` - }, - alternate: { - name: `detox-alternate-${ value }` - } - }, - teams: { - private: { - name: `detox-team-${ value }` - } - }, - registeringUser: { - username: `newuser${ value }`, - password: `password${ value }`, - email: `mobile+registering${ value }@rocket.chat` - }, - registeringUser2: { - username: `newusertwo${ value }`, - password: `passwordtwo${ value }`, - email: `mobile+registeringtwo${ value }@rocket.chat` - }, - registeringUser3: { - username: `newuserthree${ value }`, - password: `passwordthree${ value }`, - email: `mobile+registeringthree${ value }@rocket.chat` - }, - registeringUser4: { - username: `newuserfour${ value }`, - password: `passwordfour${ value }`, - email: `mobile+registeringfour${ value }@rocket.chat` - }, - random: value -} + server: 'http://localhost:3000', + adminUser: 'admin', + adminPassword: 'password', + alternateServer: 'https://stable.rocket.chat', + users: { + regular: { + username: `userone${ value }`, + password: '123', + email: `mobile+regular${ value }@rocket.chat` + }, + alternate: { + username: `usertwo${ value }`, + password: '123', + email: `mobile+alternate${ value }@rocket.chat`, + totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' + }, + profileChanges: { + username: `userthree${ value }`, + password: '123', + email: `mobile+profileChanges${ value }@rocket.chat` + }, + existing: { + username: `existinguser${ value }`, + password: '123', + email: `mobile+existing${ value }@rocket.chat` + } + }, + channels: { + detoxpublic: { + name: 'detox-public' + }, + detoxpublicprotected: { + name: 'detox-public-protected', + joinCode: '123' + } + }, + groups: { + private: { + name: `detox-private-${ value }` + }, + alternate: { + name: `detox-alternate-${ value }` + } + }, + teams: { + private: { + name: `detox-team-${ value }` + } + }, + registeringUser: { + username: `newuser${ value }`, + password: `password${ value }`, + email: `mobile+registering${ value }@rocket.chat` + }, + registeringUser2: { + username: `newusertwo${ value }`, + password: `passwordtwo${ value }`, + email: `mobile+registeringtwo${ value }@rocket.chat` + }, + registeringUser3: { + username: `newuserthree${ value }`, + password: `passwordthree${ value }`, + email: `mobile+registeringthree${ value }@rocket.chat` + }, + registeringUser4: { + username: `newuserfour${ value }`, + password: `passwordfour${ value }`, + email: `mobile+registeringfour${ value }@rocket.chat` + }, + random: value +}; module.exports = data; diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js index d5044003d..4a6b8bc66 100644 --- a/e2e/helpers/app.js +++ b/e2e/helpers/app.js @@ -5,57 +5,57 @@ const data = require('../data'); const platformTypes = require('./platformTypes'); async function navigateToWorkspace(server = data.server) { - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(10000); await element(by.id('join-workspace')).tap(); await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); - await element(by.id('new-server-view-input')).typeText(`${server}\n`); + await element(by.id('new-server-view-input')).typeText(`${ server }\n`); await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); await expect(element(by.id('workspace-view'))).toBeVisible(); } async function navigateToLogin(server) { - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); - await navigateToWorkspace(server); + await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); + await navigateToWorkspace(server); await element(by.id('workspace-view-login')).tap(); - await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(4000); - await expect(element(by.id('login-view'))).toBeVisible(); + await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); + await expect(element(by.id('login-view'))).toBeVisible(); } async function navigateToRegister(server) { - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); - await navigateToWorkspace(server); + await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); + await navigateToWorkspace(server); await element(by.id('workspace-view-register')).tap(); - await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); } async function login(username, password) { - await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); - await element(by.id('login-view-email')).replaceText(username); - await element(by.id('login-view-password')).replaceText(password); - await element(by.id('login-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(30000); + await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); + await element(by.id('login-view-email')).replaceText(username); + await element(by.id('login-view-password')).replaceText(password); + await element(by.id('login-view-submit')).tap(); + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(30000); } async function logout() { - const deviceType = device.getPlatform(); + const deviceType = device.getPlatform(); const { scrollViewType } = platformTypes[deviceType]; - await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); + await element(by.id('rooms-list-view-sidebar')).tap(); + await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000); - await element(by.id('sidebar-settings')).tap(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); - await element(by.type(scrollViewType)).atIndex(1).scrollTo('bottom'); - await element(by.id('settings-logout')).tap(); - const logoutAlertMessage = 'You will be logged out of this application.'; - await waitFor(element(by.text(logoutAlertMessage)).atIndex(0)).toExist().withTimeout(10000); - await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist(); - await element(by.text('Logout')).tap(); - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(10000); - await expect(element(by.id('onboarding-view'))).toBeVisible(); + await element(by.id('sidebar-settings')).tap(); + await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await element(by.type(scrollViewType)).atIndex(1).scrollTo('bottom'); + await element(by.id('settings-logout')).tap(); + const logoutAlertMessage = 'You will be logged out of this application.'; + await waitFor(element(by.text(logoutAlertMessage)).atIndex(0)).toExist().withTimeout(10000); + await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist(); + await element(by.text('Logout')).tap(); + await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(10000); + await expect(element(by.id('onboarding-view'))).toBeVisible(); } async function mockMessage(message, isThread = false) { - let input = isThread ? 'messagebox-input-thread' : 'messagebox-input'; + const input = isThread ? 'messagebox-input-thread' : 'messagebox-input'; await element(by.id(input)).tap(); await element(by.id(input)).typeText(`${ data.random }${ message }`); await element(by.id('messagebox-send-message')).tap(); @@ -104,35 +104,35 @@ async function mockMessageWithNag(message, isThread = false) { }; async function tapBack() { - await element(by.id('header-back')).atIndex(0).tap(); + await element(by.id('header-back')).atIndex(0).tap(); } -async function sleep(ms) { - return new Promise(res => setTimeout(res, ms)); +function sleep(ms) { + return new Promise(res => setTimeout(res, ms)); } async function searchRoom(room) { - await element(by.id('rooms-list-view-search')).tap(); + await element(by.id('rooms-list-view-search')).tap(); await expect(element(by.id('rooms-list-view-search-input'))).toExist(); await waitFor(element(by.id('rooms-list-view-search-input'))).toExist().withTimeout(5000); - await element(by.id('rooms-list-view-search-input')).typeText(room); - await sleep(300); + await element(by.id('rooms-list-view-search-input')).typeText(room); + await sleep(300); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(60000); } -async function tryTapping(theElement, timeout, longtap = false){ +async function tryTapping(theElement, timeout, longtap = false) { try { - if(longtap){ - await theElement.longPress() - } else { - await theElement.tap() - } - } catch(e) { - if(timeout <= 0){ //TODO: Maths. How closely has the timeout been honoured here? - throw e + if (longtap) { + await theElement.longPress(); + } else { + await theElement.tap(); } - await sleep(100) - await tryTapping(theElement, timeout - 100) + } catch (e) { + if (timeout <= 0) { // TODO: Maths. How closely has the timeout been honoured here? + throw e; + } + await sleep(100); + await tryTapping(theElement, timeout - 100); } } @@ -142,7 +142,7 @@ const checkServer = async(server) => { await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.label(label))).toBeVisible().withTimeout(10000); await element(by.id('sidebar-close-drawer')).tap(); -} +}; async function closeKeyboard() { if(device.getPlatform() === 'android') @@ -166,4 +166,4 @@ module.exports = { checkServer, closeKeyboard, mockMessageWithNag -}; \ No newline at end of file +}; diff --git a/e2e/helpers/data_setup.js b/e2e/helpers/data_setup.js index 7e744f54a..fd02a20c5 100644 --- a/e2e/helpers/data_setup.js +++ b/e2e/helpers/data_setup.js @@ -6,176 +6,174 @@ const TEAM_TYPE = { PRIVATE: 1 }; -let server = data.server +const { server } = data; const rocketchat = axios.create({ - baseURL: `${server}/api/v1/`, - headers: { - 'Content-Type': 'application/json;charset=UTF-8', - } -}) + baseURL: `${ server }/api/v1/`, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } +}); -const login = async (username, password) => { - console.log(`Logging in as user ${username}`) - const response = await rocketchat.post('login', { - "user": username, - "password": password - }) - const userId = response.data.data.userId - const authToken = response.data.data.authToken - rocketchat.defaults.headers.common['X-User-Id'] = userId - rocketchat.defaults.headers.common['X-Auth-Token'] = authToken - return { authToken, userId }; -} +const login = async(username, password) => { + console.log(`Logging in as user ${ username }`); + const response = await rocketchat.post('login', { + user: username, + password + }); + const { userId } = response.data.data; + const { authToken } = response.data.data; + rocketchat.defaults.headers.common['X-User-Id'] = userId; + rocketchat.defaults.headers.common['X-Auth-Token'] = authToken; + return { authToken, userId }; +}; -const createUser = async (username, password, name, email) => { - console.log(`Creating user ${username}`) - try { - await rocketchat.post('users.create', { - "username": username, - "password": password, - "name": name, - "email": email - }) - } catch (error) { - console.log(JSON.stringify(error)) - throw "Failed to create user" - } -} +const createUser = async(username, password, name, email) => { + console.log(`Creating user ${ username }`); + try { + await rocketchat.post('users.create', { + username, + password, + name, + email + }); + } catch (error) { + console.log(JSON.stringify(error)); + throw new Error('Failed to create user'); + } +}; -const createChannelIfNotExists = async (channelname) => { - console.log(`Creating public channel ${channelname}`) - try { - const room = await rocketchat.post('channels.create', { - "name": channelname - }) - return room - } catch (createError) { - try { //Maybe it exists already? - const room = rocketchat.get(`channels.info?roomName=${channelname}`) - return room - } catch (infoError) { - console.log(JSON.stringify(createError)) - console.log(JSON.stringify(infoError)) - throw "Failed to find or create public channel" - } - } -} +const createChannelIfNotExists = async(channelname) => { + console.log(`Creating public channel ${ channelname }`); + try { + const room = await rocketchat.post('channels.create', { + name: channelname + }); + return room; + } catch (createError) { + try { // Maybe it exists already? + const room = rocketchat.get(`channels.info?roomName=${ channelname }`); + return room; + } catch (infoError) { + console.log(JSON.stringify(createError)); + console.log(JSON.stringify(infoError)); + throw new Error('Failed to find or create public channel'); + } + } +}; -const createTeamIfNotExists = async (teamname) => { - console.log(`Creating private team ${teamname}`) - try { - await rocketchat.post('teams.create', { - "name": teamname, - "type": TEAM_TYPE.PRIVATE - }) - } catch (createError) { - try { //Maybe it exists already? - await rocketchat.get(`teams.info?teamName=${teamname}`) - } catch (infoError) { - console.log(JSON.stringify(createError)) - console.log(JSON.stringify(infoError)) - throw "Failed to find or create private team" - } - } -} +const createTeamIfNotExists = async(teamname) => { + console.log(`Creating private team ${ teamname }`); + try { + await rocketchat.post('teams.create', { + name: teamname, + type: TEAM_TYPE.PRIVATE + }); + } catch (createError) { + try { // Maybe it exists already? + await rocketchat.get(`teams.info?teamName=${ teamname }`); + } catch (infoError) { + console.log(JSON.stringify(createError)); + console.log(JSON.stringify(infoError)); + throw new Error('Failed to find or create private team'); + } + } +}; -const createGroupIfNotExists = async (groupname) => { - console.log(`Creating private group ${groupname}`) - try { - await rocketchat.post('groups.create', { - "name": groupname - }) - } catch (createError) { - try { //Maybe it exists already? - await rocketchat.get(`groups.info?roomName=${groupname}`) - } catch (infoError) { - console.log(JSON.stringify(createError)) - console.log(JSON.stringify(infoError)) - throw "Failed to find or create private group" - } - } -} +const createGroupIfNotExists = async(groupname) => { + console.log(`Creating private group ${ groupname }`); + try { + await rocketchat.post('groups.create', { + name: groupname + }); + } catch (createError) { + try { // Maybe it exists already? + await rocketchat.get(`groups.info?roomName=${ groupname }`); + } catch (infoError) { + console.log(JSON.stringify(createError)); + console.log(JSON.stringify(infoError)); + throw new Error('Failed to find or create private group'); + } + } +}; -const changeChannelJoinCode = async (roomId, joinCode) => { - console.log(`Changing channel Join Code ${roomId}`) - try { - await rocketchat.post('method.call/saveRoomSettings', { - message: JSON.stringify({ - method: 'saveRoomSettings', - params: [ - roomId, - { joinCode } - ] - }) - }) - } catch (createError) { - console.log(JSON.stringify(createError)) - throw "Failed to create protected channel" - } -} +const changeChannelJoinCode = async(roomId, joinCode) => { + console.log(`Changing channel Join Code ${ roomId }`); + try { + await rocketchat.post('method.call/saveRoomSettings', { + message: JSON.stringify({ + method: 'saveRoomSettings', + params: [ + roomId, + { joinCode } + ] + }) + }); + } catch (createError) { + console.log(JSON.stringify(createError)); + throw new Error('Failed to create protected channel'); + } +}; -const sendMessage = async (user, channel, msg) => { - console.log(`Sending message to ${channel}`) - try { - await login(user.username, user.password); - await rocketchat.post('chat.postMessage', { channel, msg }); - } catch (infoError) { - console.log(JSON.stringify(infoError)) - throw "Failed to find or create private group" - } -} +const sendMessage = async(user, channel, msg) => { + console.log(`Sending message to ${ channel }`); + try { + await login(user.username, user.password); + await rocketchat.post('chat.postMessage', { channel, msg }); + } catch (infoError) { + console.log(JSON.stringify(infoError)); + throw new Error('Failed to find or create private group'); + } +}; -const setup = async () => { - await login(data.adminUser, data.adminPassword) - - for (var userKey in data.users) { - if (data.users.hasOwnProperty(userKey)) { - const user = data.users[userKey] - await createUser(user.username, user.password, user.username, user.email) - } - } +const setup = async() => { + await login(data.adminUser, data.adminPassword); - for (var channelKey in data.channels) { - if (data.channels.hasOwnProperty(channelKey)) { - const channel = data.channels[channelKey] - const { data: { channel: { _id } } } = await createChannelIfNotExists(channel.name) + for (const userKey in data.users) { + if (Object.prototype.hasOwnProperty.call(data.users, userKey)) { + const user = data.users[userKey]; + await createUser(user.username, user.password, user.username, user.email); + } + } - if (channel.joinCode) { - await changeChannelJoinCode(_id, channel.joinCode); - } - } - } + for (const channelKey in data.channels) { + if (Object.prototype.hasOwnProperty.call(data.channels, channelKey)) { + const channel = data.channels[channelKey]; + const { data: { channel: { _id } } } = await createChannelIfNotExists(channel.name); - await login(data.users.regular.username, data.users.regular.password) + if (channel.joinCode) { + await changeChannelJoinCode(_id, channel.joinCode); + } + } + } - for (var groupKey in data.groups) { - if (data.groups.hasOwnProperty(groupKey)) { - const group = data.groups[groupKey] - await createGroupIfNotExists(group.name) - } - } + await login(data.users.regular.username, data.users.regular.password); - for (var teamKey in data.teams) { - if (data.teams.hasOwnProperty(teamKey)) { - const team = data.teams[teamKey] - await createTeamIfNotExists(team.name) - } - } + for (const groupKey in data.groups) { + if (Object.prototype.hasOwnProperty.call(data.groups, groupKey)) { + const group = data.groups[groupKey]; + await createGroupIfNotExists(group.name); + } + } - return -} + for (const teamKey in data.teams) { + if (Object.prototype.hasOwnProperty.call(data.teams, teamKey)) { + const team = data.teams[teamKey]; + await createTeamIfNotExists(team.name); + } + } +}; const get = (endpoint) => { - console.log(`GET /${ endpoint }`) - return rocketchat.get(endpoint); -} + console.log(`GET /${ endpoint }`); + return rocketchat.get(endpoint); +}; const post = (endpoint, body) => { - console.log(`POST /${ endpoint } ${ JSON.stringify(body) }`) - return rocketchat.post(endpoint, body); -} + console.log(`POST /${ endpoint } ${ JSON.stringify(body) }`); + return rocketchat.post(endpoint, body); +}; module.exports = { - setup, sendMessage, get, post, login -} \ No newline at end of file + setup, sendMessage, get, post, login +}; diff --git a/e2e/helpers/random.js b/e2e/helpers/random.js index d26e832e3..d41575c07 100644 --- a/e2e/helpers/random.js +++ b/e2e/helpers/random.js @@ -6,4 +6,4 @@ function random(length) { } return text; } -module.exports = random; \ No newline at end of file +module.exports = random; diff --git a/e2e/tests/assorted/01-e2eencryption.spec.js b/e2e/tests/assorted/01-e2eencryption.spec.js index c5da2bbaf..ae8cb87b1 100644 --- a/e2e/tests/assorted/01-e2eencryption.spec.js +++ b/e2e/tests/assorted/01-e2eencryption.spec.js @@ -1,15 +1,14 @@ const { - expect, element, by, waitFor -} = require('detox'); -const { navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout } = require('../../helpers/app'); + navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout +} = require('../../helpers/app'); const platformTypes = require('../../helpers/platformTypes'); const data = require('../../data'); const { prepareAndroid } = require('../../helpers/platformFunctions'); -const testuser = data.users.regular -const otheruser = data.users.alternate +const testuser = data.users.regular; +const otheruser = data.users.alternate; const checkServer = async(server) => { const label = `Connected to ${ server }`; @@ -17,7 +16,7 @@ const checkServer = async(server) => { await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.label(label))).toBeVisible().withTimeout(60000); await element(by.id('sidebar-close-drawer')).tap(); -} +}; const checkBanner = async() => { await waitFor(element(by.id('listheader-encryption').withDescendant(by.text('Save Your Encryption Password')))).toBeVisible().withTimeout(10000); @@ -49,7 +48,7 @@ describe('E2E Encryption', () => { const newPassword = 'abc'; let alertButtonType, scrollViewType; - before(async () => { + before(async() => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await prepareAndroid(); ({ alertButtonType, scrollViewType } = platformTypes[device.getPlatform()]); @@ -57,30 +56,30 @@ describe('E2E Encryption', () => { await login(testuser.username, testuser.password); }); - describe('Banner', async() => { - describe('Render', async () => { - it('should have encryption badge', async () => { + describe('Banner', () => { + describe('Render', () => { + it('should have encryption badge', async() => { await checkBanner(); }); }); - - describe('Usage', async () => { + + describe('Usage', () => { it('should tap encryption badge and open save password modal', async() => { await element(by.id('listheader-encryption')).tap(); await waitFor(element(by.id('e2e-save-password-view'))).toBeVisible().withTimeout(2000); }); - + it('should tap "How it works" and navigate', async() => { await element(by.id('e2e-save-password-view-how-it-works').and(by.label('How It Works'))).tap(); await waitFor(element(by.id('e2e-how-it-works-view'))).toBeVisible().withTimeout(2000); await tapBack(); }); - + it('should tap "Save my password" and close modal', async() => { await element(by.id('e2e-save-password-view-saved-password').and(by.label('I Saved My E2E Password'))).tap(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); }); - + it('should create encrypted room', async() => { await element(by.id('rooms-list-view-create-channel')).tap(); await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); @@ -98,15 +97,15 @@ describe('E2E Encryption', () => { await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`room-view-title-${ room }`))).toBeVisible().withTimeout(60000); }); - + it('should send message and be able to read it', async() => { await mockMessage('message'); await tapBack(); }); }); - }) + }); - describe('Security and Privacy', async() => { + describe('Security and Privacy', () => { it('should navigate to security privacy', async() => { await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await element(by.id('rooms-list-view-sidebar')).tap(); @@ -126,7 +125,7 @@ describe('E2E Encryption', () => { }); }); - describe('E2E Encryption Security', async() => { + describe('E2E Encryption Security', () => { it('should navigate to e2e encryption security', async() => { await element(by.id('security-privacy-view-e2e-encryption')).tap(); await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000); @@ -139,9 +138,9 @@ describe('E2E Encryption', () => { await expect(element(by.id('e2e-encryption-security-view-change-password').and(by.label('Save Changes')))).toExist(); await expect(element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key')))).toExist(); }); - }) + }); - describe('Change password', async() => { + describe('Change password', () => { it('should change password', async() => { await element(by.id('e2e-encryption-security-view-password')).typeText(newPassword); await element(by.id('e2e-encryption-security-view-change-password')).tap(); @@ -190,7 +189,7 @@ describe('E2E Encryption', () => { }); }); - describe('Reset E2E key', async() => { + describe('Reset E2E key', () => { it('should reset e2e key', async() => { await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); @@ -200,10 +199,8 @@ describe('E2E Encryption', () => { await element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key'))).tap(); await waitFor(element(by.text('Are you sure?'))).toExist().withTimeout(2000); await expect(element(by.text('You\'re going to be logged out.'))).toExist(); - await element(by.text('Yes, reset it').and(by.type(alertButtonType))).tap(); - await sleep(2000) - await waitFor(element(by.text('OK').and(by.type(alertButtonType)))).toExist().withTimeout(2000); - await element(by.text('OK').and(by.type(alertButtonType))).tap(); + await element(by.label('Yes, reset it').and(by.type(alertButtonType))).tap(); + await sleep(2000); await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(10000); await element(by.id('workspace-view-login')).tap(); await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); @@ -217,21 +214,21 @@ describe('E2E Encryption', () => { it('check save banner', async() => { await checkServer(data.server); await checkBanner(); - }) - + }); + it('should add server and create new user', async() => { await sleep(5000); await element(by.id('rooms-list-header-server-dropdown-button')).tap(); await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); await element(by.id('rooms-list-header-server-add')).tap(); - + // TODO: refactor await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); - await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); + await element(by.id('new-server-view-input')).typeText(`${ data.alternateServer }\n`); await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); await element(by.id('workspace-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.registeringUser.username); await element(by.id('register-view-username')).replaceText(data.registeringUser.username); @@ -240,10 +237,10 @@ describe('E2E Encryption', () => { element(by.type(scrollViewType)).atIndex(1).scrollTo('bottom'); await element(by.id('register-view-submit')).tap(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); - + await checkServer(data.alternateServer); }); - + it('should change back', async() => { await element(by.id('rooms-list-header-server-dropdown-button')).tap(); await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); @@ -262,4 +259,4 @@ describe('E2E Encryption', () => { await checkBanner(); }); }); -}); \ No newline at end of file +}); diff --git a/e2e/tests/assorted/02-broadcast.spec.js b/e2e/tests/assorted/02-broadcast.spec.js index e38fe4a5a..56b44ecc6 100644 --- a/e2e/tests/assorted/02-broadcast.spec.js +++ b/e2e/tests/assorted/02-broadcast.spec.js @@ -1,14 +1,14 @@ +// const OTP = require('otp.js'); +// const GA = OTP.googleAuthenticator; + const { - device, expect, element, by, waitFor -} = require('detox'); -const OTP = require('otp.js'); -const GA = OTP.googleAuthenticator; -const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom } = require('../../helpers/app'); + navigateToLogin, login, mockMessage, tapBack, searchRoom +} = require('../../helpers/app'); const data = require('../../data'); const { prepareAndroid } = require('../../helpers/platformFunctions'); -const testuser = data.users.regular -const otheruser = data.users.alternate +const testuser = data.users.regular; +const otheruser = data.users.alternate; describe('Broadcast room', () => { before(async() => { @@ -30,7 +30,7 @@ describe('Broadcast room', () => { await element(by.id('selected-users-view-submit')).tap(); await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000); await element(by.id('create-channel-name')).replaceText(`broadcast${ data.random }`); - await element(by.id('create-channel-broadcast')).longPress(); //https://github.com/facebook/react-native/issues/28032 + await element(by.id('create-channel-broadcast')).longPress(); // https://github.com/facebook/react-native/issues/28032 await element(by.id('create-channel-submit')).tap(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible().withTimeout(60000); @@ -56,11 +56,11 @@ describe('Broadcast room', () => { await navigateToLogin(); await login(otheruser.username, otheruser.password); - //await waitFor(element(by.id('two-factor'))).toBeVisible().withTimeout(5000); - //await expect(element(by.id('two-factor'))).toBeVisible(); - //const code = GA.gen(data.alternateUserTOTPSecret); - //await element(by.id('two-factor-input')).replaceText(code); - //await element(by.id('two-factor-send')).tap(); + // await waitFor(element(by.id('two-factor'))).toBeVisible().withTimeout(5000); + // await expect(element(by.id('two-factor'))).toBeVisible(); + // const code = GA.gen(data.alternateUserTOTPSecret); + // await element(by.id('two-factor-input')).replaceText(code); + // await element(by.id('two-factor-send')).tap(); await searchRoom(`broadcast${ data.random }`); await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap(); diff --git a/e2e/tests/assorted/03-profile.spec.js b/e2e/tests/assorted/03-profile.spec.js index c907fc2a1..955911e63 100644 --- a/e2e/tests/assorted/03-profile.spec.js +++ b/e2e/tests/assorted/03-profile.spec.js @@ -1,13 +1,10 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const { navigateToLogin, login, sleep } = require('../../helpers/app'); const data = require('../../data'); const platformTypes = require('../../helpers/platformTypes'); const { closeKeyboardAndroid, prepareAndroid } = require('../../helpers/platformFunctions'); -const profileChangeUser = data.users.profileChanges +const profileChangeUser = data.users.profileChanges; const scrollDown = 200; @@ -35,7 +32,7 @@ describe('Profile screen', () => { await waitFor(element(by.id('profile-view'))).toBeVisible().withTimeout(2000); }); - describe('Render', async() => { + describe('Render', () => { it('should have profile view', async() => { await expect(element(by.id('profile-view'))).toBeVisible(); }); @@ -63,7 +60,7 @@ describe('Profile screen', () => { it('should have avatar url', async() => { await expect(element(by.id('profile-view-avatar-url'))).toExist(); }); - + it('should have reset avatar button', async() => { await waitFor(element(by.id('profile-view-reset-avatar'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down'); }); @@ -81,7 +78,7 @@ describe('Profile screen', () => { }); }); - describe('Usage', async() => { + describe('Usage', () => { it('should change name and username', async() => { await element(by.id('profile-view-name')).replaceText(`${ profileChangeUser.username }new`); await element(by.id('profile-view-username')).typeText(`${ profileChangeUser.username }new`); @@ -96,7 +93,8 @@ describe('Profile screen', () => { await element(by.id('profile-view-new-password')).replaceText(`${ profileChangeUser.password }new`); await element(by.id('profile-view-submit')).tap(); await element(by.type(textInputType)).typeText(`${ profileChangeUser.password }\n`); - await element(by.text('SAVE')).tap(); + // TODO: Check if this is fine on iOS + if(device.getPlatform() === 'android') await element(by.text('SAVE')).tap(); await waitForToast(); }); diff --git a/e2e/tests/assorted/04-setting.spec.js b/e2e/tests/assorted/04-setting.spec.js index 9353fbf17..19822087a 100644 --- a/e2e/tests/assorted/04-setting.spec.js +++ b/e2e/tests/assorted/04-setting.spec.js @@ -1,14 +1,11 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); -const { navigateToLogin, login, tapBack } = require('../../helpers/app'); +const { navigateToLogin, login } = require('../../helpers/app'); const platformTypes = require('../../helpers/platformTypes'); const { prepareAndroid } = require('../../helpers/platformFunctions'); const data = require('../../data'); -const testuser = data.users.regular +const testuser = data.users.regular; describe('Settings screen', () => { let alertButtonType; @@ -26,7 +23,7 @@ describe('Settings screen', () => { await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); }); - describe('Render', async() => { + describe('Render', () => { it('should have settings view', async() => { await expect(element(by.id('settings-view'))).toBeVisible(); }); @@ -68,7 +65,7 @@ describe('Settings screen', () => { }); }); - describe('Usage', async() => { + describe('Usage', () => { it('should tap clear cache and navigate to roomslistview', async() => { await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); await element(by.id('settings-view-clear-cache')).tap(); @@ -76,6 +73,6 @@ describe('Settings screen', () => { await element(by.text('Clear').and(by.type(alertButtonType))).tap(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(5000); await waitFor(element(by.id(`rooms-list-view-item-${ data.groups.private.name }`))).toExist().withTimeout(10000); - }) + }); }); }); diff --git a/e2e/tests/assorted/05-joinpublicroom.spec.js b/e2e/tests/assorted/05-joinpublicroom.spec.js index 1d43a0de4..01d0815b4 100644 --- a/e2e/tests/assorted/05-joinpublicroom.spec.js +++ b/e2e/tests/assorted/05-joinpublicroom.spec.js @@ -1,13 +1,12 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom } = require('../../helpers/app'); +const { + navigateToLogin, login, mockMessage, tapBack, searchRoom +} = require('../../helpers/app'); const platformTypes = require('../../helpers/platformTypes'); const { prepareAndroid } = require('../../helpers/platformFunctions'); -const testuser = data.users.regular +const testuser = data.users.regular; const room = data.channels.detoxpublic.name; async function navigateToRoom() { @@ -32,7 +31,7 @@ describe('Join public room', () => { await navigateToRoom(); }); - describe('Render', async() => { + describe('Render', () => { it('should have room screen', async() => { await expect(element(by.id('room-view'))).toBeVisible(); }); @@ -42,14 +41,14 @@ describe('Join public room', () => { // }); // Render - Header - describe('Header', async() => { + describe('Header', () => { it('should have actions button ', async() => { await expect(element(by.id('room-header'))).toBeVisible(); }); }); // Render - Join - describe('Join', async() => { + describe('Join', () => { it('should have join', async() => { await expect(element(by.id('room-view-join'))).toBeVisible(); }); @@ -67,7 +66,7 @@ describe('Join public room', () => { }); }); - describe('Room Actions', async() => { + describe('Room Actions', () => { before(async() => { await navigateToRoomActions(); }); @@ -123,11 +122,11 @@ describe('Join public room', () => { after(async() => { await tapBack(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); - }) + }); }); }); - describe('Usage', async() => { + describe('Usage', () => { it('should join room', async() => { await element(by.id('room-view-join-button')).tap(); await tapBack(); diff --git a/e2e/tests/assorted/06-status.spec.js b/e2e/tests/assorted/06-status.spec.js index 4f9b1bd78..29673e82d 100644 --- a/e2e/tests/assorted/06-status.spec.js +++ b/e2e/tests/assorted/06-status.spec.js @@ -1,18 +1,16 @@ -const { - expect, element, by, waitFor -} = require('detox'); const { navigateToLogin, login, sleep } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); const data = require('../../data'); -const testuser = data.users.regular + +const testuser = data.users.regular; async function waitForToast() { await sleep(300); } describe('Status screen', () => { - before(async () => { + before(async() => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await prepareAndroid(); await navigateToLogin(); @@ -26,8 +24,8 @@ describe('Status screen', () => { await waitFor(element(by.id('status-view'))).toBeVisible().withTimeout(2000); }); - describe('Render', async () => { - it('should have status input', async () => { + describe('Render', () => { + it('should have status input', async() => { await expect(element(by.id('status-view-input'))).toBeVisible(); await expect(element(by.id('status-view-online'))).toExist(); await expect(element(by.id('status-view-busy'))).toExist(); @@ -36,17 +34,17 @@ describe('Status screen', () => { }); }); - describe('Usage', async () => { - it('should change status', async () => { + describe('Usage', () => { + it('should change status', async() => { await element(by.id('status-view-busy')).tap(); await waitFor(element(by.id('status-view-current-busy'))).toExist().withTimeout(2000); }); - it('should change status text', async () => { + it('should change status text', async() => { await element(by.id('status-view-input')).typeText('status-text-new'); await element(by.id('status-view-submit')).tap(); await waitForToast(); await waitFor(element(by.text('status-text-new').withAncestor(by.id('sidebar-custom-status')))).toExist().withTimeout(2000); }); }); -}); \ No newline at end of file +}); diff --git a/e2e/tests/assorted/07-changeserver.spec.js b/e2e/tests/assorted/07-changeserver.spec.js index e543e06a4..b85e6633a 100644 --- a/e2e/tests/assorted/07-changeserver.spec.js +++ b/e2e/tests/assorted/07-changeserver.spec.js @@ -1,6 +1,3 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); const { navigateToLogin, login, checkServer } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -10,7 +7,7 @@ const reopenAndCheckServer = async(server) => { await device.launchApp({ permissions: { notifications: 'YES' } }); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(6000); await checkServer(server); -} +}; describe('Change server', () => { before(async() => { @@ -27,7 +24,7 @@ describe('Change server', () => { await element(by.id('rooms-list-header-server-add')).tap(); await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(6000); - await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); + await element(by.id('new-server-view-input')).typeText(`${ data.alternateServer }\n`); await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(10000); await reopenAndCheckServer(data.server); }); @@ -67,5 +64,5 @@ describe('Change server', () => { it('should reopen the app and show main server', async() => { await reopenAndCheckServer(data.server); - }) + }); }); diff --git a/e2e/tests/assorted/08-joinprotectedroom.spec.js b/e2e/tests/assorted/08-joinprotectedroom.spec.js index 94bc91213..81f304928 100644 --- a/e2e/tests/assorted/08-joinprotectedroom.spec.js +++ b/e2e/tests/assorted/08-joinprotectedroom.spec.js @@ -1,13 +1,10 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom } = require('../../helpers/app'); +const { navigateToLogin, login, mockMessage, searchRoom } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); -const testuser = data.users.regular -const room = data.channels.detoxpublicprotected.name -const joinCode = data.channels.detoxpublicprotected.joinCode +const testuser = data.users.regular; +const room = data.channels.detoxpublicprotected.name; +const { joinCode } = data.channels.detoxpublicprotected; async function navigateToRoom() { await searchRoom(room); @@ -29,10 +26,10 @@ describe('Join protected room', () => { await navigateToRoom(); }); - describe('Usage', async() => { + describe('Usage', () => { it('should tap join and ask for join code', async() => { await openJoinCode(); - }) + }); it('should cancel join room', async() => { await element(by.id('join-code-cancel')).tap(); diff --git a/e2e/tests/assorted/09-joinfromdirectory.spec.js b/e2e/tests/assorted/09-joinfromdirectory.spec.js index c46752d8f..ee501ead0 100644 --- a/e2e/tests/assorted/09-joinfromdirectory.spec.js +++ b/e2e/tests/assorted/09-joinfromdirectory.spec.js @@ -1,14 +1,13 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, tapBack, sleep } = require('../../helpers/app'); +const { + navigateToLogin, login, tapBack, sleep +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); -const testuser = data.users.regular +const testuser = data.users.regular; async function navigateToRoom(search) { - await element(by.id('directory-view-search')).replaceText(search); + await element(by.id('directory-view-search')).replaceText(search); await waitFor(element(by.id(`directory-view-item-${ search }`))).toBeVisible().withTimeout(10000); await sleep(300); // app takes some time to animate await element(by.id(`directory-view-item-${ search }`)).tap(); @@ -24,34 +23,34 @@ describe('Join room from directory', () => { await login(testuser.username, testuser.password); }); - describe('Usage', async() => { + describe('Usage', () => { it('should tap directory', async() => { - await element(by.id('rooms-list-view-directory')).tap(); + await element(by.id('rooms-list-view-directory')).tap(); await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); - }) + }); it('should search public channel and navigate', async() => { await navigateToRoom(data.channels.detoxpublic.name); - }) - - it('should search user and navigate', async() => { - await tapBack(); - await element(by.id('rooms-list-view-directory')).tap(); - await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); - await element(by.id('directory-view-dropdown')).tap(); - await element(by.label('Users')).tap(); - await element(by.label('Search by')).tap(); - await navigateToRoom(data.users.alternate.username); - }) + }); it('should search user and navigate', async() => { await tapBack(); await element(by.id('rooms-list-view-directory')).tap(); await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); - await element(by.id('directory-view-dropdown')).tap(); + await element(by.id('directory-view-dropdown')).tap(); + await element(by.label('Users')).tap(); + await element(by.label('Search by')).tap(); + await navigateToRoom(data.users.alternate.username); + }); + + it('should search user and navigate', async() => { + await tapBack(); + await element(by.id('rooms-list-view-directory')).tap(); + await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); + await element(by.id('directory-view-dropdown')).tap(); await element(by.label('Teams')).tap(); - await element(by.label('Search by')).tap(); + await element(by.label('Search by')).tap(); await navigateToRoom(data.teams.private.name); - }) + }); }); }); diff --git a/e2e/tests/assorted/10-deleteserver.spec.js b/e2e/tests/assorted/10-deleteserver.spec.js index 9024e4efc..9224aafd8 100644 --- a/e2e/tests/assorted/10-deleteserver.spec.js +++ b/e2e/tests/assorted/10-deleteserver.spec.js @@ -1,8 +1,7 @@ -const { - device, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { sleep, navigateToLogin, login, checkServer } = require('../../helpers/app'); +const { + sleep, navigateToLogin, login, checkServer +} = require('../../helpers/app'); const platformTypes = require('../../helpers/platformTypes'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -19,7 +18,7 @@ describe('Delete server', () => { it('should be logged in main server', async() => { await checkServer(data.server); - }) + }); it('should add server', async() => { await sleep(5000); @@ -28,7 +27,7 @@ describe('Delete server', () => { await element(by.id('rooms-list-header-server-add')).tap(); await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(10000); - await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); + await element(by.id('new-server-view-input')).typeText(`${ data.alternateServer }\n`); await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(10000); await element(by.id('workspace-view-register')).tap(); await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); diff --git a/e2e/tests/assorted/11-deeplinking.spec.js b/e2e/tests/assorted/11-deeplinking.spec.js index 29df4d99e..dda7e4be1 100644 --- a/e2e/tests/assorted/11-deeplinking.spec.js +++ b/e2e/tests/assorted/11-deeplinking.spec.js @@ -1,9 +1,6 @@ -const { - device, element, by, waitFor -} = require('detox'); const data = require('../../data'); const { tapBack, checkServer, navigateToRegister } = require('../../helpers/app'); -const { post, get, login } = require('../../helpers/data_setup'); +const { get, login } = require('../../helpers/data_setup'); const platformTypes = require('../../helpers/platformTypes'); const { closeKeyboardAndroid, prepareAndroid } = require('../../helpers/platformFunctions'); @@ -50,7 +47,7 @@ describe('Deep linking', () => { await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); await checkServer(data.server); await waitFor(element(by.id(`rooms-list-view-item-${ data.groups.private.name }`))).toBeVisible().withTimeout(2000); - } + }; it('should authenticate and navigate', async() => { await authAndNavigate(); @@ -72,7 +69,7 @@ describe('Deep linking', () => { }); describe('Room', () => { - describe('While logged in', async() => { + describe('While logged in', () => { it('should navigate to the room using path', async() => { await device.launchApp({ permissions: { notifications: 'YES' }, @@ -84,7 +81,7 @@ describe('Deep linking', () => { }); it('should navigate to the room using rid', async() => { - const roomResult = await get(`groups.info?roomName=${ data.groups.private.name }`) + const roomResult = await get(`groups.info?roomName=${ data.groups.private.name }`); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, @@ -96,7 +93,7 @@ describe('Deep linking', () => { }); }); - describe('Others', async() => { + describe('Others', () => { it('should change server', async() => { await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await element(by.id('rooms-list-header-server-dropdown-button')).tap(); diff --git a/e2e/tests/assorted/12-i18n.spec.js b/e2e/tests/assorted/12-i18n.spec.js index fca49888e..da154a54d 100644 --- a/e2e/tests/assorted/12-i18n.spec.js +++ b/e2e/tests/assorted/12-i18n.spec.js @@ -116,4 +116,4 @@ describe('i18n', () => { await post('users.setPreferences', { data: { language: 'en' } }); // Set back to english }); }); -}); \ No newline at end of file +}); diff --git a/e2e/tests/init.js b/e2e/tests/init.js index 18ad98327..b1e08a2bd 100644 --- a/e2e/tests/init.js +++ b/e2e/tests/init.js @@ -1,13 +1,13 @@ const detox = require('detox'); -const config = require('../../package.json').detox; -const { setup } = require('../helpers/data_setup') const adapter = require('detox/runners/mocha/adapter'); +const config = require('../../package.json').detox; +const { setup } = require('../helpers/data_setup'); before(async() => { - await Promise.all([setup(), detox.init(config, { launchApp: false })]) - //await dataSetup() - //await detox.init(config, { launchApp: false }); - //await device.launchApp({ permissions: { notifications: 'YES' } }); + await Promise.all([setup(), detox.init(config, { launchApp: false })]); + // await dataSetup() + // await detox.init(config, { launchApp: false }); + // await device.launchApp({ permissions: { notifications: 'YES' } }); }); beforeEach(async function() { diff --git a/e2e/tests/onboarding/01-onboarding.spec.js b/e2e/tests/onboarding/01-onboarding.spec.js index 650a1c5bf..a4486b2e1 100644 --- a/e2e/tests/onboarding/01-onboarding.spec.js +++ b/e2e/tests/onboarding/01-onboarding.spec.js @@ -1,6 +1,3 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -14,10 +11,10 @@ describe('Onboarding', () => { describe('Render', () => { it('should have onboarding screen', async() => { await expect(element(by.id('onboarding-view'))).toBeVisible(); - }); + }); it('should have "Join a workspace"', async() => { - await expect(element(by.id('join-workspace'))).toBeVisible(); + await expect(element(by.id('join-workspace'))).toBeVisible(); }); it('should have "Create a new workspace"', async() => { @@ -29,7 +26,7 @@ describe('Onboarding', () => { // it('should navigate to create new workspace', async() => { // // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554 // }); - + it('should navigate to join a workspace', async() => { await element(by.id('join-workspace')).tap(); await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); @@ -52,7 +49,7 @@ describe('Onboarding', () => { await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000); await element(by.id('join-workspace')).tap(); await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); - await element(by.id('new-server-view-input')).typeText(`${data.server}\n`); + await element(by.id('new-server-view-input')).typeText(`${ data.server }\n`); await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); }); }); diff --git a/e2e/tests/onboarding/02-legal.spec.js b/e2e/tests/onboarding/02-legal.spec.js index ac7b26492..7a9c57c35 100644 --- a/e2e/tests/onboarding/02-legal.spec.js +++ b/e2e/tests/onboarding/02-legal.spec.js @@ -1,26 +1,22 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const { navigateToRegister, navigateToLogin } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); describe('Legal screen', () => { - describe('From Login', () => { before(async() => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await prepareAndroid(); await navigateToLogin(); }); - - it('should have legal button on login', async() => { + + it('should have legal button on login', async() => { await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000); }); - + it('should navigate to legal from login', async() => { await expect(element(by.id('login-view-more'))).toBeVisible(); await element(by.id('login-view-more')).tap(); - await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(4000) + await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(4000); }); }); @@ -30,32 +26,32 @@ describe('Legal screen', () => { await navigateToRegister(); }); - it('should have legal button on register', async() => { + it('should have legal button on register', async() => { await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000); }); - + it('should navigate to legal from register', async() => { await expect(element(by.id('register-view-more'))).toBeVisible(); await element(by.id('register-view-more')).tap(); await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(4000); }); - + it('should have terms of service button', async() => { await expect(element(by.id('legal-terms-button'))).toBeVisible(); }); - + it('should have privacy policy button', async() => { await expect(element(by.id('legal-privacy-button'))).toBeVisible(); }); // We can't simulate how webview behaves, so I had to disable :( - /* + /* it('should navigate to terms', async() => { await element(by.id('legal-terms-button')).tap(); await waitFor(element(by.id('terms-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('terms-view'))).toBeVisible(); }); - + it('should navigate to privacy', async() => { await tapBack(); await element(by.id('legal-privacy-button')).tap(); diff --git a/e2e/tests/onboarding/03-forgotpassword.spec.js b/e2e/tests/onboarding/03-forgotpassword.spec.js index 93612ac89..21863c8c7 100644 --- a/e2e/tests/onboarding/03-forgotpassword.spec.js +++ b/e2e/tests/onboarding/03-forgotpassword.spec.js @@ -1,6 +1,3 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); const { navigateToLogin } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -14,7 +11,7 @@ describe('Forgot password screen', () => { await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000); }); - describe('Render', async() => { + describe('Render', () => { it('should have forgot password screen', async() => { await expect(element(by.id('forgot-password-view'))).toExist(); }); @@ -28,7 +25,7 @@ describe('Forgot password screen', () => { }); }); - describe('Usage', async() => { + describe('Usage', () => { it('should reset password and navigate to login', async() => { await element(by.id('forgot-password-view-email')).replaceText(data.users.existing.email); await element(by.id('forgot-password-view-submit')).tap(); diff --git a/e2e/tests/onboarding/04-createuser.spec.js b/e2e/tests/onboarding/04-createuser.spec.js index 96f19ae3a..538512335 100644 --- a/e2e/tests/onboarding/04-createuser.spec.js +++ b/e2e/tests/onboarding/04-createuser.spec.js @@ -1,7 +1,7 @@ const { device, expect, element, by, waitFor } = require('detox'); -const { navigateToRegister, sleep } = require('../../helpers/app'); +const { navigateToRegister } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); const data = require('../../data'); @@ -39,7 +39,6 @@ describe('Create user screen', () => { }); describe('Usage', () => { - // FIXME: Detox isn't able to check if it's tappable: https://github.com/wix/Detox/issues/246 // it('should submit invalid email and do nothing', async() => { // const invalidEmail = 'invalidemail'; diff --git a/e2e/tests/onboarding/05-login.spec.js b/e2e/tests/onboarding/05-login.spec.js index 2ad5dcf4e..9d3f1f2a5 100644 --- a/e2e/tests/onboarding/05-login.spec.js +++ b/e2e/tests/onboarding/05-login.spec.js @@ -1,7 +1,7 @@ const { expect, element, by, waitFor } = require('detox'); -const { navigateToLogin, tapBack, sleep } = require('../../helpers/app'); +const { navigateToLogin, tapBack } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); const data = require('../../data'); @@ -48,13 +48,13 @@ describe('Login screen', () => { await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); await tapBack(); }); - + it('should navigate to forgot password', async() => { await element(by.id('login-view-forgot-password')).tap(); await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000); await tapBack(); }); - + it('should insert wrong password and get error', async() => { await element(by.id('login-view-email')).replaceText(data.users.regular.username); await element(by.id('login-view-password')).replaceText('NotMyActualPassword'); @@ -62,7 +62,7 @@ describe('Login screen', () => { await waitFor(element(by.text('Your credentials were rejected! Please try again.'))).toBeVisible().withTimeout(10000); await element(by.text('OK')).tap(); }); - + it('should login with success', async() => { await element(by.id('login-view-password')).replaceText(data.users.regular.password); await element(by.id('login-view-submit')).tap(); diff --git a/e2e/tests/onboarding/06-roomslist.spec.js b/e2e/tests/onboarding/06-roomslist.spec.js index 1e1c164ac..6d0b3e22d 100644 --- a/e2e/tests/onboarding/06-roomslist.spec.js +++ b/e2e/tests/onboarding/06-roomslist.spec.js @@ -1,17 +1,16 @@ const { device, expect, element, by, waitFor } = require('detox'); -const { login, navigateToLogin, logout, tapBack, sleep, searchRoom } = require('../../helpers/app'); +const { login, navigateToLogin, logout, tapBack, searchRoom } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); const data = require('../../data'); describe('Rooms list screen', () => { - before(async() => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await prepareAndroid(); await navigateToLogin(); - await login(data.users.regular.username, data.users.regular.password) + await login(data.users.regular.username, data.users.regular.password); }); describe('Render', () => { @@ -22,13 +21,13 @@ describe('Rooms list screen', () => { it('should have room item', async() => { await expect(element(by.id('rooms-list-view-item-general'))).toExist(); }); - + // Render - Header describe('Header', () => { it('should have create channel button', async() => { await expect(element(by.id('rooms-list-view-create-channel'))).toBeVisible(); }); - + it('should have sidebar button', async() => { await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible(); }); diff --git a/e2e/tests/onboarding/07-server-history.spec.js b/e2e/tests/onboarding/07-server-history.spec.js index 0878619b0..1e24e4d48 100644 --- a/e2e/tests/onboarding/07-server-history.spec.js +++ b/e2e/tests/onboarding/07-server-history.spec.js @@ -18,7 +18,7 @@ describe('Server history', () => { 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(); diff --git a/e2e/tests/room/01-createroom.spec.js b/e2e/tests/room/01-createroom.spec.js index 4449cf4db..b5c634967 100644 --- a/e2e/tests/room/01-createroom.spec.js +++ b/e2e/tests/room/01-createroom.spec.js @@ -1,8 +1,5 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { tapBack, sleep, navigateToLogin, login, tryTapping } = require('../../helpers/app'); +const { tapBack, navigateToLogin, login, tryTapping } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -15,30 +12,30 @@ describe('Create room screen', () => { await login(data.users.regular.username, data.users.regular.password); }); - describe('New Message', async() => { + describe('New Message', () => { before(async() => { await element(by.id('rooms-list-view-create-channel')).tap(); }); - describe('Render', async() => { + describe('Render', () => { it('should have new message screen', async() => { await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); }); - + it('should have search input', async() => { await waitFor(element(by.id('new-message-view-search'))).toBeVisible().withTimeout(2000); }); - }) + }); - describe('Usage', async() => { + describe('Usage', () => { it('should back to rooms list', async() => { await waitFor(element(by.id('new-message-view-close'))).toBeVisible().withTimeout(5000); await element(by.id('new-message-view-close')).tap(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(5000); - + await tryTapping(element(by.id('rooms-list-view-create-channel')), 3000); - //await element(by.id('rooms-list-view-create-channel')).tap(); + // await element(by.id('rooms-list-view-create-channel')).tap(); await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(5000); }); @@ -58,13 +55,13 @@ describe('Create room screen', () => { await element(by.id('new-message-view-create-channel')).tap(); await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); }); - }) + }); }); - describe('Select Users', async() => { + describe('Select Users', () => { 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 waitFor(element(by.id('select-users-view-item-rocket.cat'))).toBeVisible().withTimeout(10000); }); it('should select/unselect user', async() => { @@ -82,27 +79,27 @@ describe('Create room screen', () => { await element(by.id('selected-users-view-submit')).tap(); await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); }); - }) + }); - describe('Create Channel', async() => { - describe('Render', async() => { + describe('Create Channel', () => { + describe('Render', () => { it('should render all fields', async() => { 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-readonly'))).toBeVisible(); await expect(element(by.id('create-channel-broadcast'))).toBeVisible(); - }) - }) + }); + }); - describe('Usage', async() => { + describe('Usage', () => { it('should get invalid room', async() => { await element(by.id('create-channel-name')).typeText('general'); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.text(`A channel with name general exists`))).toExist().withTimeout(60000); - await expect(element(by.text(`A channel with name general exists`))).toExist(); + await waitFor(element(by.text('A channel with name general exists'))).toExist().withTimeout(60000); + await expect(element(by.text('A channel with name general exists'))).toExist(); await element(by.text('OK')).tap(); }); - + it('should create public room', async() => { const room = `public${ data.random }`; await element(by.id('create-channel-name')).replaceText(''); @@ -118,7 +115,7 @@ describe('Create room screen', () => { await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(6000); await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); }); - + it('should create private room', async() => { const room = `private${ data.random }`; await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(5000); @@ -163,6 +160,6 @@ describe('Create room screen', () => { await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); }); - }) + }); }); }); diff --git a/e2e/tests/room/02-room.spec.js b/e2e/tests/room/02-room.spec.js index cc9bb827e..6255c0036 100644 --- a/e2e/tests/room/02-room.spec.js +++ b/e2e/tests/room/02-room.spec.js @@ -1,8 +1,7 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, starMessage, pinMessage, dismissReviewNag, tryTapping, logout } = require('../../helpers/app'); +const { + navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, starMessage, pinMessage, dismissReviewNag, tryTapping +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); async function navigateToRoom(roomName) { @@ -22,14 +21,14 @@ describe('Room screen', () => { await navigateToRoom(mainRoom); }); - describe('Render', async() => { + describe('Render', () => { it('should have room screen', async() => { await expect(element(by.id('room-view'))).toExist(); await waitFor(element(by.id(`room-view-title-${ mainRoom }`))).toExist().withTimeout(5000); }); // Render - Header - describe('Header', async() => { + describe('Header', () => { it('should have actions button ', async() => { await expect(element(by.id('room-header'))).toExist(); }); @@ -40,7 +39,7 @@ describe('Room screen', () => { }); // Render - Messagebox - describe('Messagebox', async() => { + describe('Messagebox', () => { it('should have messagebox', async() => { await expect(element(by.id('messagebox'))).toExist(); }); @@ -65,15 +64,15 @@ describe('Room screen', () => { }); }); - describe('Usage', async() => { - describe('Messagebox', async() => { + describe('Usage', () => { + describe('Messagebox', () => { it('should send message', async() => { - await mockMessage('message') + await mockMessage('message'); await expect(element(by.text(`${ data.random }message`)).atIndex(0)).toExist(); }); - it('should show/hide emoji keyboard', async () => { + it('should show/hide emoji keyboard', async() => { if (device.getPlatform() === 'android') { await element(by.id('messagebox-open-emoji')).tap(); await waitFor(element(by.id('messagebox-keyboard-emoji'))).toExist().withTimeout(10000); @@ -107,20 +106,20 @@ describe('Room screen', () => { it('should not show emoji autocomplete on semicolon in middle of a string', async() => { await element(by.id('messagebox-input')).tap(); // await element(by.id('messagebox-input')).replaceText(':'); - await element(by.id('messagebox-input')).typeText('name:is'); + await element(by.id('messagebox-input')).typeText('name:is'); await waitFor(element(by.id('messagebox-container'))).toNotExist().withTimeout(20000); await element(by.id('messagebox-input')).clearText(); }); it('should show and tap on user autocomplete and send mention', async() => { - const username = data.users.regular.username + const { username } = data.users.regular; await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText(`@${ username }`); await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(4000); - await waitFor(element(by.id(`mention-item-${ username }`))).toBeVisible().withTimeout(4000) + await waitFor(element(by.id(`mention-item-${ username }`))).toBeVisible().withTimeout(4000); await tryTapping(element(by.id(`mention-item-${ username }`)), 2000, true); await expect(element(by.id('messagebox-input'))).toHaveText(`@${ username } `); - await tryTapping(element(by.id('messagebox-input')), 2000) + await tryTapping(element(by.id('messagebox-input')), 2000); await element(by.id('messagebox-input')).typeText(`${ data.random }mention`); await element(by.id('messagebox-send-message')).tap(); // await waitFor(element(by.label(`@${ data.user } ${ data.random }mention`)).atIndex(0)).toExist().withTimeout(60000); @@ -128,7 +127,7 @@ describe('Room screen', () => { it('should not show user autocomplete on @ in the middle of a string', async() => { await element(by.id('messagebox-input')).tap(); - await element(by.id('messagebox-input')).typeText(`email@gmail`); + await element(by.id('messagebox-input')).typeText('email@gmail'); await waitFor(element(by.id('messagebox-container'))).toNotExist().withTimeout(4000); await element(by.id('messagebox-input')).clearText(); }); @@ -136,9 +135,9 @@ describe('Room screen', () => { 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'))).toExist().withTimeout(4000); + // await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(4000); await waitFor(element(by.id('mention-item-general'))).toBeVisible().withTimeout(4000); - await tryTapping(element(by.id('mention-item-general')), 2000, true) + await tryTapping(element(by.id('mention-item-general')), 2000, true); await expect(element(by.id('messagebox-input'))).toHaveText('#general '); await element(by.id('messagebox-input')).clearText(); }); @@ -149,7 +148,7 @@ describe('Room screen', () => { await waitFor(element(by.id('messagebox-container'))).toNotExist().withTimeout(4000); await element(by.id('messagebox-input')).clearText(); }); - it('should draft message', async () => { + it('should draft message', async() => { await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText(`${ data.random }draft`); await tapBack(); @@ -161,10 +160,10 @@ describe('Room screen', () => { await navigateToRoom(mainRoom); await expect(element(by.id('messagebox-input'))).toHaveText(''); - }); + }); }); - describe('Message', async() => { + describe('Message', () => { it('should copy permalink', async() => { await element(by.text(`${ data.random }message`)).atIndex(0).longPress(); await expect(element(by.id('action-sheet'))).toExist(); @@ -186,9 +185,9 @@ describe('Room screen', () => { }); it('should star message', async() => { - await starMessage('message') + await starMessage('message'); - await sleep(1000) //https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2324 + await sleep(1000); //https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2324 await element(by.text(`${ data.random }message`)).atIndex(0).longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); @@ -231,9 +230,9 @@ describe('Room screen', () => { await waitFor(element(by.id('message-reaction-:grimacing:'))).toExist().withTimeout(60000); }); - // it('should ask for review', async() => { - // //await dismissReviewNag(); //TODO: Create a proper test for this elsewhere. - // }) + it('should ask for review', async() => { + await dismissReviewNag(); // TODO: Create a proper test for this elsewhere. + }); it('should remove reaction', async() => { await element(by.id('message-reaction-:grinning:')).tap(); @@ -266,8 +265,8 @@ describe('Room screen', () => { }); it('should pin message', async() => { - await mockMessage('pin') - await pinMessage('pin') + await mockMessage('pin'); + await pinMessage('pin'); await waitFor(element(by.text(`${ data.random }pin`)).atIndex(0)).toExist().withTimeout(5000); await waitFor(element(by.text(`${ data.users.regular.username } Message pinned`)).atIndex(0)).toExist().withTimeout(5000); @@ -280,7 +279,7 @@ describe('Room screen', () => { }); it('should delete message', async() => { - await mockMessage('delete') + await mockMessage('delete'); await waitFor(element(by.text(`${ data.random }delete`)).atIndex(0)).toBeVisible(); await element(by.text(`${ data.random }delete`)).atIndex(0).longPress(); diff --git a/e2e/tests/room/03-roomactions.spec.js b/e2e/tests/room/03-roomactions.spec.js index abc315a32..862ca7447 100644 --- a/e2e/tests/room/03-roomactions.spec.js +++ b/e2e/tests/room/03-roomactions.spec.js @@ -1,6 +1,3 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); const { navigateToLogin, login, tapBack, sleep, searchRoom, mockMessage, starMessage, pinMessage } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -47,9 +44,9 @@ describe('Room actions screen', () => { await login(data.users.regular.username, data.users.regular.password); ({ alertButtonType } = platformTypes[device.getPlatform()]); }); - - describe('Render', async() => { - describe('Direct', async() => { + + describe('Render', () => { + describe('Direct', () => { before(async() => { await navigateToRoomActions('d'); }); @@ -57,41 +54,41 @@ describe('Room actions screen', () => { it('should have room actions screen', async() => { await expect(element(by.id('room-actions-view'))).toExist(); }); - + it('should have info', async() => { await expect(element(by.id('room-actions-info'))).toExist(); }); - + // it('should have voice', async() => { // await expect(element(by.id('room-actions-voice'))).toExist(); // }); - + // it('should have video', async() => { // await expect(element(by.id('room-actions-video'))).toExist(); // }); - + it('should have files', async() => { await expect(element(by.id('room-actions-files'))).toExist(); }); - + it('should have mentions', async() => { await expect(element(by.id('room-actions-mentioned'))).toExist(); }); - + it('should have starred', async() => { await expect(element(by.id('room-actions-starred'))).toExist(); }); - + it('should have share', async() => { await waitFor(element(by.id('room-actions-share'))).toExist(); await expect(element(by.id('room-actions-share'))).toExist(); }); - + it('should have pinned', async() => { await waitFor(element(by.id('room-actions-pinned'))).toExist(); await expect(element(by.id('room-actions-pinned'))).toExist(); }); - + it('should have notifications', async() => { await waitFor(element(by.id('room-actions-notifications'))).toExist(); await expect(element(by.id('room-actions-notifications'))).toExist(); @@ -107,7 +104,7 @@ describe('Room actions screen', () => { }); }); - describe('Channel/Group', async() => { + describe('Channel/Group', () => { before(async() => { await navigateToRoomActions('c'); }); @@ -115,15 +112,15 @@ describe('Room actions screen', () => { it('should have room actions screen', async() => { await expect(element(by.id('room-actions-view'))).toExist(); }); - + it('should have info', async() => { await expect(element(by.id('room-actions-info'))).toExist(); }); - + // it('should have voice', async() => { // await expect(element(by.id('room-actions-voice'))).toExist(); // }); - + // it('should have video', async() => { // await expect(element(by.id('room-actions-video'))).toExist(); // }); @@ -135,34 +132,34 @@ describe('Room actions screen', () => { it('should have add user', async() => { await expect(element(by.id('room-actions-add-user'))).toExist(); }); - + it('should have files', async() => { await expect(element(by.id('room-actions-files'))).toExist(); }); - + it('should have mentions', async() => { await expect(element(by.id('room-actions-mentioned'))).toExist(); }); - + it('should have starred', async() => { await expect(element(by.id('room-actions-starred'))).toExist(); }); - + it('should have share', async() => { await waitFor(element(by.id('room-actions-share'))).toExist(); await expect(element(by.id('room-actions-share'))).toExist(); }); - + it('should have pinned', async() => { await waitFor(element(by.id('room-actions-pinned'))).toExist(); await expect(element(by.id('room-actions-pinned'))).toExist(); }); - + it('should have notifications', async() => { await waitFor(element(by.id('room-actions-notifications'))).toExist(); await expect(element(by.id('room-actions-notifications'))).toExist(); }); - + it('should have leave channel', async() => { await waitFor(element(by.id('room-actions-leave-channel'))).toExist(); await expect(element(by.id('room-actions-leave-channel'))).toExist(); @@ -170,7 +167,7 @@ describe('Room actions screen', () => { }); }); - describe('Usage', async() => { + describe('Usage', () => { describe('TDB', async() => { // TODO: test into a jitsi call // it('should NOT navigate to voice call', async() => { @@ -196,7 +193,7 @@ describe('Room actions screen', () => { // }); }); - describe('Common', async() => { + describe('Common', () => { 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); @@ -205,15 +202,14 @@ describe('Room actions screen', () => { }); it('should show starred message and unstar it', async() => { - - //Go back to room and send a message + // Go back to room and send a message await tapBack(); await mockMessage('messageToStar'); - //Star the message - await starMessage('messageToStar') + // Star the message + await starMessage('messageToStar'); - //Back into Room Actions + // Back into Room Actions await element(by.id('room-header')).tap(); await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); @@ -235,15 +231,14 @@ describe('Room actions screen', () => { }); it('should show pinned message and unpin it', async() => { - - //Go back to room and send a message + // Go back to room and send a message await tapBack(); await mockMessage('messageToPin'); - //Pin the message - await pinMessage('messageToPin') + // Pin the message + await pinMessage('messageToPin'); - //Back into Room Actions + // Back into Room Actions await element(by.id('room-header')).tap(); await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); await element(by.id('room-actions-scrollview')).scrollTo('bottom'); @@ -280,7 +275,7 @@ describe('Room actions screen', () => { // }); }); - describe('Notification', async() => { + describe('Notification', () => { it('should navigate to notification preference view', async() => { await element(by.id('room-actions-scrollview')).scrollTo('bottom'); await waitFor(element(by.id('room-actions-notifications'))).toExist().withTimeout(2000); @@ -325,13 +320,13 @@ describe('Room actions screen', () => { after(async() => { await backToActions(); }); - }) + }); - describe('Channel/Group', async() => { + describe('Channel/Group', () => { // Currently, there's no way to add more owners to the room // So we test only for the 'You are the last owner...' message - const user = data.users.alternate + const user = data.users.alternate; it('should tap on leave channel and raise alert', async() => { await element(by.id('room-actions-scrollview')).scrollTo('bottom'); @@ -370,12 +365,25 @@ describe('Room actions screen', () => { await backToActions(); }); - describe('Room Members', async() => { + describe('Room Members', () => { before(async() => { await element(by.id('room-actions-members')).tap(); await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000); }); + const openActionSheet = async(username) => { + await waitFor(element(by.id(`room-members-view-item-${ username }`))).toExist().withTimeout(5000); + await element(by.id(`room-members-view-item-${ username }`)).tap(); + await sleep(300); + await expect(element(by.id('action-sheet'))).toExist(); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up'); + }; + + const closeActionSheet = async() => { + await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); + }; + it('should show all users', async() => { await element(by.id('room-members-view-toggle-status')).tap(); await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); @@ -404,19 +412,6 @@ describe('Room actions screen', () => { await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); }); - const openActionSheet = async(username) => { - await waitFor(element(by.id(`room-members-view-item-${ username }`))).toExist().withTimeout(5000); - await element(by.id(`room-members-view-item-${ username }`)).tap(); - await sleep(300); - await expect(element(by.id('action-sheet'))).toExist(); - await expect(element(by.id('action-sheet-handle'))).toBeVisible(); - await element(by.id('action-sheet-handle')).swipe('up'); - } - - const closeActionSheet = async() => { - await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); - } - it('should set/remove as owner', async() => { await openActionSheet(user.username); await element(by.id('action-sheet-set-owner')).tap(); @@ -512,9 +507,9 @@ describe('Room actions screen', () => { await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); }); }); - }) + }); - describe('Direct', async() => { + describe('Direct', () => { before(async() => { await navigateToRoomActions('d'); }); diff --git a/e2e/tests/room/04-discussion.spec.js b/e2e/tests/room/04-discussion.spec.js index 2154a08bd..25f03d410 100644 --- a/e2e/tests/room/04-discussion.spec.js +++ b/e2e/tests/room/04-discussion.spec.js @@ -1,7 +1,6 @@ const { - expect, element, by, waitFor -} = require('detox'); -const { navigateToLogin, login, mockMessage, tapBack, searchRoom } = require('../../helpers/app'); + navigateToLogin, login, mockMessage, tapBack, searchRoom +} = require('../../helpers/app'); const data = require('../../data'); const platformTypes = require('../../helpers/platformTypes'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -12,7 +11,7 @@ const navigateToRoom = async() => { await searchRoom(channel); await element(by.id(`rooms-list-view-item-${ channel }`)).tap(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); -} +}; describe('Discussion', () => { let scrollViewType; @@ -26,18 +25,18 @@ describe('Discussion', () => { }); it('should create discussion from NewMessageView', async() => { - const discussionName = `${data.random} Discussion NewMessageView`; + const discussionName = `${ data.random } Discussion NewMessageView`; await element(by.id('rooms-list-view-create-channel')).tap(); await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(2000); await element(by.label('Create Discussion')).atIndex(0).tap(); await waitFor(element(by.id('create-discussion-view'))).toExist().withTimeout(60000); await expect(element(by.id('create-discussion-view'))).toExist(); await element(by.text('Select a Channel...')).tap(); - await element(by.id('multi-select-search')).replaceText(`${channel}`); - await waitFor(element(by.id(`multi-select-item-${channel}`))).toExist().withTimeout(10000); - await element(by.id(`multi-select-item-${channel}`)).tap(); + await element(by.id('multi-select-search')).replaceText(`${ channel }`); + await waitFor(element(by.id(`multi-select-item-${ channel }`))).toExist().withTimeout(10000); + await element(by.id(`multi-select-item-${ channel }`)).tap(); await element(by.id('multi-select-discussion-name')).replaceText(discussionName); - await waitFor(element(by.id(`create-discussion-submit`))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-discussion-submit'))).toExist().withTimeout(10000); await element(by.id('create-discussion-submit')).tap(); await waitFor(element(by.id('room-view'))).toExist().withTimeout(10000); await waitFor(element(by.id(`room-view-title-${ discussionName }`))).toExist().withTimeout(5000); @@ -46,20 +45,20 @@ describe('Discussion', () => { }); it('should create discussion from action button', async() => { - const discussionName = `${data.random} Discussion Action Button`; + const discussionName = `${ data.random } Discussion Action Button`; await navigateToRoom(); await element(by.id('messagebox-actions')).tap(); await waitFor(element(by.id('action-sheet'))).toExist().withTimeout(2000); await element(by.text('Create Discussion')).atIndex(0).tap(); await waitFor(element(by.id('create-discussion-view'))).toExist().withTimeout(2000); await element(by.id('multi-select-discussion-name')).replaceText(discussionName); - await waitFor(element(by.id(`create-discussion-submit`))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-discussion-submit'))).toExist().withTimeout(10000); await element(by.id('create-discussion-submit')).tap(); await waitFor(element(by.id('room-view'))).toExist().withTimeout(10000); await waitFor(element(by.id(`room-view-title-${ discussionName }`))).toExist().withTimeout(5000); }); - describe('Create Discussion from action sheet', async() => { + describe('Create Discussion from action sheet', () => { it('should send a message', async() => { await waitFor(element(by.id('messagebox'))).toBeVisible().withTimeout(60000); await mockMessage('message'); @@ -76,8 +75,8 @@ describe('Discussion', () => { await waitFor(element(by.id(`room-view-title-${ discussionName }`))).toExist().withTimeout(5000); }); }); - - describe('Check RoomActionsView render', async() => { + + describe('Check RoomActionsView render', () => { it('should navigete to RoomActionsView', async() => { await waitFor(element(by.id('room-header'))).toBeVisible().withTimeout(5000); await element(by.id('room-header')).tap(); diff --git a/e2e/tests/room/05-threads.spec.js b/e2e/tests/room/05-threads.spec.js index 1d8d7a15a..20adca31d 100644 --- a/e2e/tests/room/05-threads.spec.js +++ b/e2e/tests/room/05-threads.spec.js @@ -1,8 +1,7 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, starMessage, pinMessage, dismissReviewNag, tryTapping, mockMessageWithNag } = require('../../helpers/app'); +const { + navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, mockMessageWithNag +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); async function navigateToRoom(roomName) { @@ -22,14 +21,14 @@ describe('Threads', () => { await navigateToRoom(mainRoom); }); - describe('Render', async() => { + describe('Render', () => { it('should have room screen', async() => { await expect(element(by.id('room-view'))).toExist(); await waitFor(element(by.id(`room-view-title-${ mainRoom }`))).toExist().withTimeout(5000); }); // Render - Header - describe('Header', async() => { + describe('Header', () => { it('should have actions button ', async() => { await expect(element(by.id('room-header'))).toExist(); }); @@ -40,7 +39,7 @@ describe('Threads', () => { }); // Render - Messagebox - describe('Messagebox', async() => { + describe('Messagebox', () => { it('should have messagebox', async() => { await expect(element(by.id('messagebox'))).toExist(); }); @@ -65,8 +64,8 @@ describe('Threads', () => { }); }); - describe('Usage', async() => { - describe('Thread', async() => { + describe('Usage', () => { + describe('Thread', () => { const thread = `${ data.random }thread`; it('should create thread', async() => { await mockMessage('thread'); @@ -106,9 +105,9 @@ describe('Threads', () => { const messageText = 'threadonly'; await mockMessage(messageText, true); await tapBack(); - await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('room-header').and(by.text(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); - await sleep(500) //TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( + await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); + await sleep(500); // TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( await waitFor(element(by.text(`${ data.random }${ messageText }`)).atIndex(0)).toNotExist().withTimeout(2000); }); @@ -119,9 +118,9 @@ describe('Threads', () => { await element(by.id('messagebox-send-to-channel')).tap(); await element(by.id('messagebox-send-message')).tap(); await tapBack(); - await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('room-header').and(by.text(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); - await sleep(500) //TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( + await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); + await sleep(500); // TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( await waitFor(element(by.label(messageText)).atIndex(0)).toExist().withTimeout(2000); }); @@ -133,9 +132,9 @@ describe('Threads', () => { await element(by.id('messagebox-send-to-channel')).tap(); await element(by.id('messagebox-send-message')).tap(); await tapBack(); - await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('room-header').and(by.text(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); - await waitFor(element(by.id(`message-thread-replied-on-${ thread }`))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); + await waitFor(element(by.id(`message-thread-replied-on-${ thread }`))).toBeVisible().withTimeout(2000); await element(by.id(`message-thread-replied-on-${ thread }`)).tap(); await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); await expect(element(by.id(`room-view-title-${ thread }`))).toExist(); @@ -156,7 +155,7 @@ describe('Threads', () => { await tapBack(); }); - it('should draft thread message', async () => { + it('should draft thread message', async() => { await element(by.id(`message-thread-button-${ thread }`)).tap(); await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); await element(by.id('messagebox-input-thread')).typeText(`${ thread }draft`); diff --git a/e2e/tests/room/06-createdmgroup.spec.js b/e2e/tests/room/06-createdmgroup.spec.js index 799d1ee7c..cff2416f1 100644 --- a/e2e/tests/room/06-createdmgroup.spec.js +++ b/e2e/tests/room/06-createdmgroup.spec.js @@ -1,12 +1,10 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { tapBack, sleep, navigateToLogin, login, tryTapping } = require('../../helpers/app'); +const { + navigateToLogin, login +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); - describe('Group DM', () => { before(async() => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); @@ -15,41 +13,39 @@ describe('Group DM', () => { await login(data.users.regular.username, data.users.regular.password); }); - describe('Create Group DM', async() => { + describe('Create Group DM', () => { before(async() => { await element(by.id('rooms-list-view-create-channel')).tap(); }); - describe('Render', async() => { + describe('Render', () => { it('should have new message screen', async() => { await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); }); - + it('should have search input', async() => { await waitFor(element(by.id('new-message-view-search'))).toBeVisible().withTimeout(2000); }); - }) + }); - describe('Usage', async() => { + describe('Usage', () => { it('should navigate to create DM', async() => { await element(by.label('Create Direct Messages')).tap(); }); it('should add 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 waitFor(element(by.id('select-users-view-item-rocket.cat'))).toBeVisible().withTimeout(10000); await element(by.id('select-users-view-item-rocket.cat')).tap(); await element(by.id('select-users-view-search')).replaceText(data.users.existing.username); - await waitFor(element(by.id(`select-users-view-item-${data.users.existing.username}`))).toBeVisible().withTimeout(10000); - await element(by.id(`select-users-view-item-${data.users.existing.username}`)).tap(); + await waitFor(element(by.id(`select-users-view-item-${ data.users.existing.username }`))).toBeVisible().withTimeout(10000); + await element(by.id(`select-users-view-item-${ data.users.existing.username }`)).tap(); await element(by.id('selected-users-view-submit')).tap(); }); it('check Group DM exist', async() => { - await waitFor(element(by.id(`room-view-title-${data.users.existing.username}, rocket.cat`))).toExist().withTimeout(10000); + await waitFor(element(by.id(`room-view-title-${ data.users.existing.username }, rocket.cat`))).toExist().withTimeout(10000); }); - - - }) + }); }); }); diff --git a/e2e/tests/room/07-markasunread.spec.js b/e2e/tests/room/07-markasunread.spec.js index 2d00ba403..89a673d14 100644 --- a/e2e/tests/room/07-markasunread.spec.js +++ b/e2e/tests/room/07-markasunread.spec.js @@ -1,8 +1,7 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, searchRoom, sleep } = require('../../helpers/app'); +const { + navigateToLogin, login, searchRoom, sleep +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); const { sendMessage } = require('../../helpers/data_setup') @@ -13,7 +12,7 @@ async function navigateToRoom(user) { } describe('Mark as unread', () => { - const user = data.users.alternate.username + const user = data.users.alternate.username; before(async() => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); @@ -23,9 +22,8 @@ describe('Mark as unread', () => { await navigateToRoom(user); }); - // TODO: Fix flakiness. If it fails, run it solo. - describe('Usage', async() => { - describe('Mark message as unread', async() => { + describe('Usage', () => { + describe('Mark message as unread', () => { it('should mark message as unread', async() => { const message = `${ data.random }message-mark-as-unread`; const channelName = `@${ data.users.regular.username }`; @@ -37,7 +35,7 @@ describe('Mark as unread', () => { await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.label('Mark Unread')).atIndex(0).tap(); await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(5000); - await expect(element(by.id(`rooms-list-view-item-${data.users.alternate.username}`))).toExist(); + await expect(element(by.id(`rooms-list-view-item-${ data.users.alternate.username }`))).toExist(); }); }); }); diff --git a/e2e/tests/room/08-roominfo.spec.js b/e2e/tests/room/08-roominfo.spec.js index 2ac4a211b..fc4937310 100644 --- a/e2e/tests/room/08-roominfo.spec.js +++ b/e2e/tests/room/08-roominfo.spec.js @@ -1,11 +1,10 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app'); +const { + navigateToLogin, login, tapBack, sleep, searchRoom +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); -const privateRoomName = data.groups.private.name +const privateRoomName = data.groups.private.name; async function navigateToRoomInfo(type) { let room; @@ -32,7 +31,6 @@ async function waitForToast() { } describe('Room info screen', () => { - before(async() => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await prepareAndroid(); @@ -40,7 +38,7 @@ describe('Room info screen', () => { await login(data.users.regular.username, data.users.regular.password); }); - describe('Direct', async() => { + describe('Direct', () => { before(async() => { await navigateToRoomInfo('d'); }); @@ -51,107 +49,107 @@ describe('Room info screen', () => { }); after(async() => { - await tapBack() - await tapBack() - await tapBack() - }) + await tapBack(); + await tapBack(); + await tapBack(); + }); }); - describe('Channel/Group', async() => { + describe('Channel/Group', () => { before(async() => { await navigateToRoomInfo('c'); }); - describe('Render', async() => { + describe('Render', () => { it('should have room info view', async() => { await expect(element(by.id('room-info-view'))).toExist(); }); - + it('should have name', async() => { await expect(element(by.id('room-info-view-name'))).toExist(); }); - + it('should have description', async() => { await expect(element(by.label('Description'))).toExist(); }); - + it('should have topic', async() => { await expect(element(by.label('Topic'))).toExist(); }); - + it('should have announcement', async() => { await expect(element(by.label('Announcement'))).toExist(); }); - + it('should have edit button', async() => { await expect(element(by.id('room-info-view-edit-button'))).toExist(); }); }); - describe('Render Edit', async() => { + describe('Render Edit', () => { before(async() => { await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); await waitFor(element(by.id('room-info-edit-view'))).toExist().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'))).toExist(); }); - + it('should have description input', async() => { await expect(element(by.id('room-info-edit-view-description'))).toExist(); }); - + it('should have topic input', async() => { await expect(element(by.id('room-info-edit-view-topic'))).toExist(); }); - + it('should have announcement input', async() => { await expect(element(by.id('room-info-edit-view-announcement'))).toExist(); }); - + it('should have password input', async() => { await expect(element(by.id('room-info-edit-view-password'))).toExist(); }); - + it('should have type switch', async() => { // Ugly hack to scroll on detox await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.8); await expect(element(by.id('room-info-edit-view-t'))).toExist(); }); - + it('should have ready only switch', async() => { await expect(element(by.id('room-info-edit-view-ro'))).toExist(); }); - + it('should have submit button', async() => { await expect(element(by.id('room-info-edit-view-submit'))).toExist(); }); - + it('should have reset button', async() => { await expect(element(by.id('room-info-edit-view-reset'))).toExist(); }); - + it('should have archive button', async() => { await expect(element(by.id('room-info-edit-view-archive'))).toExist(); }); - + it('should have delete button', async() => { await expect(element(by.id('room-info-edit-view-delete'))).toExist(); }); - + after(async() => { // Ugly hack to scroll on detox await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); }); }); - - describe('Usage', async() => { + + describe('Usage', () => { // it('should enter "invalid name" and get error', async() => { // await element(by.type('UIScrollView')).atIndex(1).swipe('down'); // await element(by.id('room-info-edit-view-name')).replaceText('invalid name'); @@ -163,7 +161,7 @@ describe('Room info screen', () => { // await waitFor(element(by.text('There was an error while saving settings!'))).toBeNotVisible().withTimeout(10000); // await element(by.type('UIScrollView')).atIndex(1).swipe('down'); // }); - + it('should change room name', async() => { await element(by.id('room-info-edit-view-name')).replaceText(`${ privateRoomName }new`); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); @@ -181,7 +179,7 @@ describe('Room info screen', () => { await waitForToast(); await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); }); - + 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'); @@ -190,7 +188,7 @@ describe('Room info screen', () => { await element(by.id('room-info-edit-view-password')).replaceText('abc'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.3); await element(by.id('room-info-edit-view-t')).tap(); - await element(by.id('room-info-edit-view-ro')).longPress(); //https://github.com/facebook/react-native/issues/28032 + await element(by.id('room-info-edit-view-ro')).longPress(); // https://github.com/facebook/react-native/issues/28032 await element(by.id('room-info-edit-view-react-when-ro')).tap(); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.2); await element(by.id('room-info-edit-view-reset')).tap(); @@ -206,7 +204,7 @@ describe('Room info screen', () => { await expect(element(by.id('room-info-edit-view-react-when-ro'))).toBeNotVisible(); await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); }); - + 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', 'fast', 0.5); @@ -216,7 +214,7 @@ describe('Room info screen', () => { await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); await expect(element(by.text('new description').withAncestor(by.id('room-info-view-description')))).toExist(); }); - + it('should change room topic', async() => { await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); @@ -229,7 +227,7 @@ describe('Room info screen', () => { await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); await expect(element(by.text('new topic').withAncestor(by.id('room-info-view-topic')))).toExist(); }); - + it('should change room announcement', async() => { await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); @@ -242,7 +240,7 @@ describe('Room info screen', () => { await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); await expect(element(by.text('new announcement').withAncestor(by.id('room-info-view-announcement')))).toExist(); }); - + it('should change room password', async() => { await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); @@ -252,7 +250,7 @@ describe('Room info screen', () => { await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); }); - + it('should change room type', async() => { await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.3); await element(by.id('room-info-edit-view-t')).tap(); @@ -265,7 +263,7 @@ describe('Room info screen', () => { await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); }); - + // it('should change room read only and allow reactions', async() => { // await sleep(1000); // await element(by.type('UIScrollView')).atIndex(1).swipe('up'); @@ -277,7 +275,7 @@ describe('Room info screen', () => { // await waitForToast(); // // TODO: test if it's possible to react // }); - + it('should archive room', async() => { await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-archive')).tap(); diff --git a/e2e/tests/room/09-jumptomessage.spec.js b/e2e/tests/room/09-jumptomessage.spec.js index 6017e39f9..19b2ac854 100644 --- a/e2e/tests/room/09-jumptomessage.spec.js +++ b/e2e/tests/room/09-jumptomessage.spec.js @@ -1,8 +1,7 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, mockMessage, tapBack, login, sleep, searchRoom } = require('../../helpers/app'); +const { + navigateToLogin, tapBack, login, searchRoom +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); async function navigateToRoom(roomName) { @@ -21,9 +20,9 @@ async function clearCache() { await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); await element(by.id('settings-view-clear-cache')).tap(); await waitFor(element(by.text('This will clear all your offline data.'))).toExist().withTimeout(2000); - await element(by.label('Clear').and(by.type('_UIAlertControllerActionView'))).tap(); + await element(by.label('Clear').and(by.type('_UIAlertControllerActionView'))).tap(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id(`rooms-list-view-item-jumping`))).toExist().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view-item-jumping'))).toExist().withTimeout(10000); } async function waitForLoading() { @@ -53,7 +52,7 @@ describe('Room', () => { await element(by.id('nav-jump-to-bottom')).tap(); await waitFor(element(by.label('Quote first message'))).toExist().withTimeout(5000); await clearCache(); - }) + }); it('should load messages on scroll', async() => { await navigateToRoom('jumping'); @@ -66,6 +65,7 @@ describe('Room', () => { await expect(element(by.label('249'))).toExist(); found = true; } catch { + // } } await clearCache(); @@ -82,7 +82,7 @@ describe('Room', () => { await expect(element(by.label('30'))).toExist(); await expect(element(by.label('31'))).toExist(); await expect(element(by.label('32'))).toExist(); - }) + }); it('should load newer and older messages', async() => { await element(by.id('room-view-messages')).atIndex(0).swipe('down', 'fast', 0.8); @@ -116,9 +116,9 @@ describe('Room', () => { const expectThreadMessages = async(message) => { await waitFor(element(by.id('room-view-title-jumping-thread'))).toExist().withTimeout(5000); await expect(element(by.label(message))).toExist(); -} +}; -describe('Threads', async() => { +describe('Threads', () => { it('should navigate to a thread from another room', async() => { await navigateToRoom('jumping'); await waitFor(element(by.label('Go to jumping-thread\'s thread')).atIndex(0)).toExist().withTimeout(5000); @@ -152,5 +152,5 @@ describe('Threads', async() => { await expectThreadMessages('to be searched'); }); - //TODO: Threads pagination -}); \ No newline at end of file + // TODO: Threads pagination +}); diff --git a/e2e/tests/team/01-createteam.spec.js b/e2e/tests/team/01-createteam.spec.js index b278966fd..d5bf8f21e 100644 --- a/e2e/tests/team/01-createteam.spec.js +++ b/e2e/tests/team/01-createteam.spec.js @@ -1,6 +1,3 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); const { navigateToLogin, login } = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); @@ -15,7 +12,7 @@ describe('Create team screen', () => { await login(data.users.regular.username, data.users.regular.password); }); - describe('New Message', async() => { + describe('New Message', () => { before(async() => { await element(by.id('rooms-list-view-create-channel')).tap(); }); @@ -30,17 +27,17 @@ describe('Create team screen', () => { }); }); - describe('Select Users', async() => { + describe('Select Users', () => { it('should nav to create team', async() => { await element(by.id('selected-users-view-submit')).tap(); await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); }); - }) + }); - describe('Create Team', async() => { - describe('Usage', async() => { + describe('Create Team', () => { + describe('Usage', () => { it('should get invalid team name', async() => { - await element(by.id('create-channel-name')).typeText(`${data.teams.private.name}`); + await element(by.id('create-channel-name')).typeText(`${ data.teams.private.name }`); await element(by.id('create-channel-submit')).tap(); await element(by.text('OK')).tap(); }); @@ -54,10 +51,10 @@ describe('Create team screen', () => { await waitFor(element(by.id(`room-view-title-${ teamName }`))).toExist().withTimeout(6000); await expect(element(by.id(`room-view-title-${ teamName }`))).toExist(); }); - }) + }); }); - describe('Delete Team', async() => { + describe('Delete Team', () => { it('should navigate to room info edit view', async() => { await element(by.id('room-header')).tap(); await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); diff --git a/e2e/tests/team/02-team.spec.js b/e2e/tests/team/02-team.spec.js index 1338ebe04..dfab20659 100644 --- a/e2e/tests/team/02-team.spec.js +++ b/e2e/tests/team/02-team.spec.js @@ -1,8 +1,7 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app'); +const { + navigateToLogin, login, tapBack, sleep, searchRoom +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); async function navigateToRoom(roomName) { @@ -30,7 +29,7 @@ async function backToActions() { await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); } async function closeActionSheet() { - await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); + await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); } async function waitForToast() { @@ -51,13 +50,13 @@ describe('Team', () => { await navigateToRoom(team); }); - describe('Team Room', async() => { - describe('Team Header', async() => { + describe('Team Room', () => { + describe('Team Header', () => { it('should have actions button ', async() => { await expect(element(by.id('room-header'))).toExist(); }); - - it('should have team channels button ', async() => { + + it('should have team channels button ', async() => { await expect(element(by.id('room-view-header-team-channels'))).toExist(); }); @@ -71,19 +70,19 @@ describe('Team', () => { }); }); - describe('Team Header Usage', async() => { + describe('Team Header Usage', () => { it('should navigate to team channels view', async() => { await element(by.id('room-view-header-team-channels')).tap(); await waitFor(element(by.id('team-channels-view'))).toExist().withTimeout(5000); }); - }) + }); - describe('Team Channels Header', async() => { + describe('Team Channels Header', () => { it('should have actions button ', async() => { await expect(element(by.id('room-header'))).toExist(); }); - - it('should have team channels button ', async() => { + + it('should have team channels button ', async() => { await expect(element(by.id('team-channels-view-create'))).toExist(); }); @@ -92,10 +91,10 @@ describe('Team', () => { }); }); - describe('Team Channels Header Usage', async() => { + describe('Team Channels Header Usage', () => { it('should navigate to add team channels view', async() => { await element(by.id('team-channels-view-create')).tap(); - await waitFor(element(by.id('add-channel-team-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('add-channel-team-view'))).toExist().withTimeout(5000); }); it('should have create new button', async() => { @@ -105,11 +104,10 @@ describe('Team', () => { it('should add existing button', async() => { await waitFor(element(by.id('add-channel-team-view-add-existing'))).toExist().withTimeout(5000); }); - }) + }); - describe('Channels', async() => { + describe('Channels', () => { it('should create new channel for team', async() => { - await element(by.id('add-channel-team-view-create-channel')).tap(); await element(by.id('select-users-view-search')).replaceText('rocket.cat'); @@ -140,12 +138,11 @@ describe('Team', () => { }); it('should add existing channel to team', async() => { - await element(by.id('team-channels-view-create')).tap(); await waitFor(element(by.id('add-channel-team-view'))).toExist().withTimeout(5000); await element(by.id('add-channel-team-view-add-existing')).tap(); - await waitFor(element(by.id('add-existing-channel-view'))).toExist().withTimeout(60000) + await waitFor(element(by.id('add-existing-channel-view'))).toExist().withTimeout(60000); await expect(element(by.id(`add-existing-channel-view-item-${ existingRoom }`))).toExist(); await element(by.id(`add-existing-channel-view-item-${ existingRoom }`)).tap(); await waitFor(element(by.id('add-existing-channel-view-submit'))).toExist().withTimeout(6000); @@ -176,14 +173,14 @@ describe('Team', () => { await waitFor(element(by.id('auto-join-tag'))).toBeNotVisible().withTimeout(5000); await waitFor(element(by.id(`rooms-list-view-item-${ existingRoom }`))).toExist().withTimeout(6000); }); - }) + }); - describe('Team actions', () => { + describe('Team actions', () => { before(async() => { await tapBack(); await navigateToRoomActions(); }); - + it('should add users to the team', async() => { await waitFor(element(by.id('room-actions-add-user'))).toExist().withTimeout(10000); await element(by.id('room-actions-add-user')).tap(); @@ -216,9 +213,9 @@ describe('Team', () => { await element(by.id('room-actions-leave-channel')).tap(); await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${room}`))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${existingRoom}`))).toExist().withTimeout(2000); - await element(by.id(`select-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`select-list-view-item-${ room }`))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${ existingRoom }`))).toExist().withTimeout(2000); + await element(by.id(`select-list-view-item-${ room }`)).tap(); await waitFor(element(by.label('You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'))).toExist().withTimeout(2000); await element(by.text('OK')).tap(); @@ -229,8 +226,8 @@ describe('Team', () => { await tapBack(); await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); }); - - describe('Room Members', async() => { + + describe('Room Members', () => { before(async() => { await element(by.id('room-actions-members')).tap(); await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000); @@ -280,9 +277,9 @@ describe('Team', () => { await element(by.id('room-actions-leave-channel')).tap(); await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${room}`))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${existingRoom}`))).toExist().withTimeout(2000); - await element(by.id(`select-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`select-list-view-item-${ room }`))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${ existingRoom }`))).toExist().withTimeout(2000); + await element(by.id(`select-list-view-item-${ room }`)).tap(); await waitFor(element(by.label('You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'))).toExist().withTimeout(2000); await element(by.text('OK')).tap(); @@ -293,4 +290,4 @@ describe('Team', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/e2e/tests/team/03-moveconvert.spec.js b/e2e/tests/team/03-moveconvert.spec.js index ee709e86c..9b2b11396 100644 --- a/e2e/tests/team/03-moveconvert.spec.js +++ b/e2e/tests/team/03-moveconvert.spec.js @@ -1,8 +1,7 @@ -const { - device, expect, element, by, waitFor -} = require('detox'); const data = require('../../data'); -const { navigateToLogin, login, tapBack, searchRoom, sleep } = require('../../helpers/app'); +const { + navigateToLogin, login, tapBack, searchRoom, sleep +} = require('../../helpers/app'); const { prepareAndroid } = require('../../helpers/platformFunctions'); const toBeConverted = `to-be-converted-${ data.random }`; @@ -22,7 +21,7 @@ const createChannel = async(room) => { await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); -} +}; async function navigateToRoom(room) { await searchRoom(`${ room }`); @@ -44,7 +43,7 @@ describe('Move/Convert Team', () => { await login(data.users.regular.username, data.users.regular.password); }); - describe('Convert', async() => { + describe('Convert', () => { before(async() => { await createChannel(toBeConverted); }); @@ -54,7 +53,7 @@ describe('Move/Convert Team', () => { await element(by.id('room-actions-scrollview')).scrollTo('bottom'); await waitFor(element(by.id('room-actions-convert-to-team'))).toExist().withTimeout(2000); await element(by.id('room-actions-convert-to-team')).tap(); - await waitFor(element(by.label('This can\'t be undone. Once you convert a channel to a team, you can not turn it back to a channel.'))).toExist().withTimeout(2000); + await waitFor(element(by.label('You are converting this Channel to a Team. All Members will be kept.'))).toExist().withTimeout(2000); await element(by.text('Convert')).tap(); await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); await waitFor(element(by.id(`room-view-title-${ toBeConverted }`))).toExist().withTimeout(6000); @@ -63,10 +62,10 @@ describe('Move/Convert Team', () => { after(async() => { await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); - }) + }); }); - describe('Move', async() => { + describe('Move', () => { before(async() => { await createChannel(toBeMoved); }); @@ -80,12 +79,39 @@ describe('Move/Convert Team', () => { await element(by.id('select-list-view-submit')).tap(); await sleep(2000); await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${toBeConverted}`))).toExist().withTimeout(2000); - await element(by.id(`select-list-view-item-${toBeConverted}`)).tap(); + await waitFor(element(by.id(`select-list-view-item-${ toBeConverted }`))).toExist().withTimeout(2000); + await element(by.id(`select-list-view-item-${ toBeConverted }`)).tap(); await element(by.id('select-list-view-submit')).atIndex(0).tap(); await waitFor(element(by.label('After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?'))).toExist().withTimeout(2000); await element(by.text('Yes, move it!')).tap(); await waitFor(element(by.id('room-view-header-team-channels'))).toExist().withTimeout(10000); }); - }) + + after(async() => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + }); + }); + + describe('Convert Team to Channel and Delete toBeMoved channel within the Converted', () => { + it('should convert a team to a channel', async() => { + await navigateToRoomActions(toBeConverted); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-convert-channel-to-team'))).toExist().withTimeout(2000); + await element(by.id('room-actions-convert-channel-to-team')).tap(); + await sleep(2000); + await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${ toBeMoved }`))).toExist().withTimeout(2000); + await element(by.id(`select-list-view-item-${ toBeMoved }`)).tap(); + await waitFor(element(by.id('select-list-view-submit'))).toExist().withTimeout(2000); + await element(by.id('select-list-view-submit')).tap(); + await waitFor(element(by.label('You are converting this Team to a Channel'))).toExist().withTimeout(2000); + await element(by.text('Convert')).tap(); + await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); + await waitFor(element(by.id(`room-view-title-${ toBeConverted }`))).toExist().withTimeout(6000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-${ toBeMoved }`))).toBeNotVisible().withTimeout(60000); + }); + }); }); diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index bbebd2ee7..2c24aa093 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -1694,7 +1694,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.17.0; + MARKETING_VERSION = 4.18.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; @@ -1731,7 +1731,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.17.0; + MARKETING_VERSION = 4.18.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index 4357a8abb..bc30c1441 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.17.0 + 4.18.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index e38798d73..5db7e5aef 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -21,7 +21,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.17.0 + 4.18.0 CFBundleVersion 1 KeychainGroup diff --git a/package.json b/package.json index f4abc29b8..b2eecc6c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rocket-chat-reactnative", - "version": "4.17.0", + "version": "4.18.0", "private": true, "scripts": { "start": "react-native start", diff --git a/patches/react-native+0.63.4.patch b/patches/react-native+0.63.4.patch new file mode 100644 index 000000000..efadfe80b --- /dev/null +++ b/patches/react-native+0.63.4.patch @@ -0,0 +1,55 @@ +diff --git a/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js b/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js +index c344ac4..479c8c0 100644 +--- a/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js ++++ b/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js +@@ -1244,9 +1244,17 @@ class ScrollView extends React.Component { + // Note: we should split props.style on the inner and outer props + // however, the ScrollView still needs the baseStyle to be scrollable + const {outer, inner} = splitLayoutProps(flattenStyle(props.style)); ++ ++ // Workaround for RefreshControl inverted: https://github.com/facebook/react-native/issues/30034 ++ let inverted; ++ if (inner.scaleY) { ++ inverted = { scaleY: -1 }; ++ delete inner.scaleY; ++ } ++ + return React.cloneElement( + refreshControl, +- {style: [baseStyle, outer]}, ++ {style: [baseStyle, outer, inverted]}, +