From b332bfecd4dbf3d33ff310b4c943ad0bf1b8970c Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 25 Jan 2023 16:03:02 -0300 Subject: [PATCH] Re-add previous tests --- e2e/data.ts | 101 +++ e2e/data/data.cloud.ts | 96 +++ e2e/data/data.docker.ts | 101 +++ e2e/e2e_account.example.ts | 6 + e2e/helpers/app.ts | 248 +++++++ e2e/helpers/data_setup.ts | 193 +++++ e2e/helpers/random.ts | 10 + e2e/jest.config.js | 3 +- e2e/starter.test.js | 26 - e2e/tests/assorted/01-e2eencryption.spec.ts | 401 +++++++++++ e2e/tests/assorted/02-broadcast.spec.ts | 141 ++++ e2e/tests/assorted/03-profile.spec.ts | 130 ++++ e2e/tests/assorted/04-setting.spec.ts | 92 +++ e2e/tests/assorted/05-joinpublicroom.spec.ts | 170 +++++ e2e/tests/assorted/06-status.spec.ts | 65 ++ e2e/tests/assorted/07-changeserver.spec.ts | 101 +++ .../assorted/08-joinprotectedroom.spec.ts | 73 ++ .../assorted/09-joinfromdirectory.spec.ts | 86 +++ e2e/tests/assorted/10-deleteserver.spec.ts | 67 ++ e2e/tests/assorted/11-deeplinking.spec.ts | 218 ++++++ e2e/tests/assorted/12-i18n.spec.ts | 140 ++++ e2e/tests/assorted/13-display-pref.spec.ts | 102 +++ .../assorted/14-in-app-notification.spec.ts | 72 ++ e2e/tests/init.ts | 29 + e2e/tests/onboarding/01-onboarding.spec.ts | 56 ++ e2e/tests/onboarding/02-legal.spec.ts | 71 ++ .../onboarding/03-forgotpassword.spec.ts | 46 ++ e2e/tests/onboarding/04-createuser.spec.ts | 91 +++ e2e/tests/onboarding/05-login.spec.ts | 88 +++ e2e/tests/onboarding/06-roomslist.spec.ts | 59 ++ .../onboarding/07-server-history.spec.ts | 56 ++ e2e/tests/room/01-createroom.spec.ts | 257 +++++++ e2e/tests/room/02-room.spec.ts | 536 ++++++++++++++ e2e/tests/room/03-roomactions.spec.ts | 659 ++++++++++++++++++ e2e/tests/room/04-discussion.spec.ts | 225 ++++++ e2e/tests/room/05-threads.spec.ts | 240 +++++++ e2e/tests/room/06-createdmgroup.spec.ts | 61 ++ e2e/tests/room/07-markasunread.spec.ts | 50 ++ e2e/tests/room/08-roominfo.spec.ts | 324 +++++++++ e2e/tests/room/09-jumptomessage.spec.ts | 266 +++++++ e2e/tests/room/10-ignoreuser.spec.ts | 104 +++ e2e/tests/team/01-createteam.spec.ts | 110 +++ e2e/tests/team/02-team.spec.ts | 491 +++++++++++++ e2e/tests/team/03-moveconvert.spec.ts | 189 +++++ e2e/tsconfig.json | 23 + 45 files changed, 6646 insertions(+), 27 deletions(-) create mode 100644 e2e/data.ts create mode 100644 e2e/data/data.cloud.ts create mode 100644 e2e/data/data.docker.ts create mode 100644 e2e/e2e_account.example.ts create mode 100644 e2e/helpers/app.ts create mode 100644 e2e/helpers/data_setup.ts create mode 100644 e2e/helpers/random.ts delete mode 100644 e2e/starter.test.js create mode 100644 e2e/tests/assorted/01-e2eencryption.spec.ts create mode 100644 e2e/tests/assorted/02-broadcast.spec.ts create mode 100644 e2e/tests/assorted/03-profile.spec.ts create mode 100644 e2e/tests/assorted/04-setting.spec.ts create mode 100644 e2e/tests/assorted/05-joinpublicroom.spec.ts create mode 100644 e2e/tests/assorted/06-status.spec.ts create mode 100644 e2e/tests/assorted/07-changeserver.spec.ts create mode 100644 e2e/tests/assorted/08-joinprotectedroom.spec.ts create mode 100644 e2e/tests/assorted/09-joinfromdirectory.spec.ts create mode 100644 e2e/tests/assorted/10-deleteserver.spec.ts create mode 100644 e2e/tests/assorted/11-deeplinking.spec.ts create mode 100644 e2e/tests/assorted/12-i18n.spec.ts create mode 100644 e2e/tests/assorted/13-display-pref.spec.ts create mode 100644 e2e/tests/assorted/14-in-app-notification.spec.ts create mode 100644 e2e/tests/init.ts create mode 100644 e2e/tests/onboarding/01-onboarding.spec.ts create mode 100644 e2e/tests/onboarding/02-legal.spec.ts create mode 100644 e2e/tests/onboarding/03-forgotpassword.spec.ts create mode 100644 e2e/tests/onboarding/04-createuser.spec.ts create mode 100644 e2e/tests/onboarding/05-login.spec.ts create mode 100644 e2e/tests/onboarding/06-roomslist.spec.ts create mode 100644 e2e/tests/onboarding/07-server-history.spec.ts create mode 100644 e2e/tests/room/01-createroom.spec.ts create mode 100644 e2e/tests/room/02-room.spec.ts create mode 100644 e2e/tests/room/03-roomactions.spec.ts create mode 100644 e2e/tests/room/04-discussion.spec.ts create mode 100644 e2e/tests/room/05-threads.spec.ts create mode 100644 e2e/tests/room/06-createdmgroup.spec.ts create mode 100644 e2e/tests/room/07-markasunread.spec.ts create mode 100644 e2e/tests/room/08-roominfo.spec.ts create mode 100644 e2e/tests/room/09-jumptomessage.spec.ts create mode 100644 e2e/tests/room/10-ignoreuser.spec.ts create mode 100644 e2e/tests/team/01-createteam.spec.ts create mode 100644 e2e/tests/team/02-team.spec.ts create mode 100644 e2e/tests/team/03-moveconvert.spec.ts create mode 100644 e2e/tsconfig.json diff --git a/e2e/data.ts b/e2e/data.ts new file mode 100644 index 000000000..bc5a1ae70 --- /dev/null +++ b/e2e/data.ts @@ -0,0 +1,101 @@ +/* eslint-disable import/extensions, import/no-unresolved */ +import random from './helpers/random'; +// @ts-ignore +import account from './e2e_account'; + +export interface IUser { + username: string; + password: string; + email: string; +} + +export type TData = typeof data; +export type TDataKeys = keyof TData; +export type TDataUsers = keyof typeof data.users; +export type TDataChannels = keyof typeof data.channels; +export type TUserRegularChannels = keyof typeof data.userRegularChannels; +export type TDataGroups = keyof typeof data.groups; +export type TDataTeams = keyof typeof data.teams; + +const value: string = random(20); +const data = { + server: 'https://mobile.rocket.chat', + ...account, + 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' + } + }, + userRegularChannels: { + detoxpublic: { + name: `detox-public-${value}` + } + }, + groups: { + private: { + name: `detox-private-${value}` + }, + alternate: { + name: `detox-alternate-${value}` + }, + alternate2: { + name: `detox-alternate2-${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 +}; + +export default data; diff --git a/e2e/data/data.cloud.ts b/e2e/data/data.cloud.ts new file mode 100644 index 000000000..b56997085 --- /dev/null +++ b/e2e/data/data.cloud.ts @@ -0,0 +1,96 @@ +/* eslint-disable import/extensions, import/no-unresolved */ +// @ts-ignore +import random from './helpers/random'; +// @ts-ignore +import account from './e2e_account'; + +export interface IUser { + username: string; + password: string; + email: string; +} + +export type TData = typeof data; +export type TDataKeys = keyof TData; +export type TDataUsers = keyof typeof data.users; +export type TDataChannels = keyof typeof data.channels; +export type TUserRegularChannels = keyof typeof data.userRegularChannels; +export type TDataGroups = keyof typeof data.groups; +export type TDataTeams = keyof typeof data.teams; + +const value = random(20); +const data = { + server: 'https://mobile.rocket.chat', + ...account, + 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' + } + }, + userRegularChannels: { + detoxpublic: { + name: `detox-public-${value}` + } + }, + 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 +}; + +export default data; diff --git a/e2e/data/data.docker.ts b/e2e/data/data.docker.ts new file mode 100644 index 000000000..ad5150d7d --- /dev/null +++ b/e2e/data/data.docker.ts @@ -0,0 +1,101 @@ +/* eslint-disable import/extensions, import/no-unresolved */ +// @ts-ignore +import random from './helpers/random'; + +export interface IUser { + username: string; + password: string; + email: string; +} + +export type TData = typeof data; +export type TDataKeys = keyof TData; +export type TDataUsers = keyof typeof data.users; +export type TDataChannels = keyof typeof data.channels; +export type TUserRegularChannels = keyof typeof data.userRegularChannels; +export type TDataGroups = keyof typeof data.groups; +export type TDataTeams = keyof typeof data.teams; + +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' + } + }, + userRegularChannels: { + detoxpublic: { + name: `detox-public-${value}` + } + }, + groups: { + private: { + name: `detox-private-${value}` + }, + alternate: { + name: `detox-alternate-${value}` + }, + alternate2: { + name: `detox-alternate2-${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 +}; + +export default data; diff --git a/e2e/e2e_account.example.ts b/e2e/e2e_account.example.ts new file mode 100644 index 000000000..cd0ee2c14 --- /dev/null +++ b/e2e/e2e_account.example.ts @@ -0,0 +1,6 @@ +const account = { + adminUser: 'Change_here', + adminPassword: 'Change_here' +}; + +export default account; diff --git a/e2e/helpers/app.ts b/e2e/helpers/app.ts new file mode 100644 index 000000000..2bfda548e --- /dev/null +++ b/e2e/helpers/app.ts @@ -0,0 +1,248 @@ +import { exec } from 'child_process'; + +import { by, expect, element } from 'detox'; + +import data from '../data'; + +export type TTextMatcher = keyof Pick; + +const platformTypes = { + android: { + // Android types + alertButtonType: 'android.widget.Button', + scrollViewType: 'android.widget.ScrollView', + textInputType: 'android.widget.EditText', + textMatcher: 'text' as TTextMatcher + }, + ios: { + // iOS types + alertButtonType: '_UIAlertControllerActionView', + scrollViewType: 'UIScrollView', + textInputType: '_UIAlertControllerTextField', + textMatcher: 'label' as TTextMatcher + } +}; + +function sleep(ms: number) { + return new Promise(res => setTimeout(res, ms)); +} + +async function navigateToWorkspace(server = data.server) { + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(60000); + await element(by.id('new-server-view-input')).replaceText(`${server}`); + await element(by.id('new-server-view-input')).tapReturnKey(); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); + await expect(element(by.id('workspace-view'))).toBeVisible(); +} + +async function navigateToLogin(server?: string) { + await navigateToWorkspace(server); + await element(by.id('workspace-view-login')).tap(); + await waitFor(element(by.id('login-view'))) + .toExist() + .withTimeout(2000); +} + +async function navigateToRegister(server?: string) { + await navigateToWorkspace(server); + await element(by.id('workspace-view-register')).tap(); + await waitFor(element(by.id('register-view'))) + .toExist() + .withTimeout(2000); +} + +async function login(username: string, password: string) { + await waitFor(element(by.id('login-view'))) + .toExist() + .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'))) + .toExist() + .withTimeout(30000); +} + +async function logout() { + const deviceType = device.getPlatform(); + const { scrollViewType, textMatcher } = platformTypes[deviceType]; + 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[textMatcher](logoutAlertMessage)).atIndex(0)) + .toExist() + .withTimeout(10000); + await expect(element(by[textMatcher](logoutAlertMessage)).atIndex(0)).toExist(); + await element(by[textMatcher]('Logout')).atIndex(0).tap(); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(10000); + await expect(element(by.id('new-server-view'))).toBeVisible(); +} + +async function mockMessage(message: string, isThread = false) { + const deviceType = device.getPlatform(); + const { textMatcher } = platformTypes[deviceType]; + const input = isThread ? 'messagebox-input-thread' : 'messagebox-input'; + await element(by.id(input)).replaceText(`${data.random}${message}`); + await sleep(300); + await element(by.id('messagebox-send-message')).tap(); + await waitFor(element(by[textMatcher](`${data.random}${message}`))) + .toExist() + .withTimeout(60000); + await element(by[textMatcher](`${data.random}${message}`)) + .atIndex(0) + .tap(); +} + +async function starMessage(message: string) { + const deviceType = device.getPlatform(); + const { textMatcher } = platformTypes[deviceType]; + const messageLabel = `${data.random}${message}`; + await element(by[textMatcher](messageLabel)).atIndex(0).longPress(); + 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', 'fast', 0.5); + await element(by[textMatcher]('Star')).atIndex(0).tap(); + await waitFor(element(by.id('action-sheet'))) + .not.toExist() + .withTimeout(5000); +} + +async function pinMessage(message: string) { + const deviceType = device.getPlatform(); + const { textMatcher } = platformTypes[deviceType]; + const messageLabel = `${data.random}${message}`; + await waitFor(element(by[textMatcher](messageLabel)).atIndex(0)).toExist(); + await element(by[textMatcher](messageLabel)).atIndex(0).longPress(); + 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', 'fast', 0.5); + await element(by[textMatcher]('Pin')).atIndex(0).tap(); + await waitFor(element(by.id('action-sheet'))) + .not.toExist() + .withTimeout(5000); +} + +async function dismissReviewNag() { + const deviceType = device.getPlatform(); + const { textMatcher } = platformTypes[deviceType]; + await waitFor(element(by[textMatcher]('Are you enjoying this app?'))) + .toExist() + .withTimeout(60000); + await element(by[textMatcher]('No')).atIndex(0).tap(); // Tap `no` on ask for review alert +} + +async function tapBack() { + await element(by.id('header-back')).atIndex(0).tap(); +} + +async function searchRoom(room: string) { + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(30000); + await element(by.id('rooms-list-view-search')).tap(); + await waitFor(element(by.id('rooms-list-view-search-input'))) + .toExist() + .withTimeout(5000); + await expect(element(by.id('rooms-list-view-search-input'))).toExist(); + 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); +} + +// eslint-disable-next-line no-undef +async function tryTapping(theElement: Detox.IndexableNativeElement, timeout: number, 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; + } + await sleep(100); + await tryTapping(theElement, timeout - 100); + } +} + +const checkServer = async (server: string) => { + const label = `Connected to ${server}`; + await element(by.id('rooms-list-view-sidebar')).tap(); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.label(label))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.id('sidebar-close-drawer'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('sidebar-close-drawer')).tap(); + await waitFor(element(by.id('sidebar-close-drawer'))) + .not.toBeVisible() + .withTimeout(10000); +}; + +function runCommand(command: string) { + return new Promise((resolve, reject) => { + exec(command, (error, _stdout, stderr) => { + if (error) { + reject(new Error(`exec error: ${stderr}`)); + return; + } + resolve(); + }); + }); +} + +async function prepareAndroid() { + if (device.getPlatform() !== 'android') { + return; + } + await runCommand('adb shell settings put secure spell_checker_enabled 0'); + await runCommand('adb shell settings put secure autofill_service null'); + await runCommand('adb shell settings put global window_animation_scale 0.0'); + await runCommand('adb shell settings put global transition_animation_scale 0.0'); + await runCommand('adb shell settings put global animator_duration_scale 0.0'); +} + +export { + navigateToWorkspace, + navigateToLogin, + navigateToRegister, + login, + logout, + mockMessage, + starMessage, + pinMessage, + dismissReviewNag, + tapBack, + sleep, + searchRoom, + tryTapping, + checkServer, + platformTypes, + prepareAndroid +}; diff --git a/e2e/helpers/data_setup.ts b/e2e/helpers/data_setup.ts new file mode 100644 index 000000000..2549d51c5 --- /dev/null +++ b/e2e/helpers/data_setup.ts @@ -0,0 +1,193 @@ +import axios from 'axios'; + +import data, { TDataChannels, TDataGroups, TDataTeams, TDataUsers, TUserRegularChannels } from '../data'; +import random from './random'; + +const TEAM_TYPE = { + PUBLIC: 0, + PRIVATE: 1 +}; + +const { server } = data; + +const rocketchat = axios.create({ + baseURL: `${server}/api/v1/`, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } +}); + +const login = async (username: string, password: string) => { + 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: string, password: string, name: string, email: string) => { + 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: string) => { + 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: string) => { + 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: string) => { + 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: string, joinCode: string) => { + console.log(`Changing channel Join Code ${roomId}`); + try { + await rocketchat.post('method.call/saveRoomSettings', { + message: JSON.stringify({ + msg: 'method', + id: random(10), + method: 'saveRoomSettings', + params: [roomId, { joinCode }] + }) + }); + } catch (createError) { + console.log(JSON.stringify(createError)); + throw new Error('Failed to create protected channel'); + } +}; + +const sendMessage = async (user: { username: string; password: string }, channel: string, msg: string, tmid?: string) => { + console.log(`Sending message to ${channel}`); + try { + await login(user.username, user.password); + const response = await rocketchat.post('chat.postMessage', { channel, msg, tmid }); + return response.data; + } 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 (const userKey in data.users) { + if (Object.prototype.hasOwnProperty.call(data.users, userKey)) { + const user = data.users[userKey as TDataUsers]; + await createUser(user.username, user.password, user.username, user.email); + } + } + + for (const channelKey in data.channels) { + if (Object.prototype.hasOwnProperty.call(data.channels, channelKey)) { + const channel = data.channels[channelKey as TDataChannels]; + const { + data: { + channel: { _id } + } + } = await createChannelIfNotExists(channel.name); + + if ('joinCode' in channel) { + await changeChannelJoinCode(_id, channel.joinCode); + } + } + } + + await login(data.users.regular.username, data.users.regular.password); + + for (const channelKey in data.userRegularChannels) { + if (Object.prototype.hasOwnProperty.call(data.userRegularChannels, channelKey)) { + const channel = data.userRegularChannels[channelKey as TUserRegularChannels]; + await createChannelIfNotExists(channel.name); + } + } + + for (const groupKey in data.groups) { + if (Object.prototype.hasOwnProperty.call(data.groups, groupKey)) { + const group = data.groups[groupKey as TDataGroups]; + await createGroupIfNotExists(group.name); + } + } + + for (const teamKey in data.teams) { + if (Object.prototype.hasOwnProperty.call(data.teams, teamKey)) { + const team = data.teams[teamKey as TDataTeams]; + await createTeamIfNotExists(team.name); + } + } +}; + +const get = (endpoint: string) => { + console.log(`GET /${endpoint}`); + return rocketchat.get(endpoint); +}; + +const post = (endpoint: string, body: any) => { + console.log(`POST /${endpoint} ${JSON.stringify(body)}`); + return rocketchat.post(endpoint, body); +}; + +export { setup, sendMessage, get, post, login }; diff --git a/e2e/helpers/random.ts b/e2e/helpers/random.ts new file mode 100644 index 000000000..70a5a90b7 --- /dev/null +++ b/e2e/helpers/random.ts @@ -0,0 +1,10 @@ +function random(length: number) { + let text = ''; + const possible = 'abcdefghijklmnopqrstuvwxyz'; + for (let i = 0; i < length; i += 1) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +export default random; diff --git a/e2e/jest.config.js b/e2e/jest.config.js index 7e42435bc..6add52040 100644 --- a/e2e/jest.config.js +++ b/e2e/jest.config.js @@ -1,7 +1,8 @@ /** @type {import('@jest/types').Config.InitialOptions} */ module.exports = { rootDir: '..', - testMatch: ['/e2e/**/*.test.js'], + setupFilesAfterEnv: ['/e2e/tests/init.ts'], + testMatch: ['/e2e/tests/**/*.spec.ts'], testTimeout: 120000, maxWorkers: 1, globalSetup: 'detox/runners/jest/globalSetup', diff --git a/e2e/starter.test.js b/e2e/starter.test.js deleted file mode 100644 index ebab335ab..000000000 --- a/e2e/starter.test.js +++ /dev/null @@ -1,26 +0,0 @@ -describe('Example', () => { - beforeAll(async () => { - await device.launchApp(); - }); - - // beforeEach(async () => { - // await device.reloadReactNative(); - // }); - - it('should have welcome screen', async () => { - await expect(element(by.id('welcome'))).toBeVisible(); - }); - - it('should have onboarding screen', async () => { - await expect(element(by.id('new-server-view'))).toBeVisible(); - }); - // it('should show hello screen after tap', async () => { - // await element(by.id('hello_button')).tap(); - // await expect(element(by.text('Hello!!!'))).toBeVisible(); - // }); - - // it('should show world screen after tap', async () => { - // await element(by.id('world_button')).tap(); - // await expect(element(by.text('World!!!'))).toBeVisible(); - // }); -}); diff --git a/e2e/tests/assorted/01-e2eencryption.spec.ts b/e2e/tests/assorted/01-e2eencryption.spec.ts new file mode 100644 index 000000000..fb36498aa --- /dev/null +++ b/e2e/tests/assorted/01-e2eencryption.spec.ts @@ -0,0 +1,401 @@ +import { expect } from 'detox'; + +import { + navigateToLogin, + login, + sleep, + tapBack, + mockMessage, + searchRoom, + logout, + platformTypes, + TTextMatcher +} from '../../helpers/app'; +import data from '../../data'; + +const testuser = data.users.regular; +const otheruser = data.users.alternate; + +const checkServer = async (server: string) => { + const label = `Connected to ${server}`; + await element(by.id('rooms-list-view-sidebar')).tap(); + 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 () => { + // TODO: Assert 'Save Your Encryption Password' + await waitFor(element(by.id('listheader-encryption'))) + .toExist() + .withTimeout(10000); +}; + +async function navigateToRoom(roomName: string) { + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); +} + +async function waitForToast() { + await sleep(300); +} + +async function navigateSecurityPrivacy() { + await waitFor(element(by.id('rooms-list-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.id('settings-view-security-privacy')).tap(); + await waitFor(element(by.id('security-privacy-view'))) + .toBeVisible() + .withTimeout(2000); +} + +describe('E2E Encryption', () => { + const room = `encrypted${data.random}`; + const newPassword = 'abc'; + let alertButtonType: string; + let textMatcher: TTextMatcher; + + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(testuser.username, testuser.password); + }); + + describe('Banner', () => { + describe('Render', () => { + it('should have encryption badge', async () => { + await checkBanner(); + }); + }); + + 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); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('select-users-view-search')).replaceText(otheruser.username); + await waitFor(element(by.id(`select-users-view-item-${otheruser.username}`))) + .toBeVisible() + .withTimeout(60000); + await element(by.id(`select-users-view-item-${otheruser.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${otheruser.username}`))) + .toBeVisible() + .withTimeout(5000); + 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(room); + await element(by.id('create-channel-encrypted')).longPress(); + 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-${room}`))) + .toBeVisible() + .withTimeout(60000); + }); + + it('should send message and be able to read it', async () => { + await mockMessage('message'); + await tapBack(); + }); + }); + }); + + 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(); + 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.id('settings-view-security-privacy')).tap(); + await waitFor(element(by.id('security-privacy-view'))) + .toBeVisible() + .withTimeout(2000); + }); + + it('render', async () => { + await expect(element(by.id('security-privacy-view-e2e-encryption'))).toExist(); + await expect(element(by.id('security-privacy-view-screen-lock'))).toExist(); + await expect(element(by.id('security-privacy-view-analytics-events'))).toExist(); + await expect(element(by.id('security-privacy-view-crash-report'))).toExist(); + }); + }); + + 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); + }); + + describe('Render', () => { + it('should have items', async () => { + await waitFor(element(by.id('e2e-encryption-security-view'))) + .toBeVisible() + .withTimeout(2000); + await expect(element(by.id('e2e-encryption-security-view-password'))).toExist(); + 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', () => { + 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(); + await waitFor(element(by[textMatcher]('Are you sure?'))) + .toExist() + .withTimeout(2000); + await expect(element(by[textMatcher]("Make sure you've saved it carefully somewhere else."))).toExist(); + await element(by[textMatcher]('Yes, change it')).atIndex(0).tap(); + await waitForToast(); + }); + + it('should navigate to the room and messages should remain decrypted', async () => { + await waitFor(element(by.id('e2e-encryption-security-view'))) + .toBeVisible() + .withTimeout(2000); + await tapBack(); + await waitFor(element(by.id('security-privacy-view'))) + .toBeVisible() + .withTimeout(2000); + await tapBack(); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('settings-view-drawer')).tap(); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('sidebar-chats')).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + await navigateToRoom(room); + await waitFor(element(by[textMatcher](`${data.random}message`)).atIndex(0)) + .toExist() + .withTimeout(2000); + }); + + it('should logout, login and messages should be encrypted', async () => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + await logout(); + await navigateToLogin(); + await login(testuser.username, testuser.password); + await navigateToRoom(room); + await waitFor(element(by[textMatcher](`${data.random}message`)).atIndex(0)) + .not.toExist() + .withTimeout(2000); + await expect(element(by.label('Encrypted message')).atIndex(0)).toExist(); + }); + + it('should enter new e2e password and messages should be decrypted', async () => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + // TODO: assert 'Enter Your E2E Password' + await waitFor(element(by.id('listheader-encryption'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('listheader-encryption')).tap(); + await waitFor(element(by.id('e2e-enter-your-password-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('e2e-enter-your-password-view-password')).typeText(newPassword); + await element(by.id('e2e-enter-your-password-view-confirm')).tap(); + await waitFor(element(by.id('listheader-encryption'))) + .not.toExist() + .withTimeout(10000); + await navigateToRoom(room); + await waitFor(element(by[textMatcher](`${data.random}message`)).atIndex(0)) + .toExist() + .withTimeout(2000); + }); + }); + + describe('Reset E2E key', () => { + beforeAll(async () => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + }); + it('should reset e2e key', async () => { + // FIXME: too flaky on Android for now... let's fix it later + // It's also flaky on iOS, but it works from time to time + if (device.getPlatform() === 'android') { + return; + } + await navigateSecurityPrivacy(); + await element(by.id('security-privacy-view-e2e-encryption')).tap(); + await waitFor(element(by.id('e2e-encryption-security-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key'))).tap(); + await waitFor(element(by[textMatcher]('Are you sure?'))) + .toExist() + .withTimeout(2000); + await expect(element(by[textMatcher]("You're going to be logged out."))).toExist(); + await element(by[textMatcher]('Yes, reset it').and(by.type(alertButtonType))).tap(); + await sleep(2000); + + // FIXME: The app isn't showing this alert anymore + // await waitFor(element(by[textMatcher]("You've been logged out by the server. Please log in again."))) + // .toExist() + // .withTimeout(20000); + // await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + // await waitFor(element(by.id('workspace-view'))) + // .toBeVisible() + // .withTimeout(10000); + // await element(by.id('workspace-view-login')).tap(); + await navigateToLogin(); + await waitFor(element(by.id('login-view'))) + .toBeVisible() + .withTimeout(2000); + await login(testuser.username, testuser.password); + // TODO: assert 'Save Your Encryption Password' + await waitFor(element(by.id('listheader-encryption'))) + .toBeVisible() + .withTimeout(5000); + }); + }); + }); + + describe('Persist Banner', () => { + beforeAll(async () => { + // reinstall the app because of one flaky test above + if (device.getPlatform() === 'android') { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(testuser.username, testuser.password); + } + }); + it('check save banner', async () => { + 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}`); + await element(by.id('new-server-view-input')).tapReturnKey(); + 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); + await element(by.id('register-view-email')).replaceText(data.registeringUser.email); + await element(by.id('register-view-password')).replaceText(data.registeringUser.password); + await element(by.id('register-view-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); + await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await checkServer(data.server); + await checkBanner(); + }); + + it('should reopen the app and have banner', async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true + }); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await checkBanner(); + }); + }); +}); diff --git a/e2e/tests/assorted/02-broadcast.spec.ts b/e2e/tests/assorted/02-broadcast.spec.ts new file mode 100644 index 000000000..c9ec2da6d --- /dev/null +++ b/e2e/tests/assorted/02-broadcast.spec.ts @@ -0,0 +1,141 @@ +// const OTP = require('otp.js'); +// const GA = OTP.googleAuthenticator; +import { expect } from 'detox'; + +import { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes, TTextMatcher, sleep } from '../../helpers/app'; +import data from '../../data'; + +const testuser = data.users.regular; +const otheruser = data.users.alternate; + +describe('Broadcast room', () => { + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(testuser.username, testuser.password); + }); + + it('should create broadcast room', async () => { + await waitFor(element(by.id('rooms-list-view-create-channel'))) + .toExist() + .withTimeout(2000); + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('select-users-view-search')).replaceText(otheruser.username); + await waitFor(element(by.id(`select-users-view-item-${otheruser.username}`))) + .toBeVisible() + .withTimeout(60000); + await element(by.id(`select-users-view-item-${otheruser.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${otheruser.username}`))) + .toBeVisible() + .withTimeout(5000); + 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-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); + await sleep(500); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('room-actions-info')).tap(); + await waitFor(element(by.id('room-info-view'))) + .toBeVisible() + .withTimeout(2000); + await expect(element(by.label('Broadcast').withAncestor(by.id('room-info-view-broadcast')))).toBeVisible(); + await tapBack(); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(2000); + await tapBack(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(2000); + }); + + it('should send message', async () => { + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await mockMessage('message'); + await tapBack(); + }); + + it('should login as user without write message authorization and enter room', async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + 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 searchRoom(`broadcast${data.random}`); + await element(by.id(`rooms-list-view-item-broadcast${data.random}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-broadcast${data.random}`))) + .toBeVisible() + .withTimeout(60000); + }); + + it('should not have messagebox', async () => { + await expect(element(by.id('messagebox'))).toBeNotVisible(); + }); + + it('should be read only', async () => { + await expect(element(by.label('This room is read only'))).toExist(); + }); + + it('should have the message created earlier', async () => { + await waitFor(element(by[textMatcher](`${data.random}message`))) + .toExist() + .withTimeout(60000); + }); + + it('should have reply button', async () => { + await expect(element(by.id('message-broadcast-reply'))).toBeVisible(); + }); + + it('should tap on reply button and navigate to direct room', async () => { + await element(by.id('message-broadcast-reply')).tap(); + await waitFor(element(by.id(`room-view-title-${testuser.username}`))) + .toBeVisible() + .withTimeout(5000); + }); + + it('should reply broadcasted message', async () => { + // Server is adding 2 spaces in front a reply message + await element(by.id('messagebox-input')).replaceText(`${data.random}broadcastreply`); + await sleep(300); + await element(by.id('messagebox-send-message')).tap(); + await waitFor(element(by[textMatcher](`${data.random}message`))) + .toExist() + .withTimeout(10000); + await element(by[textMatcher](`${data.random}message`)).tap(); + await sleep(600); + await waitFor(element(by.id(`room-view-title-broadcast${data.random}`))) + .toBeVisible() + .withTimeout(10000); + }); +}); diff --git a/e2e/tests/assorted/03-profile.spec.ts b/e2e/tests/assorted/03-profile.spec.ts new file mode 100644 index 000000000..3f7d7741c --- /dev/null +++ b/e2e/tests/assorted/03-profile.spec.ts @@ -0,0 +1,130 @@ +import { expect } from 'detox'; + +import { navigateToLogin, login, sleep, platformTypes, TTextMatcher } from '../../helpers/app'; +import data from '../../data'; + +const profileChangeUser = data.users.profileChanges; + +const scrollDown = 200; + +async function waitForToast() { + // await waitFor(element(by.id('toast'))).toBeVisible().withTimeout(1000); + // await expect(element(by.id('toast'))).toBeVisible(); + // await waitFor(element(by.id('toast'))).not.toBeNotVisible().withTimeout(1000); + // await expect(element(by.id('toast'))).not.toBeVisible(); + await sleep(600); +} + +describe('Profile screen', () => { + let scrollViewType: string; + let textMatcher: TTextMatcher; + + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ scrollViewType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(profileChangeUser.username, profileChangeUser.password); + 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-profile'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('sidebar-profile')).tap(); + await waitFor(element(by.id('profile-view'))) + .toBeVisible() + .withTimeout(2000); + }); + + describe('Render', () => { + it('should have profile view', async () => { + await expect(element(by.id('profile-view'))).toBeVisible(); + }); + + it('should have avatar', async () => { + await expect(element(by.id('profile-view-avatar')).atIndex(0)).toExist(); + }); + + it('should have name', async () => { + await expect(element(by.id('profile-view-name'))).toExist(); + }); + + it('should have username', async () => { + await expect(element(by.id('profile-view-username'))).toExist(); + }); + + it('should have email', async () => { + await expect(element(by.id('profile-view-email'))).toExist(); + }); + + it('should have new password', async () => { + await expect(element(by.id('profile-view-new-password'))).toExist(); + }); + + 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'); + }); + + it('should have upload avatar button', async () => { + await waitFor(element(by.id('profile-view-upload-avatar'))) + .toExist() + .whileElement(by.id('profile-view-list')) + .scroll(scrollDown, 'down'); + }); + + it('should have avatar url button', async () => { + await waitFor(element(by.id('profile-view-avatar-url-button'))) + .toExist() + .whileElement(by.id('profile-view-list')) + .scroll(scrollDown, 'down'); + }); + + it('should have submit button', async () => { + await waitFor(element(by.id('profile-view-submit'))) + .toExist() + .whileElement(by.id('profile-view-list')) + .scroll(scrollDown, 'down'); + }); + }); + + 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')).replaceText(`${profileChangeUser.username}new`); + await element(by.type(scrollViewType)).atIndex(1).swipe('up'); + await element(by.id('profile-view-submit')).tap(); + await waitForToast(); + }); + + it('should change email and password', async () => { + await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`); + await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`); + await waitFor(element(by.id('profile-view-submit'))) + .toExist() + .withTimeout(2000); + await element(by.id('profile-view-submit')).tap(); + await waitFor(element(by.id('profile-view-enter-password-sheet'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('profile-view-enter-password-sheet')).replaceText(`${profileChangeUser.password}`); + await element(by[textMatcher]('Save').withAncestor(by.id('action-sheet-content-with-input-and-submit'))) + .atIndex(0) + .tap(); + await waitForToast(); + }); + + it('should reset avatar', async () => { + await element(by.type(scrollViewType)).atIndex(1).swipe('up'); + await element(by.id('profile-view-reset-avatar')).tap(); + await waitForToast(); + }); + }); +}); diff --git a/e2e/tests/assorted/04-setting.spec.ts b/e2e/tests/assorted/04-setting.spec.ts new file mode 100644 index 000000000..a22266b7e --- /dev/null +++ b/e2e/tests/assorted/04-setting.spec.ts @@ -0,0 +1,92 @@ +import { expect } from 'detox'; + +import { navigateToLogin, login, platformTypes, TTextMatcher } from '../../helpers/app'; +import data from '../../data'; + +const testuser = data.users.regular; + +describe('Settings screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(testuser.username, testuser.password); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + 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); + }); + + describe('Render', () => { + it('should have settings view', async () => { + await expect(element(by.id('settings-view'))).toBeVisible(); + }); + + it('should have language', async () => { + await expect(element(by.id('settings-view-language'))).toExist(); + }); + + it('should have review app', async () => { + await expect(element(by.id('settings-view-review-app'))).toExist(); + }); + + it('should have share app', async () => { + await expect(element(by.id('settings-view-share-app'))).toExist(); + }); + + it('should have default browser', async () => { + await expect(element(by.id('settings-view-default-browser'))).toExist(); + }); + + it('should have theme', async () => { + await expect(element(by.id('settings-view-theme'))).toExist(); + }); + + it('should have security and privacy', async () => { + await expect(element(by.id('settings-view-security-privacy'))).toExist(); + }); + + it('should have licence', async () => { + await expect(element(by.id('settings-view-license'))).toExist(); + }); + + it('should have version no', async () => { + await expect(element(by.id('settings-view-version'))).toExist(); + }); + + it('should have server version', async () => { + await expect(element(by.id('settings-view-server-version'))).toExist(); + }); + }); + + 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(); + await waitFor(element(by[textMatcher]('This will clear all your offline data.'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('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.ts b/e2e/tests/assorted/05-joinpublicroom.spec.ts new file mode 100644 index 000000000..d14f46218 --- /dev/null +++ b/e2e/tests/assorted/05-joinpublicroom.spec.ts @@ -0,0 +1,170 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app'; + +const testuser = data.users.regular; +const room = data.channels.detoxpublic.name; + +async function navigateToRoom() { + await searchRoom(room); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view')).atIndex(0)) + .toExist() + .withTimeout(5000); +} + +async function navigateToRoomActions() { + await element(by.id(`room-view-title-${room}`)).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); +} + +describe('Join public room', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(testuser.username, testuser.password); + await navigateToRoom(); + }); + + describe('Render', () => { + it('should have room screen', async () => { + await expect(element(by.id('room-view'))).toBeVisible(); + }); + + // Render - Header + describe('Header', () => { + it('should have actions button ', async () => { + await expect(element(by.id('room-header'))).toBeVisible(); + }); + }); + + // Render - Join + describe('Join', () => { + it('should have join', async () => { + await expect(element(by.id('room-view-join'))).toBeVisible(); + }); + + it('should have join text', async () => { + await expect(element(by.label('You are in preview mode'))).toBeVisible(); + }); + + it('should have join button', async () => { + await expect(element(by.id('room-view-join-button'))).toBeVisible(); + }); + + it('should not have messagebox', async () => { + await expect(element(by.id('messagebox'))).toBeNotVisible(); + }); + }); + + describe('Room Actions', () => { + beforeAll(async () => { + await navigateToRoomActions(); + }); + + it('should have room actions screen', async () => { + await expect(element(by.id('room-actions-view'))).toBeVisible(); + }); + + it('should have info', async () => { + await expect(element(by.id('room-actions-info'))).toBeVisible(); + }); + + it('should have members', async () => { + await waitFor(element(by.id('room-actions-members'))) + .toBeVisible() + .withTimeout(2000); + }); + + it('should have files', async () => { + await expect(element(by.id('room-actions-files'))).toBeVisible(); + }); + + it('should have mentions', async () => { + await expect(element(by.id('room-actions-mentioned'))).toBeVisible(); + }); + + it('should have starred', async () => { + await expect(element(by.id('room-actions-starred'))).toBeVisible(); + }); + + it('should have share', async () => { + await expect(element(by.id('room-actions-share'))).toBeVisible(); + }); + + it('should have pinned', async () => { + await expect(element(by.id('room-actions-pinned'))).toBeVisible(); + }); + + it('should not have notifications', async () => { + await expect(element(by.id('room-actions-notifications'))).toBeNotVisible(); + }); + + it('should not have leave channel', async () => { + await expect(element(by.id('room-actions-leave-channel'))).toBeNotVisible(); + }); + + afterAll(async () => { + await tapBack(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(2000); + }); + }); + }); + + describe('Usage', () => { + it('should join room', async () => { + await element(by.id('room-view-join-button')).tap(); + await tapBack(); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('messagebox'))) + .toBeVisible() + .withTimeout(60000); + await expect(element(by.id('messagebox'))).toBeVisible(); + await expect(element(by.id('room-view-join'))).toBeNotVisible(); + }); + + it('should send message', async () => { + await mockMessage('message'); + }); + + it('should have notifications and leave channel', async () => { + await navigateToRoomActions(); + await expect(element(by.id('room-actions-view'))).toBeVisible(); + await expect(element(by.id('room-actions-info'))).toBeVisible(); + await expect(element(by.id('room-actions-members'))).toBeVisible(); + await expect(element(by.id('room-actions-files'))).toBeVisible(); + await expect(element(by.id('room-actions-mentioned'))).toBeVisible(); + await expect(element(by.id('room-actions-starred'))).toBeVisible(); + await expect(element(by.id('room-actions-share'))).toBeVisible(); + await expect(element(by.id('room-actions-pinned'))).toBeVisible(); + await expect(element(by.id('room-actions-notifications'))).toBeVisible(); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await expect(element(by.id('room-actions-leave-channel'))).toBeVisible(); + }); + + it('should leave room', async () => { + await element(by.id('room-actions-leave-channel')).tap(); + await waitFor(element(by[textMatcher]('Yes, leave it!').and(by.type(alertButtonType)))) + .toBeVisible() + .withTimeout(5000); + await element(by[textMatcher]('Yes, leave it!').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toBeNotVisible() + .withTimeout(60000); // flaky on Android + }); + }); +}); diff --git a/e2e/tests/assorted/06-status.spec.ts b/e2e/tests/assorted/06-status.spec.ts new file mode 100644 index 000000000..57fb16c03 --- /dev/null +++ b/e2e/tests/assorted/06-status.spec.ts @@ -0,0 +1,65 @@ +import { expect } from 'detox'; + +import { navigateToLogin, login, sleep } from '../../helpers/app'; +import data from '../../data'; + +const testuser = data.users.regular; + +describe('Status screen', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(testuser.username, testuser.password); + + 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-custom-status-online'))) + .toBeVisible() + .withTimeout(2000); + + await element(by.id('sidebar-custom-status-online')).tap(); + await waitFor(element(by.id('status-view'))) + .toBeVisible() + .withTimeout(2000); + }); + + 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(); + await expect(element(by.id('status-view-away'))).toExist(); + await expect(element(by.id('status-view-offline'))).toExist(); + }); + }); + + describe('Usage', () => { + it('should change status', async () => { + await element(by.id('status-view-busy')).tap(); + await element(by.id('status-view-submit')).tap(); + await sleep(3000); // Wait until the loading hide + await waitFor(element(by.id('rooms-list-view-sidebar'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-custom-status-busy'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('sidebar-custom-status-busy')).tap(); + }); + + // TODO: flaky + it('should change status text', async () => { + await element(by.id('status-view-input')).replaceText('status-text-new'); + await element(by.id('status-view-submit')).tap(); + await sleep(3000); // Wait until the loading hide + await waitFor(element(by.label('status-text-new'))) + .toExist() + .withTimeout(5000); + }); + }); +}); diff --git a/e2e/tests/assorted/07-changeserver.spec.ts b/e2e/tests/assorted/07-changeserver.spec.ts new file mode 100644 index 000000000..84f1cc831 --- /dev/null +++ b/e2e/tests/assorted/07-changeserver.spec.ts @@ -0,0 +1,101 @@ +import data from '../../data'; +import { navigateToLogin, login, checkServer, sleep } from '../../helpers/app'; + +const reopenAndCheckServer = async (server: string) => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true }); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await checkServer(server); +}; + +describe('Change server', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + }); + + it('should open the dropdown button, have the server add button and create workspace button', 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); + await waitFor(element(by.id('rooms-list-header-server-add'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('rooms-list-header-create-workspace-button'))) + .toBeVisible() + .withTimeout(5000); + }); + + it('should login to server, add new server, close the app, open the app and show previous logged server', async () => { + 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')).replaceText(`${data.alternateServer}`); + await element(by.id('new-server-view-input')).tapReturnKey(); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(10000); + await reopenAndCheckServer(data.server); + }); + + it('should add server and create new user', 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); + await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); + 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'))) + .toExist() + .withTimeout(2000); + + // Register new user + await sleep(5000); + await element(by.id('register-view-name')).replaceText(data.registeringUser2.username); + await element(by.id('register-view-username')).replaceText(data.registeringUser2.username); + await element(by.id('register-view-email')).replaceText(data.registeringUser2.email); + await element(by.id('register-view-password')).replaceText(data.registeringUser2.password); + await element(by.id('register-view-submit')).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); + + await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`))) + .toBeNotVisible() + .withTimeout(60000); + await checkServer(data.alternateServer); + }); + + it('should reopen the app and show alternate server', async () => { + await reopenAndCheckServer(data.alternateServer); + }); + + it('should change back to main server', 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); + await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`))) + .toBeVisible() + .withTimeout(60000); + await checkServer(data.server); + }); + + it('should reopen the app and show main server', async () => { + await reopenAndCheckServer(data.server); + }); +}); diff --git a/e2e/tests/assorted/08-joinprotectedroom.spec.ts b/e2e/tests/assorted/08-joinprotectedroom.spec.ts new file mode 100644 index 000000000..fcfdea5d5 --- /dev/null +++ b/e2e/tests/assorted/08-joinprotectedroom.spec.ts @@ -0,0 +1,73 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, mockMessage, searchRoom } from '../../helpers/app'; + +const testuser = data.users.regular; +const room = data.channels.detoxpublicprotected.name; +const { joinCode } = data.channels.detoxpublicprotected; + +async function navigateToRoom() { + await searchRoom(room); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(5000); +} + +async function openJoinCode() { + await waitFor(element(by.id('room-view-join-button'))) + .toExist() + .withTimeout(2000); + let n = 0; + while (n < 3) { + try { + await element(by.id('room-view-join-button')).tap(); + await waitFor(element(by.id('join-code'))) + .toBeVisible() + .withTimeout(500); + } catch (error) { + n += 1; + } + } +} + +describe('Join protected room', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(testuser.username, testuser.password); + await navigateToRoom(); + }); + + 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(); + await waitFor(element(by.id('join-code'))) + .toBeNotVisible() + .withTimeout(5000); + }); + + it('should join room', async () => { + await openJoinCode(); + await element(by.id('join-code-input')).replaceText(joinCode); + await element(by.id('join-code-submit')).tap(); + await waitFor(element(by.id('join-code'))) + .toBeNotVisible() + .withTimeout(5000); + await waitFor(element(by.id('messagebox'))) + .toBeVisible() + .withTimeout(60000); + await expect(element(by.id('messagebox'))).toBeVisible(); + await expect(element(by.id('room-view-join'))).toBeNotVisible(); + }); + + it('should send message', async () => { + await mockMessage('message'); + }); + }); +}); diff --git a/e2e/tests/assorted/09-joinfromdirectory.spec.ts b/e2e/tests/assorted/09-joinfromdirectory.spec.ts new file mode 100644 index 000000000..da378e030 --- /dev/null +++ b/e2e/tests/assorted/09-joinfromdirectory.spec.ts @@ -0,0 +1,86 @@ +import data from '../../data'; +import { navigateToLogin, login, tapBack, sleep } from '../../helpers/app'; +import { sendMessage } from '../../helpers/data_setup'; + +const testuser = data.users.regular; + +async function navigateToRoom(search: string) { + 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(); + await waitFor(element(by.id('room-view')).atIndex(0)) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${search}`))) + .toExist() + .withTimeout(5000); +} + +describe('Join room from directory', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(testuser.username, testuser.password); + }); + + describe('Usage', () => { + const threadMessage = `thread-${data.random}`; + beforeAll(async () => { + const result = await sendMessage(data.users.alternate, data.channels.detoxpublic.name, threadMessage); + const threadId = result.message._id; + await sendMessage(data.users.alternate, result.message.rid, data.random, threadId); + }); + + it('should tap directory', async () => { + 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 navigate to thread messages view and load messages', async () => { + await waitFor(element(by.id('room-view-header-threads'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('room-view-header-threads')).tap(); + await waitFor(element(by.id(`thread-messages-view-${threadMessage}`))) + .toBeVisible() + .withTimeout(2000); + await tapBack(); + await waitFor(element(by.id('room-view-header-threads'))) + .toBeVisible() + .withTimeout(2000); + }); + + 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')).atIndex(0).tap(); + await element(by.label('Search by')).atIndex(0).tap(); + await navigateToRoom(data.users.alternate.username); + }); + + it('should search team 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')).atIndex(0).tap(); + await element(by.label('Search by')).atIndex(0).tap(); + await navigateToRoom(data.teams.private.name); + }); + }); +}); diff --git a/e2e/tests/assorted/10-deleteserver.spec.ts b/e2e/tests/assorted/10-deleteserver.spec.ts new file mode 100644 index 000000000..16b1af816 --- /dev/null +++ b/e2e/tests/assorted/10-deleteserver.spec.ts @@ -0,0 +1,67 @@ +import data from '../../data'; +import { sleep, navigateToLogin, login, checkServer, platformTypes, TTextMatcher } from '../../helpers/app'; + +describe('Delete server', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + it('should be logged in main server', async () => { + await checkServer(data.server); + }); + + it('should add server', 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(); + + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('new-server-view-input')).replaceText(`${data.alternateServer}`); + await element(by.id('new-server-view-input')).tapReturnKey(); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(10000); + 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.registeringUser3.username); + await element(by.id('register-view-username')).replaceText(data.registeringUser3.username); + await element(by.id('register-view-email')).replaceText(data.registeringUser3.email); + await element(by.id('register-view-password')).replaceText(data.registeringUser3.password); + 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 delete server', 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); + await element(by.id(`rooms-list-header-server-${data.server}`)).longPress(1500); + await element(by[textMatcher]('Delete').and(by.type(alertButtonType))).tap(); + 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 waitFor(element(by.id(`rooms-list-header-server-${data.server}`))) + .toBeNotVisible() + .withTimeout(10000); + }); +}); diff --git a/e2e/tests/assorted/11-deeplinking.spec.ts b/e2e/tests/assorted/11-deeplinking.spec.ts new file mode 100644 index 000000000..4ea72d4e2 --- /dev/null +++ b/e2e/tests/assorted/11-deeplinking.spec.ts @@ -0,0 +1,218 @@ +import EJSON from 'ejson'; + +import data from '../../data'; +import { tapBack, checkServer, navigateToRegister, platformTypes, TTextMatcher } from '../../helpers/app'; +import { get, login, sendMessage } from '../../helpers/data_setup'; + +const DEEPLINK_METHODS = { AUTH: 'auth', ROOM: 'room' }; + +let amp = '&'; + +const getDeepLink = (method: string, server: string, params?: string) => { + const deeplink = `rocketchat://${method}?host=${server.replace(/^(http:\/\/|https:\/\/)/, '')}${amp}${params}`; + console.log(`Deeplinking to: ${deeplink}`); + return deeplink; +}; + +describe('Deep linking', () => { + let userId: string; + let authToken: string; + let scrollViewType: string; + let threadId: string; + let textMatcher: TTextMatcher; + const threadMessage = `to-thread-${data.random}`; + beforeAll(async () => { + const loginResult = await login(data.users.regular.username, data.users.regular.password); + ({ userId, authToken } = loginResult); + const deviceType = device.getPlatform(); + amp = deviceType === 'android' ? '\\&' : '&'; + ({ scrollViewType, textMatcher } = platformTypes[deviceType]); + // create a thread with api + const result = await sendMessage(data.users.regular, data.groups.alternate2.name, threadMessage); + threadId = result.message._id; + await sendMessage(data.users.regular, result.message.rid, data.random, threadId); + }); + + describe('Authentication', () => { + it('should run a deep link to an invalid account and raise error', async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + delete: true, + url: getDeepLink(DEEPLINK_METHODS.AUTH, data.server, `userId=123${amp}token=abc`) + }); + await waitFor(element(by[textMatcher]("You've been logged out by the server. Please log in again."))) + .toExist() + .withTimeout(30000); // TODO: we need to improve this message + }); + + const authAndNavigate = async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: getDeepLink( + DEEPLINK_METHODS.AUTH, + data.server, + `userId=${userId}${amp}token=${authToken}${amp}path=group/${data.groups.private.name}` + ) + }); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(30000); + await tapBack(); + 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}`))) + .toExist() + .withTimeout(2000); + }; + + it('should authenticate and navigate', async () => { + await authAndNavigate(); + }); + + it('should authenticate while logged in another server', async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToRegister(data.alternateServer); + await element(by.id('register-view-name')).replaceText(data.registeringUser4.username); + await element(by.id('register-view-username')).replaceText(data.registeringUser4.username); + await element(by.id('register-view-email')).replaceText(data.registeringUser4.email); + await element(by.id('register-view-password')).replaceText(data.registeringUser4.password); + await element(by.type(scrollViewType)).atIndex(0).scrollTo('bottom'); + await element(by.id('register-view-submit')).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await authAndNavigate(); + }); + }); + + describe('Room', () => { + describe('While logged in', () => { + it('should navigate to the room using path', async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`) + }); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(30000); + }); + + it('should navigate to the thread using path', async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.alternate2.name}/thread/${threadId}`) + }); + await waitFor(element(by.id(`room-view-title-${threadMessage}`))) + .toExist() + .withTimeout(30000); + }); + + it('should navigate to the room using rid', async () => { + const roomResult = await get(`groups.info?roomName=${data.groups.private.name}`); + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `rid=${roomResult.data.group._id}`) + }); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(30000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + }); + + it('should resume from background and navigate to the room', async () => { + await device.sendToHome(); + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: false, + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`) + }); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(30000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + }); + + it('should simulate a tap on a push notification and navigate to the room', async () => { + /** + * Ideally, we would repeat this test to simulate a resume from background, + * but for some reason it was not working as expected + * This was always turning to false right before running the logic https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/18f359a8ef9691144970c0c1fad990f82096b024/app/lib/notifications/push.ts#L58 + */ + // await device.sendToHome(); + await device.launchApp({ + newInstance: true, + userNotification: { + trigger: { + type: 'push' + }, + title: 'From push', + body: 'Body', + badge: 1, + payload: { + ejson: EJSON.stringify({ + rid: null, + host: data.server, + name: data.groups.private.name, + type: 'p' + }) + } + } + }); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(30000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + }); + }); + + 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(); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); + await checkServer(data.alternateServer); + + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`) + }); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(30000); + }); + + it('should add a not existing server and fallback to the previous one', async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: getDeepLink(DEEPLINK_METHODS.ROOM, 'https://google.com') + }); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(30000); + await checkServer(data.server); + }); + }); + }); +}); diff --git a/e2e/tests/assorted/12-i18n.spec.ts b/e2e/tests/assorted/12-i18n.spec.ts new file mode 100644 index 000000000..a197d3382 --- /dev/null +++ b/e2e/tests/assorted/12-i18n.spec.ts @@ -0,0 +1,140 @@ +import { expect } from 'detox'; + +import { navigateToLogin, login, sleep } from '../../helpers/app'; +import { post } from '../../helpers/data_setup'; +import data from '../../data'; + +const testuser = data.users.regular; +const defaultLaunchArgs = { permissions: { notifications: 'YES' } } as Detox.DeviceLaunchAppConfig; + +const navToLanguage = async () => { + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + 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.id('settings-view-language')).tap(); + await waitFor(element(by.id('language-view'))) + .toBeVisible() + .withTimeout(10000); +}; + +describe('i18n', () => { + describe('OS language', () => { + it("OS set to 'en' and proper translate to 'en'", async () => { + if (device.getPlatform() === 'android') { + return; // FIXME: Passing language with launch parameters doesn't work with Android + } + await device.launchApp({ + ...defaultLaunchArgs, + languageAndLocale: { + language: 'en', + locale: 'en' + }, + delete: true + }); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(20000); + await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible(); + }); + + it("OS set to unavailable language and fallback to 'en'", async () => { + if (device.getPlatform() === 'android') { + return; // FIXME: Passing language with launch parameters doesn't work with Android + } + await device.launchApp({ + ...defaultLaunchArgs, + languageAndLocale: { + language: 'es-MX', + locale: 'es-MX' + } + }); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(20000); + await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible(); + }); + + /** + * This test might become outdated as soon as we support the language + * Although this seems to be a bad approach, that's the intention for having fallback enabled + */ + // it('OS set to available language and fallback to \'en\' on strings missing translation', async() => { + // await device.launchApp({ + // ...defaultLaunchArgs, + // languageAndLocale: { + // language: "nl", + // locale: "nl" + // } + // }); + // }); + }); + + describe('Rocket.Chat language', () => { + beforeAll(async () => { + await device.launchApp({ ...defaultLaunchArgs, delete: true }); + await navigateToLogin(); + await login(testuser.username, testuser.password); + }); + + it("should select 'en'", async () => { + await navToLanguage(); + await element(by.id('language-view-en')).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('rooms-list-view-sidebar')).tap(); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await expect(element(by.id('sidebar-chats').withDescendant(by.label('Chats')))).toBeVisible(); + await expect(element(by.id('sidebar-profile').withDescendant(by.label('Profile')))).toBeVisible(); + await expect(element(by.id('sidebar-settings').withDescendant(by.label('Settings')))).toBeVisible(); + await element(by.id('sidebar-close-drawer')).tap(); + }); + + it("should select 'nl' and fallback to 'en'", async () => { + await navToLanguage(); + await element(by.id('language-view-nl')).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('rooms-list-view-sidebar')).tap(); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await expect(element(by.id('sidebar-chats').withDescendant(by.label('Chats')))).toBeVisible(); // fallback to en + await expect(element(by.id('sidebar-profile').withDescendant(by.label('Profiel')))).toBeVisible(); + await expect(element(by.id('sidebar-settings').withDescendant(by.label('Instellingen')))).toBeVisible(); + await element(by.id('sidebar-close-drawer')).tap(); + }); + + it("should set unsupported language and fallback to 'en'", async () => { + await post('users.setPreferences', { data: { language: 'eo' } }); // Set language to Esperanto + await device.launchApp({ ...defaultLaunchArgs, newInstance: true }); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('rooms-list-view-sidebar')).tap(); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + // give the app some time to apply new language + await sleep(3000); + await expect(element(by.id('sidebar-chats').withDescendant(by.label('Chats')))).toBeVisible(); + await expect(element(by.id('sidebar-profile').withDescendant(by.label('Profile')))).toBeVisible(); + await expect(element(by.id('sidebar-settings').withDescendant(by.label('Settings')))).toBeVisible(); + await post('users.setPreferences', { data: { language: 'en' } }); // Set back to english + }); + }); +}); diff --git a/e2e/tests/assorted/13-display-pref.spec.ts b/e2e/tests/assorted/13-display-pref.spec.ts new file mode 100644 index 000000000..5ca1633d1 --- /dev/null +++ b/e2e/tests/assorted/13-display-pref.spec.ts @@ -0,0 +1,102 @@ +import { expect } from 'detox'; + +import { login, navigateToLogin } from '../../helpers/app'; +import data from '../../data'; + +const goToDisplayPref = async () => { + await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible(); + await element(by.id('rooms-list-view-sidebar')).tap(); + await expect(element(by.id('sidebar-display'))).toBeVisible(); + await element(by.id('sidebar-display')).tap(); +}; +const goToRoomList = async () => { + await expect(element(by.id('display-view-drawer'))).toBeVisible(); + await element(by.id('display-view-drawer')).tap(); + await expect(element(by.id('sidebar-chats'))).toBeVisible(); + await element(by.id('sidebar-chats')).tap(); +}; + +describe('Display prefs', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Render', () => { + it('should have rooms list screen', async () => { + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + }); + + it('should have room item', async () => { + await waitFor(element(by.id('rooms-list-view-item-general'))) + .toExist() + .withTimeout(2000); + }); + + // 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(); + }); + }); + + describe('DisplayPrefView', () => { + it('should go to Display Preferences', async () => { + await goToDisplayPref(); + }); + + it('should have Displays button, expanded, condensed, avatars', async () => { + await expect(element(by.id('display-pref-view-expanded'))).toBeVisible(); + await expect(element(by.id('display-pref-view-condensed'))).toBeVisible(); + await expect(element(by.id('display-pref-view-avatars'))).toBeVisible(); + }); + + it('should have Sort By button', async () => { + await expect(element(by.id('display-pref-view-activity'))).toBeVisible(); + await expect(element(by.id('display-pref-view-name'))).toBeVisible(); + }); + + it('should have Group by button', async () => { + await expect(element(by.id('display-pref-view-unread'))).toBeVisible(); + await expect(element(by.id('display-pref-view-favorites'))).toBeVisible(); + await expect(element(by.id('display-pref-view-types'))).toBeVisible(); + }); + }); + + describe('Change display', () => { + it('should appear the last message in RoomList when is Expanded', async () => { + await element(by.id('display-pref-view-expanded')).tap(); + await goToRoomList(); + await expect(element(by.id('room-item-last-message')).atIndex(0)).toBeVisible(); + }); + + it('should not appear the last message in RoomList when is Condensed', async () => { + await goToDisplayPref(); + await element(by.id('display-pref-view-condensed')).tap(); + await goToRoomList(); + await expect(element(by.id('room-item-last-message'))).not.toBeVisible(); + }); + }); + + describe('Change the avatar visible', () => { + it('should have avatar as default in room list', async () => { + await expect(element(by.id('avatar')).atIndex(0)).toExist(); + }); + + it('should hide the avatar', async () => { + await goToDisplayPref(); + await expect(element(by.id('display-pref-view-avatar-switch'))).toBeVisible(); + await element(by.id('display-pref-view-avatar-switch')).tap(); + await goToRoomList(); + await waitFor(element(by.id('avatar').withAncestor(by.id('rooms-list-view-item-general')))) + .not.toBeVisible() + .withTimeout(2000); + }); + }); + }); +}); diff --git a/e2e/tests/assorted/14-in-app-notification.spec.ts b/e2e/tests/assorted/14-in-app-notification.spec.ts new file mode 100644 index 000000000..b8ab2f943 --- /dev/null +++ b/e2e/tests/assorted/14-in-app-notification.spec.ts @@ -0,0 +1,72 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, sleep, tapBack } from '../../helpers/app'; +import { sendMessage, post } from '../../helpers/data_setup'; + +describe('InApp Notification', () => { + let dmCreatedRid: string; + + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + const result = await post(`im.create`, { username: data.users.alternate.username }); + dmCreatedRid = result.data.room.rid; + }); + + describe('receive in RoomsListView', () => { + const text = 'Message in DM'; + it('should have rooms list screen', async () => { + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + }); + + it('should send direct message from user alternate to user regular', async () => { + await sleep(1000); + await sendMessage(data.users.alternate, dmCreatedRid, text); + }); + + it('should tap on InApp Notification', async () => { + await waitFor(element(by.id(`in-app-notification-${text}`))) + .toExist() + .withTimeout(2000); + await sleep(500); + await element(by.id(`in-app-notification-${text}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${data.users.alternate.username}`))).toExist(); + }); + }); + + describe('receive in another room', () => { + const text = 'Another msg'; + it('should back to RoomsListView and open the channel Detox Public', async () => { + await tapBack(); + await sleep(500); + await element(by.id(`rooms-list-view-item-${data.userRegularChannels.detoxpublic.name}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${data.userRegularChannels.detoxpublic.name}`))).toExist(); + }); + + it('should receive and tap InAppNotification in another room', async () => { + await sendMessage(data.users.alternate, dmCreatedRid, text); + await waitFor(element(by.id(`in-app-notification-${text}`))) + .toExist() + .withTimeout(2000); + await sleep(500); + await element(by.id(`in-app-notification-${text}`)).tap(); + await sleep(500); + await expect(element(by.id('room-header'))).toExist(); + await expect(element(by.id(`room-view-title-${data.users.alternate.username}`))).toExist(); + }); + + it('should back to RoomsListView', async () => { + await tapBack(); + await sleep(500); + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + }); + }); +}); diff --git a/e2e/tests/init.ts b/e2e/tests/init.ts new file mode 100644 index 000000000..71525d7b3 --- /dev/null +++ b/e2e/tests/init.ts @@ -0,0 +1,29 @@ +// import detox from 'detox'; +// import adapter from 'detox/runners/mocha/adapter'; + +// import { detox as config } from '../../package.json'; +import { setup } from '../helpers/data_setup'; +// import { prepareAndroid } from '../helpers/app'; + +beforeAll(async () => { + await setup(); + // await Promise.all([setup(), detox.init(config, { launchApp: false })]); + // await prepareAndroid(); // Make Android less flaky + // await dataSetup() + // await detox.init(config, { launchApp: false }); + // await device.launchApp({ permissions: { notifications: 'YES' } }); +}); + +// beforeEach(async function () { +// // @ts-ignore +// await adapter.beforeEach(this); +// }); + +// afterEach(async function () { +// // @ts-ignore +// await adapter.afterEach(this); +// }); + +// afterAll(async () => { +// await detox.cleanup(); +// }); diff --git a/e2e/tests/onboarding/01-onboarding.spec.ts b/e2e/tests/onboarding/01-onboarding.spec.ts new file mode 100644 index 000000000..2e7e1984e --- /dev/null +++ b/e2e/tests/onboarding/01-onboarding.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'detox'; + +import { TTextMatcher, platformTypes } from '../../helpers/app'; +import data from '../../data'; + +describe('Onboarding', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(20000); + }); + + describe('Render', () => { + it('should have onboarding screen', async () => { + await expect(element(by.id('new-server-view'))).toBeVisible(); + }); + + it('should have "Join our open workspace"', async () => { + await expect(element(by.id('new-server-view-open'))).toBeVisible(); + }); + }); + + describe('Usage', () => { + it('should enter an invalid server and get error', async () => { + await element(by.id('new-server-view-input')).replaceText('invalidtest'); + await element(by.id('new-server-view-input')).tapReturnKey(); + await waitFor(element(by[textMatcher]('Oops!'))) + .toExist() + .withTimeout(10000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + }); + + it('should tap on "Join our open workspace" and navigate', async () => { + await element(by.id('new-server-view-open')).tap(); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); + }); + + it('should enter a valid server without login services and navigate to login', async () => { + await device.launchApp({ newInstance: true }); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('new-server-view-input')).replaceText(data.server); + await element(by.id('new-server-view-input')).tapReturnKey(); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); + }); + }); +}); diff --git a/e2e/tests/onboarding/02-legal.spec.ts b/e2e/tests/onboarding/02-legal.spec.ts new file mode 100644 index 000000000..8844ac085 --- /dev/null +++ b/e2e/tests/onboarding/02-legal.spec.ts @@ -0,0 +1,71 @@ +import { expect } from 'detox'; + +import { navigateToRegister, navigateToLogin } from '../../helpers/app'; + +describe('Legal screen', () => { + describe('From Login', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + }); + + 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); + }); + }); + + describe('From Register', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToRegister(); + }); + + 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(); + await waitFor(element(by.id('privacy-view'))).toBeVisible().withTimeout(2000); + await expect(element(by.id('privacy-view'))).toBeVisible(); + }); + */ + }); +}); diff --git a/e2e/tests/onboarding/03-forgotpassword.spec.ts b/e2e/tests/onboarding/03-forgotpassword.spec.ts new file mode 100644 index 000000000..bbad13c10 --- /dev/null +++ b/e2e/tests/onboarding/03-forgotpassword.spec.ts @@ -0,0 +1,46 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, platformTypes, TTextMatcher } from '../../helpers/app'; + +describe('Forgot password screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await element(by.id('login-view-forgot-password')).tap(); + await waitFor(element(by.id('forgot-password-view'))) + .toExist() + .withTimeout(2000); + }); + + describe('Render', () => { + it('should have forgot password screen', async () => { + await expect(element(by.id('forgot-password-view'))).toExist(); + }); + + it('should have email input', async () => { + await expect(element(by.id('forgot-password-view-email'))).toBeVisible(); + }); + + it('should have submit button', async () => { + await expect(element(by.id('forgot-password-view-submit'))).toBeVisible(); + }); + }); + + 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(); + await waitFor(element(by[textMatcher]('OK'))) + .toExist() + .withTimeout(10000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('login-view'))) + .toBeVisible() + .withTimeout(60000); + }); + }); +}); diff --git a/e2e/tests/onboarding/04-createuser.spec.ts b/e2e/tests/onboarding/04-createuser.spec.ts new file mode 100644 index 000000000..f64bb3406 --- /dev/null +++ b/e2e/tests/onboarding/04-createuser.spec.ts @@ -0,0 +1,91 @@ +import { expect } from 'detox'; + +import { navigateToRegister, platformTypes, TTextMatcher } from '../../helpers/app'; +import data from '../../data'; + +describe('Create user screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToRegister(); + }); + + describe('Render', () => { + it('should have create user screen', async () => { + await waitFor(element(by.id('register-view'))) + .toExist() + .withTimeout(2000); + }); + + it('should have name input', async () => { + await expect(element(by.id('register-view-name'))).toBeVisible(); + }); + + it('should have email input', async () => { + await expect(element(by.id('register-view-email'))).toBeVisible(); + }); + + it('should have password input', async () => { + await expect(element(by.id('register-view-password'))).toBeVisible(); + }); + + it('should have submit button', async () => { + await element(by.id('register-view')).atIndex(0).swipe('up', 'fast', 0.5); + await expect(element(by.id('register-view-submit'))).toBeVisible(); + }); + + it('should have legal button', async () => { + await expect(element(by.id('register-view-more'))).toBeVisible(); + }); + }); + + 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'; + // await element(by.id('register-view-name')).replaceText(data.user); + // await element(by.id('register-view-username')).replaceText(data.user); + // await element(by.id('register-view-email')).replaceText(invalidEmail); + // await element(by.id('register-view-password')).replaceText(data.password); + // await element(by.id('register-view-submit')).tap(); + // }); + + // TODO: When server handle two errors in sequence, the server return Too many requests and force to wait for some time. + // it('should submit email already taken and raise error', async () => { + // await element(by.id('register-view-name')).replaceText(data.registeringUser.username); + // await element(by.id('register-view-username')).replaceText(data.registeringUser.username); + // await element(by.id('register-view-email')).replaceText(data.users.existing.email); + // await element(by.id('register-view-password')).replaceText(data.registeringUser.password); + // await element(by.id('register-view-submit')).tap(); + // await waitFor(element(by[textMatcher]('Email already exists. [403]')).atIndex(0)) + // .toExist() + // .withTimeout(10000); + // await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + // }); + + it('should submit username already taken and raise error', async () => { + await element(by.id('register-view-name')).replaceText(data.registeringUser.username); + await element(by.id('register-view-username')).replaceText(data.users.existing.username); + await element(by.id('register-view-email')).replaceText(data.registeringUser.email); + await element(by.id('register-view-password')).replaceText(data.registeringUser.password); + await element(by.id('register-view-submit')).tap(); + await waitFor(element(by[textMatcher]('Username is already in use')).atIndex(0)) + .toExist() + .withTimeout(10000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + }); + + it('should register', async () => { + await element(by.id('register-view-name')).replaceText(data.registeringUser.username); + await element(by.id('register-view-username')).replaceText(data.registeringUser.username); + await element(by.id('register-view-email')).replaceText(data.registeringUser.email); + await element(by.id('register-view-password')).replaceText(data.registeringUser.password); + await element(by.id('register-view-submit')).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); + }); + }); +}); diff --git a/e2e/tests/onboarding/05-login.spec.ts b/e2e/tests/onboarding/05-login.spec.ts new file mode 100644 index 000000000..ed538b3e0 --- /dev/null +++ b/e2e/tests/onboarding/05-login.spec.ts @@ -0,0 +1,88 @@ +import { expect } from 'detox'; + +import { navigateToLogin, tapBack, platformTypes, navigateToWorkspace, login, TTextMatcher } from '../../helpers/app'; +import data from '../../data'; + +describe('Login screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + }); + + describe('Render', () => { + it('should have login screen', async () => { + await expect(element(by.id('login-view'))).toExist(); + }); + + it('should have email input', async () => { + await expect(element(by.id('login-view-email'))).toBeVisible(); + }); + + it('should have password input', async () => { + await expect(element(by.id('login-view-password'))).toBeVisible(); + }); + + it('should have submit button', async () => { + await expect(element(by.id('login-view-submit'))).toBeVisible(); + }); + + it('should have register button', async () => { + await expect(element(by.id('login-view-register'))).toBeVisible(); + }); + + it('should have forgot password button', async () => { + await expect(element(by.id('login-view-forgot-password'))).toBeVisible(); + }); + + it('should have legal button', async () => { + await expect(element(by.id('login-view-more'))).toBeVisible(); + }); + }); + + describe('Usage', () => { + it('should navigate to register', async () => { + await element(by.id('login-view-register')).tap(); + await waitFor(element(by.id('register-view'))) + .toExist() + .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'); + await element(by.id('login-view-submit')).tap(); + await waitFor(element(by[textMatcher]('Your credentials were rejected! Please try again.'))) + .toBeVisible() + .withTimeout(10000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).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(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); + }); + + it('should connect, go back, connect to the same server and login', async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); + await navigateToWorkspace(); + await tapBack(); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + }); +}); diff --git a/e2e/tests/onboarding/06-roomslist.spec.ts b/e2e/tests/onboarding/06-roomslist.spec.ts new file mode 100644 index 000000000..8366d0c96 --- /dev/null +++ b/e2e/tests/onboarding/06-roomslist.spec.ts @@ -0,0 +1,59 @@ +import { expect } from 'detox'; + +import { login, navigateToLogin, logout, tapBack, searchRoom } from '../../helpers/app'; +import data from '../../data'; + +describe('Rooms list screen', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Render', () => { + it('should have rooms list screen', async () => { + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + }); + + it('should have room item', async () => { + await waitFor(element(by.id('rooms-list-view-item-general'))) + .toExist() + .withTimeout(10000); + }); + + // 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(); + }); + }); + }); + + describe('Usage', () => { + it('should search room and navigate', async () => { + await searchRoom('rocket.cat'); + await element(by.id('rooms-list-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.id('room-view-title-rocket.cat'))) + .toBeVisible() + .withTimeout(60000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(6000); + await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))) + .toExist() + .withTimeout(60000); + }); + + it('should logout', async () => { + await logout(); + }); + }); +}); diff --git a/e2e/tests/onboarding/07-server-history.spec.ts b/e2e/tests/onboarding/07-server-history.spec.ts new file mode 100644 index 000000000..3fd78302d --- /dev/null +++ b/e2e/tests/onboarding/07-server-history.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'detox'; + +import { login, navigateToLogin, logout, tapBack } from '../../helpers/app'; +import data from '../../data'; + +describe('Server history', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + }); + + describe('Usage', () => { + it('should login, save server as history and logout', async () => { + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + await logout(); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(60000); + }); + + it('should show servers history', async () => { + await element(by.id('new-server-view-input')).tap(); + await waitFor(element(by.id(`server-history-${data.server}`))) + .toBeVisible() + .withTimeout(2000); + }); + + it('should tap on a server history and navigate to login', async () => { + await element(by.id(`server-history-${data.server}`)).tap(); + await waitFor(element(by.id('login-view-email'))) + .toBeVisible() + .withTimeout(5000); + await expect(element(by.label(data.users.regular.username).withAncestor(by.id('login-view-email')))); + }); + + it('should delete server from history', async () => { + await tapBack(); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(2000); + await tapBack(); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('new-server-view-input')).tap(); + await waitFor(element(by.id(`server-history-${data.server}`))) + .toBeVisible() + .withTimeout(2000); + await element(by.id(`server-history-delete-${data.server}`)).tap(); + await element(by.id('new-server-view-input')).tap(); + await waitFor(element(by.id(`server-history-${data.server}`))) + .toBeNotVisible() + .withTimeout(2000); + }); + }); +}); diff --git a/e2e/tests/room/01-createroom.spec.ts b/e2e/tests/room/01-createroom.spec.ts new file mode 100644 index 000000000..8ecc4ee4c --- /dev/null +++ b/e2e/tests/room/01-createroom.spec.ts @@ -0,0 +1,257 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { tapBack, navigateToLogin, login, tryTapping, platformTypes, TTextMatcher } from '../../helpers/app'; + +describe('Create room screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('New Message', () => { + beforeAll(async () => { + await waitFor(element(by.id('rooms-list-view-create-channel'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('rooms-list-view-create-channel')).tap(); + }); + + 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', () => { + 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 waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); + }); + + it('should search user and navigate', async () => { + await element(by.id('new-message-view-search')).replaceText('rocket.cat'); + await waitFor(element(by.id('new-message-view-item-rocket.cat'))) + .toExist() + .withTimeout(60000); + await element(by.id('new-message-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id('room-view-title-rocket.cat'))) + .toExist() + .withTimeout(60000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(5000); + }); + + it('should navigate to select users', async () => { + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); + }); + }); + }); + + 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); + }); + + it('should select/unselect user', async () => { + // Spotlight issues + await element(by.id('select-users-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('selected-user-rocket.cat')).tap(); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeNotVisible() + .withTimeout(10000); + // Spotlight issues + await element(by.id('select-users-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); + }); + + it('should navigate to create channel view', async () => { + await element(by.id('selected-users-view-submit')).tap(); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); + }); + }); + + 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', () => { + it('should get invalid room', async () => { + await element(by.id('create-channel-name')).replaceText('general'); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(2000); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by[textMatcher]('A channel with name general exists'))) + .toExist() + .withTimeout(60000); + await expect(element(by[textMatcher]('A channel with name general exists'))).toExist(); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + }); + + it('should create public room', async () => { + const room = `public${data.random}`; + await element(by.id('create-channel-name')).replaceText(''); + await element(by.id('create-channel-name')).replaceText(room); + await element(by.id('create-channel-type')).tap(); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(2000); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(6000); + await expect(element(by.id('room-view'))).toExist(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(6000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); + 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); + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('select-users-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toExist() + .withTimeout(5000); + 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(room); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(2000); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await expect(element(by.id('room-view'))).toExist(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`rooms-list-view-item-${room}`))).toExist(); + }); + + it('should create empty room', async () => { + const room = `empty${data.random}`; + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); + // await device.launchApp({ newInstance: true }); + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('selected-users-view-submit')).tap(); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); + await element(by.id('create-channel-name')).replaceText(room); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(2000); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await expect(element(by.id('room-view'))).toExist(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); + 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); + await expect(element(by.id(`rooms-list-view-item-${room}`))).toExist(); + }); + }); + }); +}); diff --git a/e2e/tests/room/02-room.spec.ts b/e2e/tests/room/02-room.spec.ts new file mode 100644 index 000000000..2df9e640b --- /dev/null +++ b/e2e/tests/room/02-room.spec.ts @@ -0,0 +1,536 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { + navigateToLogin, + login, + mockMessage, + tapBack, + sleep, + searchRoom, + dismissReviewNag, + tryTapping, + platformTypes, + TTextMatcher +} from '../../helpers/app'; +import { sendMessage } from '../../helpers/data_setup'; + +async function navigateToRoom(roomName: string) { + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); +} + +describe('Room screen', () => { + const mainRoom = data.groups.private.name; + let alertButtonType: string; + let textMatcher: TTextMatcher; + + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + await navigateToRoom(mainRoom); + }); + + 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', () => { + it('should have actions button ', async () => { + await expect(element(by.id('room-header'))).toExist(); + }); + + it('should have threads button ', async () => { + await expect(element(by.id('room-view-header-threads'))).toExist(); + }); + }); + + // Render - Messagebox + describe('Messagebox', () => { + it('should have messagebox', async () => { + await expect(element(by.id('messagebox'))).toExist(); + }); + + it('should have open emoji button', async () => { + await expect(element(by.id('messagebox-open-emoji'))).toExist(); + }); + + it('should have message input', async () => { + await expect(element(by.id('messagebox-input'))).toExist(); + }); + + it('should have audio button', async () => { + await expect(element(by.id('messagebox-send-audio'))).toExist(); + }); + + it('should have actions button', async () => { + await expect(element(by.id('messagebox-actions'))).toExist(); + }); + }); + }); + + describe('Usage', () => { + describe('Messagebox', () => { + it('should send message', async () => { + await mockMessage('message'); + await expect(element(by[textMatcher](`${data.random}message`)).atIndex(0)).toExist(); + }); + + describe('Emoji Keyboard', () => { + it('should open emoji keyboard, select an emoji and send it', async () => { + await element(by.id('messagebox-open-emoji')).tap(); + await waitFor(element(by.id('messagebox-keyboard-emoji'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id('emoji-picker-tab-emoji'))) + .toExist() + .withTimeout(10000); + await element(by.id('emoji-picker-tab-emoji')).tap(); + await expect(element(by.id('emoji-blush'))).toExist(); + await element(by.id('emoji-blush')).tap(); + await expect(element(by.id('messagebox-input'))).toHaveText('😊'); + await element(by.id('messagebox-send-message')).tap(); + await waitFor(element(by[textMatcher]('😊'))) + .toExist() + .withTimeout(60000); + await element(by[textMatcher]('😊')).atIndex(0).tap(); + }); + + it('should open emoji keyboard, select an emoji and delete it using emoji keyboards backspace', async () => { + await element(by.id('messagebox-open-emoji')).tap(); + await waitFor(element(by.id('messagebox-keyboard-emoji'))) + .toExist() + .withTimeout(10000); + await expect(element(by.id('emoji-picker-tab-emoji'))).toExist(); + await element(by.id('emoji-picker-tab-emoji')).tap(); + await expect(element(by.id('emoji-upside_down'))).toExist(); + await element(by.id('emoji-upside_down')).tap(); + await expect(element(by.id('messagebox-input'))).toHaveText('🙃'); + await waitFor(element(by.id('emoji-picker-backspace'))) + .toExist() + .withTimeout(2000); + await element(by.id('emoji-picker-backspace')).tap(); + await expect(element(by.id('messagebox-input'))).toHaveText(''); + await element(by.id('messagebox-close-emoji')).tap(); + await waitFor(element(by.id('messagebox-keyboard-emoji'))) + .not.toBeVisible() + .withTimeout(10000); + }); + + it('should search emoji and send it', async () => { + await element(by.id('messagebox-open-emoji')).tap(); + await waitFor(element(by.id('emoji-picker-search'))) + .toExist() + .withTimeout(4000); + await element(by.id('emoji-picker-search')).tap(); + await waitFor(element(by.id('emoji-searchbar-input'))) + .toExist() + .withTimeout(2000); + await element(by.id('emoji-searchbar-input')).replaceText('no_mouth'); + await waitFor(element(by.id('emoji-no_mouth'))) + .toExist() + .withTimeout(2000); + await element(by.id('emoji-no_mouth')).tap(); + await expect(element(by.id('messagebox-input'))).toHaveText('😶'); + await element(by.id('messagebox-send-message')).tap(); + await waitFor(element(by[textMatcher]('😶'))) + .toExist() + .withTimeout(60000); + await element(by[textMatcher]('😶')).atIndex(0).tap(); + }); + + it('should search emojis, go back to Emoji keyboard and then close the Emoji keyboard', async () => { + await element(by.id('messagebox-open-emoji')).tap(); + await waitFor(element(by.id('emoji-picker-search'))) + .toExist() + .withTimeout(4000); + await element(by.id('emoji-picker-search')).tap(); + await waitFor(element(by.id('emoji-searchbar-input'))) + .toExist() + .withTimeout(2000); + await element(by.id('openback-emoji-keyboard')).tap(); + await waitFor(element(by.id('emoji-searchbar-input'))) + .not.toBeVisible() + .withTimeout(2000); + await expect(element(by.id('messagebox-close-emoji'))).toExist(); + await element(by.id('messagebox-close-emoji')).tap(); + await waitFor(element(by.id('messagebox-keyboard-emoji'))) + .not.toBeVisible() + .withTimeout(10000); + }); + + it('frequently used emojis should contain the recently used emojis', async () => { + await element(by.id('messagebox-open-emoji')).tap(); + await waitFor(element(by.id('emoji-picker-tab-clock'))); + await element(by.id('emoji-picker-tab-clock')).tap(); + await waitFor(element(by.id('emoji-blush'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id('emoji-upside_down'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id('emoji-no_mouth'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('messagebox-close-emoji'))).toExist(); + await element(by.id('messagebox-close-emoji')).tap(); + await waitFor(element(by.id('messagebox-keyboard-emoji'))) + .not.toBeVisible() + .withTimeout(10000); + }); + }); + + it('should show/hide emoji autocomplete', async () => { + await element(by.id('messagebox-input')).clearText(); + await element(by.id('messagebox-input')).typeText(':joy'); + await sleep(300); + await waitFor(element(by.id('messagebox-container'))) + .toExist() + .withTimeout(10000); + await element(by.id('messagebox-input')).clearText(); + await waitFor(element(by.id('messagebox-container'))) + .toBeNotVisible() + .withTimeout(10000); + }); + + it('should show and tap on emoji autocomplete', async () => { + await element(by.id('messagebox-input')).typeText(':joy'); + await sleep(300); + await waitFor(element(by.id('messagebox-container'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id('mention-item-joy'))) + .toExist() + .withTimeout(10000); + await element(by.id('mention-item-joy')).tap(); + await expect(element(by.id('messagebox-input'))).toHaveText(':joy: '); + await element(by.id('messagebox-input')).clearText(); + }); + + it('should not show emoji autocomplete on semicolon in middle of a string', async () => { + await element(by.id('messagebox-input')).typeText('name:is'); + await sleep(300); + 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; + const messageMention = `@${username}`; + const message = `${data.random}mention`; + const fullMessage = `${messageMention} ${message}`; + await element(by.id('messagebox-input')).typeText(`@${username}`); + await sleep(300); + await waitFor(element(by.id('messagebox-container'))) + .toExist() + .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(`${messageMention} `); + await tryTapping(element(by.id('messagebox-input')), 2000); + if (device.getPlatform() === 'ios') { + await element(by.id('messagebox-input')).typeText(message); + await element(by.id('messagebox-send-message')).tap(); + const fullMessageMatcher = fullMessage.substr(1); // removes `@` + await waitFor(element(by[textMatcher](fullMessageMatcher))) + .toExist() + .withTimeout(60000); + await expect(element(by[textMatcher](fullMessageMatcher))).toExist(); + await element(by[textMatcher](fullMessageMatcher)).atIndex(0).tap(); + } else { + await element(by.id('messagebox-input')).replaceText(fullMessage); + await element(by.id('messagebox-send-message')).tap(); + } + }); + + it('should not show user autocomplete on @ in the middle of a string', async () => { + 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(); + }); + + it('should show and tap on room autocomplete', async () => { + await element(by.id('messagebox-input')).typeText('#general'); + await waitFor(element(by.id('mention-item-general'))) + .toBeVisible() + .withTimeout(4000); + 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(); + }); + + it('should not show room autocomplete on # in middle of a string', async () => { + await element(by.id('messagebox-input')).tap(); + await element(by.id('messagebox-input')).typeText('te#gen'); + await waitFor(element(by.id('messagebox-container'))) + .toNotExist() + .withTimeout(4000); + await element(by.id('messagebox-input')).clearText(); + }); + it('should draft message', async () => { + await element(by.id('messagebox-input')).typeText(`${data.random}draft`); + await tapBack(); + + await navigateToRoom(mainRoom); + await expect(element(by.id('messagebox-input'))).toHaveText(`${data.random}draft`); + await element(by.id('messagebox-input')).clearText(); + await tapBack(); + + await navigateToRoom(mainRoom); + await expect(element(by.id('messagebox-input'))).toHaveText(''); + }); + }); + + describe('Message', () => { + it('should copy link', async () => { + await element(by[textMatcher](`${data.random}message`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await element(by[textMatcher]('Get Link')).atIndex(0).tap(); + // TODO: test clipboard + }); + it('should copy message', async () => { + await element(by[textMatcher](`${data.random}message`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await element(by[textMatcher]('Copy')).atIndex(0).tap(); + // TODO: test clipboard + }); + + it('should react to message', async () => { + await waitFor(element(by[textMatcher](`${data.random}message`))) + .toExist() + .withTimeout(60000); + await element(by[textMatcher](`${data.random}message`)) + .atIndex(0) + .tap(); + await element(by[textMatcher](`${data.random}message`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await element(by.id('add-reaction')).tap(); + await waitFor(element(by.id('emoji-picker-tab-emoji'))) + .toExist() + .withTimeout(2000); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 1); + await element(by.id('emoji-picker-tab-emoji')).tap(); + await waitFor(element(by.id('emoji-grinning'))) + .toExist() + .withTimeout(10000); + await element(by.id('emoji-grinning')).tap(); + await waitFor(element(by.id('message-reaction-:grinning:'))) + .toExist() + .withTimeout(60000); + }); + + it('should ask for review', async () => { + await dismissReviewNag(); // TODO: Create a proper test for this elsewhere. + }); + + it('should search emojis in the reaction picker and react', async () => { + await element(by[textMatcher](`${data.random}message`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await element(by.id('add-reaction')).tap(); + await waitFor(element(by.id('emoji-searchbar-input'))) + .toExist() + .withTimeout(2000); + await element(by.id('emoji-searchbar-input')).typeText('laughing'); + await waitFor(element(by.id('emoji-laughing'))) + .toExist() + .withTimeout(4000); + await element(by.id('emoji-laughing')).tap(); + await waitFor(element(by.id('message-reaction-:laughing:'))) + .toExist() + .withTimeout(60000); + }); + + it('should react to message with frequently used emoji', async () => { + await element(by[textMatcher](`${data.random}message`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await waitFor(element(by.id('message-actions-emoji-upside_down'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('message-actions-emoji-upside_down')).tap(); + await waitFor(element(by.id('message-reaction-:upside_down:'))) + .toBeVisible() + .withTimeout(60000); + }); + + it('should show reaction picker on add reaction button pressed and have frequently used emoji', async () => { + await element(by.id('message-add-reaction')).tap(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 1); + await waitFor(element(by.id('emoji-upside_down'))) + .toExist() + .withTimeout(4000); + await waitFor(element(by.id('emoji-picker-tab-emoji'))) + .toExist() + .withTimeout(2000); + await element(by.id('emoji-picker-tab-emoji')).tap(); + await waitFor(element(by.id('emoji-wink'))) + .toExist() + .withTimeout(10000); + await element(by.id('emoji-wink')).tap(); + await waitFor(element(by.id('message-reaction-:wink:'))) + .toExist() + .withTimeout(60000); + }); + + it('should open/close reactions list', async () => { + await element(by.id('message-reaction-:grinning:')).longPress(); + await waitFor(element(by.id('reactionsList'))) + .toExist() + .withTimeout(4000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.5); + }); + + it('should remove reaction', async () => { + await element(by.id('message-reaction-:grinning:')).tap(); + await waitFor(element(by.id('message-reaction-:grinning:'))) + .not.toExist() + .withTimeout(60000); + }); + + it('should edit message', async () => { + await mockMessage('edit'); + await element(by[textMatcher](`${data.random}edit`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await element(by[textMatcher]('Edit')).atIndex(0).tap(); + await element(by.id('messagebox-input')).replaceText(`${data.random}edited`); + await element(by.id('messagebox-send-message')).tap(); + await waitFor(element(by[textMatcher](`${data.random}edited`)).atIndex(0)) + .toExist() + .withTimeout(60000); + await waitFor(element(by.id(`${data.random}edited-edited`))) + .toExist() + .withTimeout(60000); + }); + it('should quote message', async () => { + await mockMessage('quote'); + await element(by[textMatcher](`${data.random}quote`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await element(by[textMatcher]('Quote')).atIndex(0).tap(); + await element(by.id('messagebox-input')).replaceText(`${data.random}quoted`); + await waitFor(element(by.id('messagebox-send-message'))) + .toExist() + .withTimeout(2000); + await element(by.id('messagebox-send-message')).tap(); + // TODO: test if quote was sent + }); + + it('should delete message', async () => { + await mockMessage('delete'); + await waitFor(element(by[textMatcher](`${data.random}delete`)).atIndex(0)).toBeVisible(); + await element(by[textMatcher](`${data.random}delete`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await waitFor(element(by[textMatcher]('Delete'))) + .toExist() + .withTimeout(1000); + await element(by[textMatcher]('Delete')).atIndex(0).tap(); + const deleteAlertMessage = 'You will not be able to recover this message!'; + await waitFor(element(by[textMatcher](deleteAlertMessage)).atIndex(0)) + .toExist() + .withTimeout(10000); + await element(by[textMatcher]('Delete').and(by.type(alertButtonType))).tap(); + await waitFor(element(by[textMatcher](`${data.random}delete`)).atIndex(0)) + .toNotExist() + .withTimeout(2000); + await tapBack(); + }); + + it('should reply in DM to another user', async () => { + const channelName = data.userRegularChannels.detoxpublic.name; + const stringToReply = 'Message to reply in DM'; + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + await navigateToRoom(channelName); + await sendMessage(data.users.alternate, channelName, stringToReply); + await waitFor(element(by[textMatcher](stringToReply)).atIndex(0)) + .toBeVisible() + .withTimeout(3000); + await element(by[textMatcher](stringToReply)).atIndex(0).longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await waitFor(element(by[textMatcher]('Reply in Direct Message')).atIndex(0)) + .toExist() + .withTimeout(6000); + await element(by[textMatcher]('Reply in Direct Message')).atIndex(0).tap(); + await waitFor(element(by.id(`room-view-title-${data.users.alternate.username}`))) + .toExist() + .withTimeout(6000); + await element(by.id('messagebox-input')).replaceText(`${data.random} replied in dm`); + await waitFor(element(by.id('messagebox-send-message'))) + .toExist() + .withTimeout(2000); + await element(by.id('messagebox-send-message')).tap(); + }); + }); + }); +}); diff --git a/e2e/tests/room/03-roomactions.spec.ts b/e2e/tests/room/03-roomactions.spec.ts new file mode 100644 index 000000000..98a8fbcd8 --- /dev/null +++ b/e2e/tests/room/03-roomactions.spec.ts @@ -0,0 +1,659 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { + navigateToLogin, + login, + tapBack, + sleep, + searchRoom, + mockMessage, + starMessage, + pinMessage, + platformTypes, + TTextMatcher +} from '../../helpers/app'; + +const { sendMessage } = require('../../helpers/data_setup'); + +async function navigateToRoomActions(type: string) { + let room; + if (type === 'd') { + room = 'rocket.cat'; + } else { + room = data.groups.private.name; + } + await searchRoom(room); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); +} + +async function backToActions() { + await tapBack(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(2000); +} + +async function backToRoomsList() { + await tapBack(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); +} + +async function waitForToast() { + await sleep(1000); +} + +describe('Room actions screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + }); + + describe('Render', () => { + describe('Direct', () => { + beforeAll(async () => { + await navigateToRoomActions('d'); + }); + + 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(); + }); + + it('should have block user', async () => { + await waitFor(element(by.id('room-actions-block-user'))).toExist(); + await expect(element(by.id('room-actions-block-user'))).toExist(); + }); + + afterAll(async () => { + await backToRoomsList(); + }); + }); + + describe('Channel/Group', () => { + beforeAll(async () => { + await navigateToRoomActions('c'); + }); + + 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 members', async () => { + await expect(element(by.id('room-actions-members'))).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(); + }); + }); + }); + + describe('Usage', () => { + 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); + await backToActions(); + }); + + it('should show starred message and unstar it', async () => { + // Go back to room and send a message + await tapBack(); + await mockMessage('messageToStar'); + + // Star the message + await starMessage('messageToStar'); + + // Back into Room Actions + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); + + // Go to starred messages + await element(by.id('room-actions-view')).swipe('up'); + await waitFor(element(by.id('room-actions-starred'))).toExist(); + await element(by.id('room-actions-starred')).tap(); + await waitFor(element(by.id('starred-messages-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by[textMatcher](`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view')))) + .toExist() + .withTimeout(60000); + + // Unstar message + await element(by[textMatcher](`${data.random}messageToStar`)) + .atIndex(0) + .longPress(); + await expect(element(by.id('action-sheet'))).toExist(); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by[textMatcher]('Unstar')).atIndex(0).tap(); + + await waitFor(element(by[textMatcher](`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view')))) + .toBeNotVisible() + .withTimeout(60000); + await backToActions(); + }); + + it('should show pinned message and unpin it', async () => { + // Go back to room and send a message + await tapBack(); + await mockMessage('messageToPin'); + + // Pin the message + await pinMessage('messageToPin'); + + // 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'); + await waitFor(element(by.id('room-actions-pinned'))).toExist(); + await element(by.id('room-actions-pinned')).tap(); + await waitFor(element(by.id('pinned-messages-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by[textMatcher](`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view')))) + .toExist() + .withTimeout(6000); + await element(by[textMatcher](`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view'))) + .atIndex(0) + .longPress(); + + await expect(element(by.id('action-sheet'))).toExist(); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by[textMatcher]('Unpin')).atIndex(0).tap(); + + await waitFor(element(by[textMatcher](`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view')))) + .not.toExist() + .withTimeout(6000); + await backToActions(); + }); + }); + + describe('Notification', () => { + it('should navigate to notification preference view', async () => { + await waitFor(element(by.id('room-actions-scrollview'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-notifications'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-notifications')).tap(); + await waitFor(element(by.id('notification-preference-view'))) + .toExist() + .withTimeout(2000); + }); + + it('should have receive notification option', async () => { + await expect(element(by.id('notification-preference-view-receive-notification'))).toExist(); + }); + + it('should have show unread count option', async () => { + await expect(element(by.id('notification-preference-view-mark-as-unread'))).toExist(); + }); + + it('should have notification alert option', async () => { + await expect(element(by.id('notification-preference-view-alert'))).toExist(); + }); + + it('should have push notification option', async () => { + await waitFor(element(by.id('notification-preference-view-push-notification'))) + .toExist() + .withTimeout(4000); + }); + + it('should have notification sound option', async () => { + await waitFor(element(by.id('notification-preference-view-sound'))) + .toExist() + .withTimeout(4000); + }); + + it('should have email alert option', async () => { + await waitFor(element(by.id('notification-preference-view-email-alert'))) + .toExist() + .withTimeout(4000); + }); + + afterAll(async () => { + await backToActions(); + }); + }); + + 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; + + it('should tap on leave channel and raise alert', async () => { + await waitFor(element(by.id('room-actions-scrollview'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-leave-channel'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-leave-channel')).tap(); + await waitFor(element(by[textMatcher]('Yes, leave it!'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('Yes, leave it!').and(by.type(alertButtonType))).tap(); + await waitFor(element(by[textMatcher]('You are the last owner. Please set new owner before leaving the room.'))) + .toExist() + .withTimeout(8000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(2000); + }); + + it('should add users to the room', async () => { + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); + + await waitFor(element(by.id('room-actions-add-user'))) + .toExist() + .withTimeout(4000); + await element(by.id('room-actions-add-user')).tap(); + + // add rocket.cat + const rocketCat = 'rocket.cat'; + await waitFor(element(by.id(`select-users-view-item-${rocketCat}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${rocketCat}`)).tap(); + await waitFor(element(by.id(`selected-user-${rocketCat}`))) + .toExist() + .withTimeout(5000); + + await waitFor(element(by.id('select-users-view-search'))) + .toExist() + .withTimeout(4000); + await element(by.id('select-users-view-search')).tap(); + await element(by.id('select-users-view-search')).replaceText(user.username); + await sleep(300); + await waitFor(element(by.id(`select-users-view-item-${user.username}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${user.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${user.username}`))) + .toExist() + .withTimeout(5000); + + await element(by.id('selected-users-view-submit')).tap(); + await sleep(300); + await backToActions(); + }); + + describe('Room Members', () => { + beforeAll(async () => { + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); + }); + + const openActionSheet = async (username: string) => { + await waitFor(element(by.id(`room-members-view-item-${username}`))) + .toExist() + .withTimeout(5000); + let n = 0; + while (n < 3) { + // Max tries three times, in case it does not register the click + try { + await element(by.id(`room-members-view-item-${username}`)).tap(); + await sleep(300); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(5000); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + await element(by.id('action-sheet-handle')).swipe('up'); + return; + } catch (e) { + n += 1; + } + } + }; + + const closeActionSheet = async () => { + await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); + await waitFor(element(by.id('action-sheet'))) + .toBeNotVisible() + .withTimeout(1000); + await sleep(100); + }; + + it('should show all users', async () => { + await waitFor(element(by.id('room-members-view-filter'))) + .toExist() + .withTimeout(10000); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + await tapBack(); + }); + + it('should filter user', async () => { + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-members')).tap(); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + await element(by.id('room-members-view-search')).replaceText('rocket'); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toBeNotVisible() + .withTimeout(60000); + await element(by.id('room-members-view-search')).tap(); + await element(by.id('room-members-view-search')).clearText(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + }); + + it('should remove user from room', async () => { + await openActionSheet('rocket.cat'); + await waitFor(element(by[textMatcher]('Remove from room'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('Remove from room')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('Are you sure?'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Yes, remove user!').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('room-members-view-item-rocket.cat'))) + .toBeNotVisible() + .withTimeout(60000); + }); + + it('should clear search', async () => { + await element(by.id('room-members-view-search')).tap(); + await element(by.id('room-members-view-search')).clearText(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + }); + + it('should set/remove as owner', async () => { + await openActionSheet(user.username); + await element(by.id('action-sheet-set-owner')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-owner-checked'))) + .toBeVisible() + .withTimeout(6000); + await element(by.id('action-sheet-set-owner')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-owner-unchecked'))) + .toBeVisible() + .withTimeout(60000); + await closeActionSheet(); + }); + + it('should set/remove as leader', async () => { + await openActionSheet(user.username); + await element(by.id('action-sheet-set-leader')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-leader-checked'))) + .toBeVisible() + .withTimeout(6000); + await element(by.id('action-sheet-set-leader')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-owner-unchecked'))) + .toBeVisible() + .withTimeout(60000); + await closeActionSheet(); + }); + + it('should set/remove as moderator', async () => { + await openActionSheet(user.username); + await element(by.id('action-sheet-set-moderator')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-moderator-checked'))) + .toBeVisible() + .withTimeout(6000); + await element(by.id('action-sheet-set-moderator')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-moderator-unchecked'))) + .toBeVisible() + .withTimeout(60000); + await closeActionSheet(); + }); + + it('should set/remove as mute', async () => { + await openActionSheet(user.username); + await element(by[textMatcher]('Mute')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('Are you sure?'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Mute').and(by.type(alertButtonType))).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await element(by[textMatcher]('Unmute')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('Are you sure?'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Unmute').and(by.type(alertButtonType))).tap(); + await waitForToast(); + + await openActionSheet(user.username); + // Tests if Remove as mute worked + await waitFor(element(by[textMatcher]('Mute'))) + .toExist() + .withTimeout(5000); + await closeActionSheet(); + }); + + it('should ignore user', async () => { + const message = `${data.random}ignoredmessagecontent`; + const channelName = `#${data.groups.private.name}`; + await sendMessage(user, channelName, message); + await openActionSheet(user.username); + await element(by[textMatcher]('Ignore')).atIndex(0).tap(); + await waitForToast(); + await backToActions(); + await tapBack(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await waitFor(element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0)) + .toExist() + .withTimeout(60000); + await element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0).tap(); + await waitFor(element(by[textMatcher](message)).atIndex(0)) + .toExist() + .withTimeout(60000); + await element(by[textMatcher](message)).atIndex(0).tap(); + }); + + it('should navigate to direct message', async () => { + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id('room-members-view-filter'))) + .toExist() + .withTimeout(10000); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + await openActionSheet(user.username); + await element(by[textMatcher]('Direct message')).atIndex(0).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await waitFor(element(by.id(`room-view-title-${user.username}`))) + .toExist() + .withTimeout(60000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); + }); + }); + }); + + describe('Direct', () => { + beforeAll(async () => { + await navigateToRoomActions('d'); + }); + + it('should block/unblock user', async () => { + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-block-user'))).toExist(); + await element(by.id('room-actions-block-user')).tap(); + await waitFor(element(by[textMatcher]('Unblock user'))) + .toExist() + .withTimeout(60000); + await element(by.id('room-actions-block-user')).tap(); + await waitFor(element(by[textMatcher]('Block user'))) + .toExist() + .withTimeout(60000); + }); + }); + }); +}); diff --git a/e2e/tests/room/04-discussion.spec.ts b/e2e/tests/room/04-discussion.spec.ts new file mode 100644 index 000000000..7fa66a906 --- /dev/null +++ b/e2e/tests/room/04-discussion.spec.ts @@ -0,0 +1,225 @@ +import { expect } from 'detox'; + +import { TTextMatcher, navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } from '../../helpers/app'; +import data from '../../data'; + +const channel = data.groups.private.name; + +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 textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); + ({ textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + it('should create discussion from NewMessageView', async () => { + const discussionName = `${data.random} Discussion NewMessageView`; + await waitFor(element(by.id('rooms-list-view-create-channel'))) + .toExist() + .withTimeout(2000); + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('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[textMatcher]('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-discussion-name')).replaceText(discussionName); + 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); + await tapBack(); + await waitFor(element(by.id(`rooms-list-view-item-${discussionName}`))) + .toExist() + .withTimeout(5000); + }); + + it('should create discussion from action button', async () => { + 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[textMatcher]('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 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', () => { + it('should send a message', async () => { + await waitFor(element(by.id('messagebox'))) + .toBeVisible() + .withTimeout(60000); + await mockMessage('message'); + }); + + it('should create discussion', async () => { + const discussionName = `${data.random}message`; + await element(by[textMatcher](discussionName)).atIndex(0).longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('Start a Discussion')).atIndex(0).tap(); + await waitFor(element(by.id('create-discussion-view'))) + .toExist() + .withTimeout(2000); + 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('Check RoomActionsView render', () => { + it('should navigate to RoomActionsView', async () => { + await waitFor(element(by.id('room-header'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); + }); + + it('should have room actions screen', async () => { + await expect(element(by.id('room-actions-view'))).toBeVisible(); + }); + + it('should have info', async () => { + await expect(element(by.id('room-actions-info'))).toBeVisible(); + }); + + it('should have members', async () => { + await expect(element(by.id('room-actions-members'))).toBeVisible(); + }); + + it('should have files', async () => { + await expect(element(by.id('room-actions-files'))).toBeVisible(); + }); + + it('should have mentions', async () => { + await expect(element(by.id('room-actions-mentioned'))).toBeVisible(); + }); + + it('should have starred', async () => { + await element(by.id('room-actions-scrollview')).swipe('up', 'slow', 0.5); + await expect(element(by.id('room-actions-starred'))).toBeVisible(); + }); + + it('should have share', async () => { + await element(by.id('room-actions-scrollview')).swipe('up'); + await expect(element(by.id('room-actions-share'))).toBeVisible(); + }); + + it('should have pinned', async () => { + await expect(element(by.id('room-actions-pinned'))).toBeVisible(); + }); + + it('should not have notifications', async () => { + await expect(element(by.id('room-actions-notifications'))).toBeVisible(); + }); + + it('should not have leave channel', async () => { + await expect(element(by.id('room-actions-leave-channel'))).toBeVisible(); + }); + + it('should navigate to RoomActionView', async () => { + await element(by.id('room-actions-scrollview')).swipe('down'); + await expect(element(by.id('room-actions-info'))).toBeVisible(); + await element(by.id('room-actions-info')).tap(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(60000); + await expect(element(by.id('room-info-view'))).toExist(); + }); + + it('should have edit button', async () => { + await expect(element(by.id('room-info-view-edit-button'))).toBeVisible(); + }); + }); + + describe('Open Discussion from DiscussionsView', () => { + const discussionName = `${data.random}message`; + it('should go back to main room', async () => { + await tapBack(); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); + await tapBack(); + await waitFor(element(by.id(`room-view-title-${discussionName}`))) + .toExist() + .withTimeout(5000); + await tapBack(); + await navigateToRoom(); + }); + + it('should navigate to DiscussionsView', async () => { + await waitFor(element(by.id(`room-view-title-${channel}`))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id('room-header'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-discussions'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('room-actions-discussions')).tap(); + await waitFor(element(by.id('discussions-view'))) + .toBeVisible() + .withTimeout(5000); + }); + + it('should navigate to discussion', async () => { + const discussionName = `${data.random} Discussion NewMessageView`; + await waitFor(element(by.label(discussionName)).atIndex(0)) + .toExist() + .withTimeout(5000); + await element(by.label(discussionName)).atIndex(0).tap(); + await waitFor(element(by.id(`room-view-title-${discussionName}`))) + .toExist() + .withTimeout(5000); + }); + }); +}); diff --git a/e2e/tests/room/05-threads.spec.ts b/e2e/tests/room/05-threads.spec.ts new file mode 100644 index 000000000..61c35229c --- /dev/null +++ b/e2e/tests/room/05-threads.spec.ts @@ -0,0 +1,240 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { + navigateToLogin, + login, + mockMessage, + tapBack, + sleep, + searchRoom, + platformTypes, + dismissReviewNag, + TTextMatcher +} from '../../helpers/app'; + +async function navigateToRoom(roomName: string) { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id(`room-view-title-${roomName}`))) + .toExist() + .withTimeout(5000); +} + +describe('Threads', () => { + const mainRoom = data.groups.private.name; + let textMatcher: TTextMatcher; + + beforeAll(async () => { + ({ textMatcher } = platformTypes[device.getPlatform()]); + await navigateToRoom(mainRoom); + }); + + describe('Render', () => { + it('should have room screen', async () => { + await waitFor(element(by.id(`room-view-title-${mainRoom}`))) + .toExist() + .withTimeout(5000); + }); + + // Render - Header + describe('Header', () => { + it('should have actions button ', async () => { + await expect(element(by.id('room-header'))).toExist(); + }); + + it('should have threads button ', async () => { + await expect(element(by.id('room-view-header-threads'))).toExist(); + }); + }); + + // Render - Messagebox + describe('Messagebox', () => { + it('should have messagebox', async () => { + await expect(element(by.id('messagebox'))).toExist(); + }); + + it('should have open emoji button', async () => { + if (device.getPlatform() === 'android') { + await expect(element(by.id('messagebox-open-emoji'))).toExist(); + } + }); + + it('should have message input', async () => { + await expect(element(by.id('messagebox-input'))).toExist(); + }); + + it('should have audio button', async () => { + await expect(element(by.id('messagebox-send-audio'))).toExist(); + }); + + it('should have actions button', async () => { + await expect(element(by.id('messagebox-actions'))).toExist(); + }); + }); + }); + + describe('Usage', () => { + describe('Thread', () => { + const thread = `${data.random}thread`; + it('should create thread', async () => { + await mockMessage('thread'); + await element(by[textMatcher](thread)).atIndex(0).longPress(); + 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', 'fast', 0.5); + await element(by[textMatcher]('Reply in Thread')).atIndex(0).tap(); + await element(by.id('messagebox-input')).replaceText('replied'); + await waitFor(element(by.id('messagebox-send-message'))) + .toExist() + .withTimeout(2000); + await element(by.id('messagebox-send-message')).tap(); + await waitFor(element(by.id(`message-thread-button-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`message-thread-button-${thread}`))).toExist(); + }); + + it('should navigate to thread from button', async () => { + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${thread}`))).toExist(); + await tapBack(); + }); + + it('should toggle follow thread', 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('room-view-header-unfollow')).tap(); + await waitFor(element(by.id('room-view-header-follow'))) + .toExist() + .withTimeout(60000); + await expect(element(by.id('room-view-header-follow'))).toExist(); + await element(by.id('room-view-header-follow')).tap(); + await waitFor(element(by.id('room-view-header-unfollow'))) + .toExist() + .withTimeout(60000); + await expect(element(by.id('room-view-header-unfollow'))).toExist(); + }); + + it('should send message in thread only', async () => { + const messageText = 'threadonly'; + await mockMessage(messageText, true); + await tapBack(); + await waitFor(element(by.id(`room-view-title-${data.random}thread`))) + .not.toExist() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${mainRoom}`))) + .toExist() + .withTimeout(5000); + await waitFor(element(by[textMatcher](`${data.random}${messageText}`)).atIndex(0)) + .toNotExist() + .withTimeout(2000); + }); + + it('should mark send to channel and show on main channel', async () => { + const messageText = 'sendToChannel'; + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id('messagebox-input-thread'))) + .toExist() + .withTimeout(5000); + await element(by.id('messagebox-input-thread')).replaceText(messageText); + 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-view-title-${data.random}thread`))) + .not.toExist() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${mainRoom}`))) + .toExist() + .withTimeout(5000); + await waitFor(element(by[textMatcher](messageText)).atIndex(0)) + .toExist() + .withTimeout(2000); + }); + + it('should navigate to thread from thread name', async () => { + const messageText = 'navthreadname'; + await mockMessage('dummymessagebetweenthethread'); // TODO: Create a proper test for this elsewhere. + await dismissReviewNag(); + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id('messagebox-input-thread'))) + .toExist() + .withTimeout(5000); + await element(by.id('messagebox-input-thread')).replaceText(messageText); + 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-view-title-${data.random}thread`))) + .not.toExist() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${mainRoom}`))) + .toExist() + .withTimeout(5000); + 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(); + await sleep(2000); + await tapBack(); + }); + + it('should navigate to thread from threads view', async () => { + await waitFor(element(by.id('room-view-header-threads'))) + .toExist() + .withTimeout(1000); + await element(by.id('room-view-header-threads')).tap(); + await waitFor(element(by.id('thread-messages-view'))) + .toExist() + .withTimeout(5000); + await element(by.id(`thread-messages-view-${thread}`)) + .atIndex(0) + .tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${thread}`))).toExist(); + await tapBack(); + await waitFor(element(by.id('thread-messages-view'))) + .toExist() + .withTimeout(5000); + await expect(element(by.id('thread-messages-view'))).toExist(); + await tapBack(); + }); + + 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')).replaceText(`${thread}draft`); + await tapBack(); + + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id('messagebox-input-thread'))).toHaveText(`${thread}draft`); + await element(by.id('messagebox-input-thread')).clearText(); + await tapBack(); + + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id('messagebox-input-thread'))).toHaveText(''); + }); + }); + }); +}); diff --git a/e2e/tests/room/06-createdmgroup.spec.ts b/e2e/tests/room/06-createdmgroup.spec.ts new file mode 100644 index 000000000..5cba938ed --- /dev/null +++ b/e2e/tests/room/06-createdmgroup.spec.ts @@ -0,0 +1,61 @@ +import data from '../../data'; +import { navigateToLogin, login, platformTypes, TTextMatcher } from '../../helpers/app'; + +describe('Group DM', () => { + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Create Group DM', () => { + beforeAll(async () => { + await waitFor(element(by.id('rooms-list-view-create-channel'))) + .toExist() + .withTimeout(2000); + await element(by.id('rooms-list-view-create-channel')).tap(); + }); + + 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', () => { + it('should navigate to create DM', async () => { + await element(by[textMatcher]('Direct message')).atIndex(0).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 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 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); + }); + }); + }); +}); diff --git a/e2e/tests/room/07-markasunread.spec.ts b/e2e/tests/room/07-markasunread.spec.ts new file mode 100644 index 000000000..1fbb999ff --- /dev/null +++ b/e2e/tests/room/07-markasunread.spec.ts @@ -0,0 +1,50 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, searchRoom, sleep, platformTypes, TTextMatcher } from '../../helpers/app'; +import { sendMessage } from '../../helpers/data_setup'; + +async function navigateToRoom(user: string) { + await searchRoom(`${user}`); + await element(by.id(`rooms-list-view-item-${user}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); +} + +describe('Mark as unread', () => { + const user = data.users.alternate.username; + let textMatcher: TTextMatcher; + + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + await navigateToRoom(user); + }); + + 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}`; + await sendMessage(data.users.alternate, channelName, message); + await waitFor(element(by[textMatcher](message)).atIndex(0)) + .toExist() + .withTimeout(30000); + await sleep(300); + await element(by[textMatcher](message)).atIndex(0).longPress(); + await waitFor(element(by.id('action-sheet-handle'))) + .toBeVisible() + .withTimeout(3000); + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await element(by[textMatcher]('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(); + }); + }); + }); +}); diff --git a/e2e/tests/room/08-roominfo.spec.ts b/e2e/tests/room/08-roominfo.spec.ts new file mode 100644 index 000000000..2aff91b13 --- /dev/null +++ b/e2e/tests/room/08-roominfo.spec.ts @@ -0,0 +1,324 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app'; + +const privateRoomName = data.groups.private.name; + +async function navigateToRoomInfo(type: string) { + let room; + if (type === 'd') { + room = 'rocket.cat'; + } else { + room = privateRoomName; + } + await searchRoom(room); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-actions-info')).tap(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); +} + +async function swipe(direction: Detox.Direction) { + await element(by.id('room-info-edit-view-list')).swipe(direction, 'fast', 0.8); +} + +async function waitForToast() { + await sleep(300); +} + +describe('Room info screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Direct', () => { + beforeAll(async () => { + await navigateToRoomInfo('d'); + }); + + it('should navigate to room info', async () => { + await expect(element(by.id('room-info-view'))).toExist(); + await expect(element(by.id('room-info-view-name'))).toExist(); + }); + + afterAll(async () => { + await tapBack(); + await tapBack(); + await tapBack(); + }); + }); + + describe('Channel/Group', () => { + beforeAll(async () => { + await navigateToRoomInfo('c'); + }); + + 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[textMatcher]('Description'))).toExist(); + }); + + it('should have topic', async () => { + await expect(element(by[textMatcher]('Topic'))).toExist(); + }); + + it('should have announcement', async () => { + await expect(element(by[textMatcher]('Announcement'))).toExist(); + }); + + it('should have edit button', async () => { + await expect(element(by.id('room-info-view-edit-button'))).toExist(); + }); + }); + + describe('Render Edit', () => { + beforeAll(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 () => { + await swipe('up'); + 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(); + }); + + afterAll(async () => { + await swipe('down'); + }); + }); + + describe('Usage', () => { + 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); + await swipe('up'); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + await tapBack(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); + const matcher = device.getPlatform() === 'android' ? 'toHaveText' : 'toHaveLabel'; + await expect(element(by.id('room-info-view-name')))[matcher](`${privateRoomName}new`); + // change name to original + await element(by.id('room-info-view-edit-button')).tap(); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); + await sleep(2000); + await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + await swipe('down'); + }); + + it('should reset form', async () => { + await sleep(2000); + await element(by.id('room-info-edit-view-name')).replaceText('abc'); + await element(by.id('room-info-edit-view-description')).replaceText('abc'); + await element(by.id('room-info-edit-view-topic')).replaceText('abc'); + await element(by.id('room-info-edit-view-announcement')).replaceText('abc'); + await element(by.id('room-info-edit-view-password')).replaceText('abc'); + await element(by.id('room-info-edit-view-t')).tap(); + await swipe('up'); + await element(by.id('room-info-edit-view-ro')).longPress(); // https://github.com/facebook/react-native/issues/28032 + await element(by.id('room-info-edit-view-react-when-ro')).tap(); + await swipe('up'); + await element(by.id('room-info-edit-view-reset')).tap(); + // after reset + await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.5); + await expect(element(by.id('room-info-edit-view-name'))).toHaveText(privateRoomName); + await expect(element(by.id('room-info-edit-view-description'))).toHaveText(''); + await expect(element(by.id('room-info-edit-view-topic'))).toHaveText(''); + await expect(element(by.id('room-info-edit-view-announcement'))).toHaveText(''); + await expect(element(by.id('room-info-edit-view-password'))).toHaveText(''); + // await swipe('down'); + await expect(element(by.id('room-info-edit-view-t'))).toHaveToggleValue(true); + await expect(element(by.id('room-info-edit-view-ro'))).toHaveToggleValue(false); + await expect(element(by.id('room-info-edit-view-react-when-ro'))).toBeNotVisible(); + await swipe('down'); + }); + + it('should change room description', async () => { + await element(by.id('room-info-edit-view-description')).replaceText('new description'); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + await tapBack(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); + await expect(element(by[textMatcher]('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(); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); + await sleep(2000); + await element(by.id('room-info-edit-view-topic')).replaceText('new topic'); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + await tapBack(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); + await expect(element(by[textMatcher]('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(); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); + await sleep(2000); + await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement'); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + await tapBack(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); + await expect(element(by[textMatcher]('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(); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); + await sleep(2000); + await element(by.id('room-info-edit-view-password')).replaceText('password'); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + }); + + it('should change room type', async () => { + await swipe('down'); + await element(by.id('room-info-edit-view-t')).tap(); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + await swipe('down'); + await element(by.id('room-info-edit-view-t')).tap(); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-submit')).tap(); + await waitForToast(); + }); + + 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(); + await waitFor(element(by[textMatcher]('Yes, archive it!'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Yes, archive it!').and(by.type(alertButtonType))).tap(); + await waitForToast(); + // await waitFor(element(by.id('room-info-edit-view-unarchive'))) + // .toExist() + // .withTimeout(60000); + // await expect(element(by.id('room-info-edit-view-archive'))).toBeNotVisible(); + }); + + it('should delete room', async () => { + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-delete')).tap(); + await waitFor(element(by[textMatcher]('Yes, delete it!'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Yes, delete it!').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${privateRoomName}`))) + .toBeNotVisible() + .withTimeout(60000); + }); + }); + }); +}); diff --git a/e2e/tests/room/09-jumptomessage.spec.ts b/e2e/tests/room/09-jumptomessage.spec.ts new file mode 100644 index 000000000..0c327de25 --- /dev/null +++ b/e2e/tests/room/09-jumptomessage.spec.ts @@ -0,0 +1,266 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, tapBack, login, searchRoom, sleep, platformTypes, TTextMatcher } from '../../helpers/app'; + +async function navigateToRoom(roomName: string) { + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${roomName}`))) + .toExist() + .withTimeout(5000); +} + +let textMatcher: TTextMatcher; +let alertButtonType: string; + +async function clearCache() { + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('rooms-list-view-sidebar')).tap(); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('sidebar-settings')).tap(); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('settings-view-clear-cache')).tap(); + await waitFor(element(by[textMatcher]('This will clear all your offline data.'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('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-jumping'))) + .toExist() + .withTimeout(10000); +} + +async function waitForLoading() { + if (device.getPlatform() === 'android') { + await sleep(10000); + return; // FIXME: Loading indicator doesn't animate properly on android + } + await waitFor(element(by.id('loading-image'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('loading-image'))) + .toBeNotVisible() + .withTimeout(10000); +} + +describe('Room', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.adminUser, data.adminPassword); + }); + + it('should jump to an old message and load its surroundings', async () => { + await navigateToRoom('jumping'); + await waitFor(element(by[textMatcher]('295'))) + .toExist() + .withTimeout(5000); + await sleep(2000); + await element(by[textMatcher]('1')).atIndex(0).tap(); + await waitForLoading(); + await waitFor(element(by[textMatcher]('1')).atIndex(0)) + .toExist() + .withTimeout(10000); + await expect(element(by[textMatcher]('2'))).toExist(); + }); + + it('should tap FAB and scroll to bottom', async () => { + await waitFor(element(by.id('nav-jump-to-bottom'))) + .toExist() + .withTimeout(15000); + await element(by.id('nav-jump-to-bottom')).tap(); + await waitFor(element(by[textMatcher]("Go to jumping-thread's thread"))) + .toExist() + .withTimeout(15000); + await clearCache(); + }); + + it('should load messages on scroll', async () => { + await navigateToRoom('jumping'); + await waitFor(element(by.id('room-view-messages'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by[textMatcher]('300'))) + .toExist() + .withTimeout(5000); + let found = false; + while (!found) { + try { + const direction = device.getPlatform() === 'android' ? 'down' : 'up'; + await element(by.id('room-view-messages')).scroll(500, direction); + await expect(element(by[textMatcher]('249'))).toExist(); + found = true; + } catch { + // + } + } + await clearCache(); + }); + + it('should search for old message and load its surroundings', async () => { + await navigateToRoom('jumping'); + await sleep(1000); // wait for proper load the room + await element(by.id('room-view-search')).tap(); + await waitFor(element(by.id('search-messages-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('search-message-view-input')).replaceText('30'); + await waitFor(element(by[textMatcher]('30')).atIndex(1)) + .toExist() + .withTimeout(30000); + await sleep(1000); + await element(by[textMatcher]('30')).atIndex(1).tap(); + await waitForLoading(); + await waitFor(element(by[textMatcher]('30')).atIndex(0)) + .toExist() + .withTimeout(30000); + await expect(element(by[textMatcher]('31'))).toExist(); + await expect(element(by[textMatcher]('32'))).toExist(); + }); + + it('should load newer and older messages', async () => { + // TODO: couldn't make it work on Android :( + if (device.getPlatform() === 'android') { + return; + } + let found = false; + while (!found) { + try { + // it doesn't recognize this list + await element(by.id('room-view-messages')).scroll(500, 'up'); + await expect(element(by[textMatcher]('Load Older'))).toBeVisible(); + await expect(element(by[textMatcher]('5'))).toExist(); + found = true; + } catch { + // + } + } + await element(by[textMatcher]('Load Older')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('4'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-view-messages')).atIndex(0).swipe('down', 'fast', 0.5); + await waitFor(element(by[textMatcher]('1'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5); + await waitFor(element(by[textMatcher]('25'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5); + await waitFor(element(by[textMatcher]('50'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'slow', 0.4); + await waitFor(element(by[textMatcher]('Load Newer'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Load Newer')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('104'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by[textMatcher]('Load Newer'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Load Newer')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('154'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by[textMatcher]('Load Newer'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Load Newer')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('Load Newer'))) + .toNotExist() + .withTimeout(5000); + await expect(element(by[textMatcher]('Load More'))).toNotExist(); + await expect(element(by[textMatcher]('201'))).toExist(); + await expect(element(by[textMatcher]('202'))).toExist(); + await tapBack(); + }); +}); + +const expectThreadMessages = async (message: string) => { + await waitFor(element(by.id('room-view-title-thread 1'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by[textMatcher](message)).atIndex(0)) + .toExist() + .withTimeout(10000); + await element(by[textMatcher](message)).atIndex(0).tap(); +}; + +describe('Threads', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true }); + }); + + it('should navigate to a thread from another room', async () => { + await navigateToRoom('jumping'); + await waitFor(element(by[textMatcher]("Go to jumping-thread's thread")).atIndex(0)) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]("Go to jumping-thread's thread")).atIndex(0).tap(); + await expectThreadMessages("Go to jumping-thread's thread"); + await tapBack(); + }); + + it('should tap on thread message from main room', async () => { + await waitFor(element(by.id('room-view-title-jumping-thread'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by[textMatcher]('thread message sent to main room'))) + .toExist() + .withTimeout(10000); + await element(by[textMatcher]('thread message sent to main room')).atIndex(0).tap(); + await expectThreadMessages('thread message sent to main room'); + await tapBack(); + }); + + it('should tap on quote', async () => { + await waitFor(element(by.id('room-view-title-jumping-thread'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by[textMatcher]('quoted'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('quoted')).atIndex(0).tap(); + await expectThreadMessages('quoted'); + await tapBack(); + }); + + it('should jump from search message', async () => { + await waitFor(element(by.id('room-view-title-jumping-thread'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-view-search')).atIndex(0).tap(); + await waitFor(element(by.id('search-messages-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('search-message-view-input')).replaceText('to be searched'); + await waitFor(element(by[textMatcher]('to be searched')).atIndex(1)) + .toExist() + .withTimeout(30000); + await element(by[textMatcher]('to be searched')).atIndex(1).tap(); + await expectThreadMessages('to be searched'); + }); + + // TODO: Threads pagination +}); diff --git a/e2e/tests/room/10-ignoreuser.spec.ts b/e2e/tests/room/10-ignoreuser.spec.ts new file mode 100644 index 000000000..91f9e8401 --- /dev/null +++ b/e2e/tests/room/10-ignoreuser.spec.ts @@ -0,0 +1,104 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, searchRoom, sleep, platformTypes, TTextMatcher, tapBack } from '../../helpers/app'; +import { sendMessage } from '../../helpers/data_setup'; + +async function navigateToRoom(user: string) { + await searchRoom(`${user}`); + await element(by.id(`rooms-list-view-item-${user}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); +} + +async function navigateToInfoView() { + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-actions-info')).tap(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); +} + +describe('Ignore/Block User', () => { + const user = data.users.alternate.username; + let textMatcher: TTextMatcher; + + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Usage', () => { + describe('Block user from DM', () => { + it('should go to user info view', async () => { + await navigateToRoom(user); + await navigateToInfoView(); + }); + it('should block user', async () => { + await waitFor(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Block user')))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('room-info-view-ignore')).tap(); + await waitFor(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Unblock user')))) + .toExist() + .withTimeout(2000); + await tapBack(); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); + await tapBack(); + await expect(element(by[textMatcher]('This room is blocked'))).toExist(); + }); + + it('should unblock user', async () => { + await navigateToInfoView(); + await element(by.id('room-info-view-ignore')).tap(); + await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Block user')))).toExist(); + await tapBack(); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); + await tapBack(); + await expect(element(by.id('messagebox'))).toBeVisible(); + await tapBack(); + }); + }); + describe('Ignore user from Message', () => { + it('should ignore user from message', async () => { + const channelName = data.userRegularChannels.detoxpublic.name; + await navigateToRoom(channelName); + await sleep(300); + await sendMessage(data.users.alternate, channelName, 'message-01'); + await sendMessage(data.users.alternate, channelName, 'message-02'); + await waitFor(element(by[textMatcher](user)).atIndex(0)) + .toExist() + .withTimeout(30000); + await sleep(300); + await element(by[textMatcher](user)).atIndex(0).tap(); + await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Ignore')))).toExist(); + await element(by.id('room-info-view-ignore')).tap(); + await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Unignore')))).toExist(); + await tapBack(); + }); + it('should tap to display message', async () => { + await expect(element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0)).toExist(); + await element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0).tap(); + await waitFor(element(by[textMatcher](user))) + .toBeVisible() + .withTimeout(1000); + await element(by[textMatcher](user)).atIndex(0).tap(); + await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Unignore')))).toExist(); + await element(by.id('room-info-view-ignore')).tap(); + await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Ignore')))).toExist(); + await tapBack(); + await expect(element(by[textMatcher]('message-02')).atIndex(0)).toBeVisible(); + }); + }); + }); +}); diff --git a/e2e/tests/team/01-createteam.spec.ts b/e2e/tests/team/01-createteam.spec.ts new file mode 100644 index 000000000..af9b4d4b9 --- /dev/null +++ b/e2e/tests/team/01-createteam.spec.ts @@ -0,0 +1,110 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, platformTypes, TTextMatcher } from '../../helpers/app'; + +const teamName = `team-${data.random}`; + +describe('Create team screen', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('New Message', () => { + beforeAll(async () => { + await waitFor(element(by.id('rooms-list-view-create-channel'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('rooms-list-view-create-channel')).tap(); + }); + + it('should have team button', async () => { + await waitFor(element(by.id('new-message-view-create-team'))) + .toBeVisible() + .withTimeout(2000); + }); + + it('should navigate to select users', async () => { + await element(by.id('new-message-view-create-team')).tap(); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); + }); + }); + + 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', () => { + describe('Usage', () => { + it('should get invalid team name', async () => { + await element(by.id('create-channel-name')).replaceText(`${data.teams.private.name}`); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(2000); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by[textMatcher]('OK').and(by.type(alertButtonType)))) + .toBeVisible() + .withTimeout(5000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + }); + + it('should create private team', async () => { + await element(by.id('create-channel-name')).replaceText(''); + await element(by.id('create-channel-name')).replaceText(teamName); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(2000); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); + await expect(element(by.id('room-view'))).toExist(); + 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', () => { + 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); + await element(by.id('room-actions-info')).tap(); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); + }); + + it('should delete team', async () => { + await element(by.id('room-info-view-edit-button')).tap(); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-delete')).tap(); + await waitFor(element(by[textMatcher]('Yes, delete it!'))) + .toExist() + .withTimeout(5000); + await element(by[textMatcher]('Yes, delete it!').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${teamName}`))) + .toBeNotVisible() + .withTimeout(60000); + }); + }); +}); diff --git a/e2e/tests/team/02-team.spec.ts b/e2e/tests/team/02-team.spec.ts new file mode 100644 index 000000000..6a16b452d --- /dev/null +++ b/e2e/tests/team/02-team.spec.ts @@ -0,0 +1,491 @@ +import { expect } from 'detox'; + +import data from '../../data'; +import { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app'; + +async function navigateToRoom(roomName: string) { + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); +} + +async function openActionSheet(username: string) { + 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'); +} + +async function navigateToRoomActions() { + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); +} + +async function backToActions() { + await tapBack(); + 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 waitFor(element(by.id('action-sheet-handle'))) + .toBeNotVisible() + .withTimeout(3000); + await sleep(200); +} + +async function waitForToast() { + await sleep(1000); +} + +async function swipeTillVisible( + container: Detox.NativeMatcher, + find: Detox.NativeMatcher, + direction: Detox.Direction = 'up', + delta = 0.3, + speed: Detox.Speed = 'slow' +) { + let found = false; + while (!found) { + try { + await element(container).swipe(direction, speed, delta); + await sleep(200); + await expect(element(find)).toBeVisible(); + found = true; + } catch (e) { + // + } + } +} + +describe('Team', () => { + const team = data.teams.private.name; + const user = data.users.alternate; + const room = `private${data.random}-channel-team`; + const existingRoom = data.groups.alternate.name; + let alertButtonType: string; + let textMatcher: TTextMatcher; + + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + await navigateToRoom(team); + }); + + 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 () => { + await expect(element(by.id('room-view-header-team-channels'))).toExist(); + }); + + it('should have threads button ', async () => { + await expect(element(by.id('room-view-header-threads'))).toExist(); + }); + + it('should have threads button ', async () => { + await expect(element(by.id('room-view-search'))).toExist(); + }); + }); + + 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', () => { + it('should have actions button ', async () => { + await expect(element(by.id('room-header')).atIndex(0)).toExist(); + }); + + it('should have team channels button ', async () => { + await expect(element(by.id('team-channels-view-create'))).toExist(); + }); + + it('should have threads button ', async () => { + await expect(element(by.id('team-channels-view-search'))).toExist(); + }); + }); + + 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); + }); + + it('should have create new button', async () => { + await waitFor(element(by.id('add-channel-team-view-create-channel'))) + .toExist() + .withTimeout(5000); + }); + + it('should add existing button', async () => { + await waitFor(element(by.id('add-channel-team-view-add-existing'))) + .toExist() + .withTimeout(5000); + }); + }); + + 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'); + await waitFor(element(by.id('select-users-view-item-rocket.cat'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('select-users-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('selected-users-view-submit')).tap(); + + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); + await element(by.id('create-channel-name')).replaceText(''); + await element(by.id('create-channel-name')).replaceText(room); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(10000); + await element(by.id('create-channel-submit')).tap(); + + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); + await expect(element(by.id('room-view'))).toExist(); + await expect(element(by.id('room-view-header-team-channels'))).toExist(); + await element(by.id('room-view-header-team-channels')).tap(); + + await waitFor(element(by.id('team-channels-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(6000); + await expect(element(by.id(`rooms-list-view-item-${room}`))).toExist(); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); + await expect(element(by.id('room-view-header-team-channels')).atIndex(0)).toExist(); + await expect(element(by.id('room-view-header-threads')).atIndex(0)).toExist(); + await expect(element(by.id('room-view-search')).atIndex(0)).toExist(); + await tapBack(); + }); + + it('should add existing channel to team', async () => { + await navigateToRoom(team); + await waitFor(element(by.id('room-view-header-team-channels'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-view-header-team-channels')).tap(); + await waitFor(element(by.id('team-channels-view'))) + .toExist() + .withTimeout(5000); + 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 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); + await element(by.id('add-existing-channel-view-submit')).tap(); + + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); + await expect(element(by.id('room-view'))).toExist(); + await expect(element(by.id('room-view-header-team-channels'))).toExist(); + await element(by.id('room-view-header-team-channels')).tap(); + + await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`)).atIndex(0)) + .toExist() + .withTimeout(10000); + }); + + it('should activate/deactivate auto-join to channel', async () => { + await element(by.id(`rooms-list-view-item-${existingRoom}`)) + .atIndex(0) + .longPress(); + await sleep(500); + await swipeTillVisible(by.id('action-sheet-remove-from-team'), by.id('action-sheet-delete')); + await waitFor(element(by.id('action-sheet-auto-join'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('auto-join-unchecked'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('action-sheet-remove-from-team'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('action-sheet-delete'))) + .toBeVisible() + .withTimeout(5000); + + await element(by.id('auto-join-unchecked')).tap(); + await waitFor(element(by.id('auto-join-tag'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-view-item-${existingRoom}`)) + .atIndex(0) + .longPress(); + + await waitFor(element(by.id('auto-join-checked'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('auto-join-checked')).tap(); + await waitFor(element(by.id('auto-join-tag'))) + .toBeNotVisible() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`)).atIndex(0)) + .toExist() + .withTimeout(6000); + }); + }); + + describe('Team actions', () => { + beforeAll(async () => { + await tapBack(); + await navigateToRoomActions(); + }); + + it('should add users to the team', async () => { + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); + + await waitFor(element(by.id('room-actions-add-user'))) + .toExist() + .withTimeout(10000); + await element(by.id('room-actions-add-user')).tap(); + + const rocketCat = 'rocket.cat'; + await element(by.id('select-users-view-search')).replaceText('rocket.cat'); + await waitFor(element(by.id(`select-users-view-item-${rocketCat}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${rocketCat}`)).tap(); + await waitFor(element(by.id(`selected-user-${rocketCat}`))) + .toExist() + .withTimeout(5000); + + await waitFor(element(by.id('select-users-view-search'))) + .toExist() + .withTimeout(4000); + await element(by.id('select-users-view-search')).tap(); + await element(by.id('select-users-view-search')).replaceText(user.username); + await waitFor(element(by.id(`select-users-view-item-${user.username}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${user.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${user.username}`))) + .toExist() + .withTimeout(5000); + + await element(by.id('selected-users-view-submit')).tap(); + await sleep(300); + await tapBack(); + await sleep(300); + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(10000); + await element(by.id('room-actions-members')).tap(); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + await backToActions(); + }); + + it('should try to leave to leave team and raise alert', async () => { + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-leave-channel'))) + .toExist() + .withTimeout(2000); + 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[textMatcher]( + 'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.' + ) + ) + ) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).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[textMatcher]('Last owner cannot be removed'))) + .toExist() + .withTimeout(8000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); + await tapBack(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(2000); + }); + + describe('Room Members', () => { + beforeAll(async () => { + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); + }); + + it('should show all users', async () => { + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + }); + + it('should filter user', async () => { + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + await element(by.id('room-members-view-search')).replaceText('rocket'); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toBeNotVisible() + .withTimeout(60000); + await element(by.id('room-members-view-search')).tap(); + await element(by.id('room-members-view-search')).clearText(); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); + }); + + it('should remove member from team', async () => { + await openActionSheet('rocket.cat'); + await waitFor(element(by.id('action-sheet-remove-from-team'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('action-sheet-remove-from-team')).tap(); + await waitFor(element(by.id('select-list-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`select-list-view-item-${room}`))) + .toExist() + .withTimeout(5000); + await element(by.id(`select-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`${room}-checked`))) + .toExist() + .withTimeout(5000); + await element(by.id(`select-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`${room}-checked`))) + .toNotExist() + .withTimeout(5000); + await element(by.id('select-list-view-submit')).tap(); + await waitFor(element(by.id('room-members-view-item-rocket.cat'))) + .toBeNotVisible() + .withTimeout(60000); + }); + + it('should set member as owner', async () => { + await openActionSheet(user.username); + await element(by.id('action-sheet-set-owner')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-owner-checked'))) + .toBeVisible() + .withTimeout(6000); + await closeActionSheet(); + }); + + it('should leave team', async () => { + await tapBack(); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-leave-channel'))) + .toExist() + .withTimeout(2000); + 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[textMatcher]( + 'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.' + ) + ) + ) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('OK').and(by.type(alertButtonType))).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.id(`rooms-list-view-item-${team}`))) + .toBeNotVisible() + .withTimeout(60000); + }); + }); + }); + }); +}); diff --git a/e2e/tests/team/03-moveconvert.spec.ts b/e2e/tests/team/03-moveconvert.spec.ts new file mode 100644 index 000000000..b3cc33d92 --- /dev/null +++ b/e2e/tests/team/03-moveconvert.spec.ts @@ -0,0 +1,189 @@ +import data from '../../data'; +import { navigateToLogin, login, tapBack, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app'; + +const toBeConverted = `to-be-converted-${data.random}`; +const toBeMoved = `to-be-moved-${data.random}`; + +const createChannel = async (room: string) => { + await waitFor(element(by.id('rooms-list-view-create-channel'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('selected-users-view-submit')).tap(); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); + await element(by.id('create-channel-name')).replaceText(room); + await waitFor(element(by.id('create-channel-submit'))) + .toExist() + .withTimeout(10000); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); + 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: string) { + await searchRoom(`${room}`); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); +} + +async function navigateToRoomActions(room: string) { + await navigateToRoom(room); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); +} + +describe('Move/Convert Team', () => { + let alertButtonType: string; + let textMatcher: TTextMatcher; + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Convert', () => { + beforeAll(async () => { + await createChannel(toBeConverted); + }); + + it('should convert channel to a team', async () => { + await navigateToRoomActions(toBeConverted); + 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[textMatcher]('You are converting this Channel to a Team. All Members will be kept.'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('Convert').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); + await waitFor(element(by.id(`room-view-title-${toBeConverted}`))) + .toExist() + .withTimeout(6000); + }); + + afterAll(async () => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); + }); + }); + + describe('Move', () => { + beforeAll(async () => { + await createChannel(toBeMoved); + }); + + it('should move channel to a team', async () => { + await navigateToRoomActions(toBeMoved); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-move-to-team'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-move-to-team')).tap(); + await waitFor(element(by[textMatcher]('Move to Team')).atIndex(0)) + .toExist() + .withTimeout(2000); + 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[textMatcher]('Select Team'))) + .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 element(by.id('select-list-view-submit')).atIndex(0).tap(); + await waitFor( + element( + by[textMatcher]( + '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[textMatcher]('Yes, move it!').and(by.type(alertButtonType))).tap(); + await waitFor(element(by.id('room-view-header-team-channels'))) + .toExist() + .withTimeout(10000); + }); + + afterAll(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[textMatcher]('Convert to Channel'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('Convert to Channel')).atIndex(0).tap(); + await waitFor(element(by[textMatcher]('Converting Team to Channel'))) + .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[textMatcher]('You are converting this Team to a Channel'))) + .toExist() + .withTimeout(2000); + await element(by[textMatcher]('Convert').and(by.type(alertButtonType))).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/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 000000000..1c51cdad0 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "jsx": "react", + "target": "es2018", + "module": "commonjs", + "importHelpers": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": ["node", "detox", "mocha"] + }, + "include": ["./**/*.ts"], + "exclude": ["../node_modules"] +}