[TESTS] Update and separate E2E tests (#2126)

* Tests passing until roomslist

* create room

* roominfo

* change server

* broadcast

* profile

* custom status

* forgot password

* working

* room and onboarding

* Tests separated

* config.yml refactor

* Revert "config.yml refactor"

This reverts commit 0e984d3029.

* CI

* lint

* CI refactor

* Onboarding tests

* npx detox

* Add all tests

* Save brew cache

* mac-env executor

* detox-test command

* Update readme

* Remove folder
This commit is contained in:
Diego Mello 2020-05-20 13:33:40 -03:00 committed by GitHub
parent c46dcfa9e6
commit d1e751bf12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 713 additions and 710 deletions

View File

@ -1,7 +1,127 @@
defaults: &defaults
working_directory: ~/repo
version: 2
macos: &macos
macos:
xcode: "11.2.1"
bash-env: &bash-env
BASH_ENV: "~/.nvm/nvm.sh"
install-npm-modules: &install-npm-modules
name: Install NPM modules
command: yarn
restore-npm-cache-linux: &restore-npm-cache-linux
name: Restore NPM cache
key: node-modules-{{ checksum "yarn.lock" }}
save-npm-cache-linux: &save-npm-cache-linux
key: node-modules-{{ checksum "yarn.lock" }}
name: Save NPM cache
paths:
- ./node_modules
restore-npm-cache-mac: &restore-npm-cache-mac
name: Restore NPM cache
key: node-v1-mac-{{ checksum "yarn.lock" }}
save-npm-cache-mac: &save-npm-cache-mac
key: node-v1-mac-{{ checksum "yarn.lock" }}
name: Save NPM cache
paths:
- ./node_modules
install-node: &install-node
name: Install Node 10
command: |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
source ~/.nvm/nvm.sh
# https://github.com/creationix/nvm/issues/1394
set +e
nvm install 10
echo 'export PATH="/home/circleci/.nvm/versions/node/v10.20.1/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
restore-gems-cache: &restore-gems-cache
name: Restore gems cache
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}
save-gems-cache: &save-gems-cache
name: Save gems cache
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}
paths:
- vendor/bundle
update-fastlane: &update-fastlane
name: Update Fastlane
command: |
echo "ruby-2.6.4" > ~/.ruby-version
bundle install
working_directory: ios
restore-brew-cache: &restore-brew-cache
name: Restore Brew cache
key: brew-{{ checksum "yarn.lock" }}-{{ checksum ".circleci/config.yml" }}
save-brew-cache: &save-brew-cache
name: Save brew cache
key: brew-{{ checksum "yarn.lock" }}-{{ checksum ".circleci/config.yml" }}
paths:
- /usr/local/Homebrew
install-apple-sim-utils: &install-apple-sim-utils
name: Install appleSimUtils
command: |
brew update
brew tap wix/brew
brew install wix/brew/applesimutils
rebuild-detox: &rebuild-detox
name: Rebuild Detox framework cache
command: |
npx detox clean-framework-cache
npx detox build-framework-cache
version: 2.1
# EXECUTORS
executors:
mac-env:
<<: *macos
environment:
<<: *bash-env
# COMMANDS
commands:
detox-test:
parameters:
folder:
type: string
steps:
- checkout
- attach_workspace:
at: .
- restore_cache: *restore-npm-cache-mac
- restore_cache: *restore-brew-cache
- run: *install-node
- run: *install-apple-sim-utils
- run: *install-npm-modules
- run: *rebuild-detox
- run:
name: Test
command: |
npx detox test << parameters.folder >> --configuration ios.sim.release --cleanup
# JOBS
jobs:
lint-testunit:
<<: *defaults
@ -14,14 +134,9 @@ jobs:
steps:
- checkout
- restore_cache:
name: Restore NPM cache
key: node-modules-{{ checksum "yarn.lock" }}
- restore_cache: *restore-npm-cache-linux
- run:
name: Install NPM modules
command: |
yarn
- run: *install-npm-modules
- run:
name: Lint
@ -38,162 +153,79 @@ jobs:
command: |
yarn codecov
- save_cache:
key: node-modules-{{ checksum "yarn.lock" }}
name: Save NPM cache
paths:
- ./node_modules
- save_cache: *save-npm-cache-linux
# E2E
e2e-build:
macos:
xcode: "11.2.1"
environment:
BASH_ENV: "~/.nvm/nvm.sh"
executor: mac-env
steps:
- checkout
- restore_cache:
name: Restore NPM cache
key: node-v1-mac-{{ checksum "yarn.lock" }}
- restore_cache: *restore-npm-cache-mac
- run:
name: Install Node 10
command: |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
source ~/.nvm/nvm.sh
# https://github.com/creationix/nvm/issues/1394
set +e
nvm install 10
- restore_cache: *restore-brew-cache
- run:
name: Install appleSimUtils
command: |
brew update
brew tap wix/brew
brew install wix/brew/applesimutils
- run: *install-node
- run:
name: Install NPM modules
command: |
yarn global add detox-cli
yarn
- run: *install-apple-sim-utils
- run:
name: Rebuild Detox framework cache
command: |
detox clean-framework-cache
detox build-framework-cache
- run: *install-npm-modules
- run: *rebuild-detox
- run:
name: Build
command: |
detox build --configuration ios.sim.release
npx detox build --configuration ios.sim.release
- persist_to_workspace:
root: .
paths:
- ios/build/Build/Products/Release-iphonesimulator/RocketChatRN.app
- save_cache:
name: Save NPM cache
key: node-v1-mac-{{ checksum "yarn.lock" }}
paths:
- node_modules
- save_cache: *save-npm-cache-mac
e2e-test:
macos:
xcode: "11.2.1"
environment:
BASH_ENV: "~/.nvm/nvm.sh"
- save_cache: *save-brew-cache
e2e-test-onboarding:
executor: mac-env
steps:
- checkout
- detox-test:
folder: "./e2e/tests/onboarding"
- attach_workspace:
at: .
e2e-test-room:
executor: mac-env
steps:
- detox-test:
folder: "./e2e/tests/room"
- restore_cache:
name: Restore NPM cache
key: node-v1-mac-{{ checksum "yarn.lock" }}
- run:
name: Install Node 10
command: |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
source ~/.nvm/nvm.sh
# https://github.com/creationix/nvm/issues/1394
set +e
nvm install 10
- run:
name: Install appleSimUtils
command: |
brew update
brew tap wix/brew
brew install wix/brew/applesimutils
- run:
name: Install NPM modules
command: |
yarn global add detox-cli
yarn
- run:
name: Rebuild Detox framework cache
command: |
detox clean-framework-cache
detox build-framework-cache
- run:
name: Test
command: |
detox test --configuration ios.sim.release --cleanup
- save_cache:
name: Save NPM cache
key: node-v1-mac-{{ checksum "yarn.lock" }}
paths:
- node_modules
e2e-test-assorted:
executor: mac-env
steps:
- detox-test:
folder: "./e2e/tests/assorted"
# Android builds
android-build:
<<: *defaults
docker:
- image: circleci/android:api-28-node
environment:
# GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"
# GRADLE_OPTS: -Xmx2048m -Dorg.gradle.daemon=false
# JVM_OPTS: -Xmx4096m
JAVA_OPTS: '-Xms512m -Xmx2g'
GRADLE_OPTS: '-Xmx3g -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx2g -XX:+HeapDumpOnOutOfMemoryError"'
TERM: dumb
BASH_ENV: "~/.nvm/nvm.sh"
<<: *bash-env
steps:
- checkout
- run:
name: Install Node 10
command: |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
source ~/.nvm/nvm.sh
# https://github.com/creationix/nvm/issues/1394
set +e
nvm install 10
echo 'export PATH="/home/circleci/.nvm/versions/node/v10.20.1/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
- run: *install-node
- restore_cache:
name: Restore NPM cache
key: node-modules-{{ checksum "yarn.lock" }}
- restore_cache: *restore-npm-cache-linux
- run:
name: Install NPM modules
command: |
yarn
- run: *install-npm-modules
- restore_cache:
name: Restore gradle cache
@ -261,11 +293,7 @@ jobs:
- store_artifacts:
path: /tmp/build/outputs
- save_cache:
name: Save NPM cache
key: node-modules-{{ checksum "yarn.lock" }}
paths:
- ./node_modules
- save_cache: *save-npm-cache-linux
- save_cache:
name: Save gradle cache
@ -273,44 +301,22 @@ jobs:
paths:
- ~/.gradle
# iOS builds
ios-build:
macos:
xcode: "11.2.1"
environment:
BASH_ENV: "~/.nvm/nvm.sh"
executor: mac-env
steps:
- checkout
- restore_cache:
name: Restore gems cache
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}
- restore_cache: *restore-gems-cache
- restore_cache:
name: Restore NPM cache
key: node-v1-mac-{{ checksum "yarn.lock" }}
- restore_cache: *restore-npm-cache-mac
- run:
name: Install Node 10
command: |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
source ~/.nvm/nvm.sh
# https://github.com/creationix/nvm/issues/1394
set +e
nvm install 10
- run: *install-node
- run:
name: Install NPM modules
command: |
yarn
- run: *install-npm-modules
- run:
name: Update Fastlane
command: |
echo "ruby-2.6.4" > ~/.ruby-version
bundle install
working_directory: ios
- run: *update-fastlane
- run:
name: Set Google Services
@ -348,17 +354,9 @@ jobs:
fi
working_directory: ios
- save_cache:
name: Save NPM cache
key: node-v1-mac-{{ checksum "yarn.lock" }}
paths:
- node_modules
- save_cache: *save-npm-cache-mac
- save_cache:
name: Save gems cache
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}
paths:
- vendor/bundle
- save_cache: *save-gems-cache
- store_artifacts:
path: ios/RocketChatRN.ipa
@ -370,8 +368,7 @@ jobs:
- ios/fastlane/report.xml
ios-testflight:
macos:
xcode: "11.2.1"
executor: mac-env
steps:
- checkout
@ -379,16 +376,9 @@ jobs:
- attach_workspace:
at: ios
- restore_cache:
name: Restore gems cache
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}
- restore_cache: *restore-gems-cache
- run:
name: Update Fastlane
command: |
echo "ruby-2.4" > ~/.ruby-version
bundle install
working_directory: ios
- run: *update-fastlane
- run:
name: Fastlane Tesflight Upload
@ -396,14 +386,9 @@ jobs:
bundle exec fastlane ios beta
working_directory: ios
- save_cache:
name: Save gems cache
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}
paths:
- vendor/bundle
- save_cache: *save-gems-cache
workflows:
version: 2
build-and-test:
jobs:
- lint-testunit
@ -415,7 +400,13 @@ workflows:
- e2e-build:
requires:
- e2e-hold
- e2e-test:
- e2e-test-onboarding:
requires:
- e2e-build
- e2e-test-room:
requires:
- e2e-build
- e2e-test-assorted:
requires:
- e2e-build

View File

@ -208,13 +208,15 @@ Readme will guide you on how to config.
- Build your app
```bash
$ detox build --configuration ios.sim.release
$ npx detox build --configuration ios.sim.release
```
- Run tests
```bash
$ detox test --configuration ios.sim.release
$ npx detox test ./e2e/tests/onboarding --configuration ios.sim.release
$ npx detox test ./e2e/tests/room --configuration ios.sim.release
$ npx detox test ./e2e/tests/assorted --configuration ios.sim.release
```
## Storybook

View File

@ -23,7 +23,7 @@ export const FormContainerInner = ({ children }) => (
</View>
);
const FormContainer = ({ children, theme }) => (
const FormContainer = ({ children, theme, testID }) => (
<KeyboardView
style={{ backgroundColor: themes[theme].backgroundColor }}
contentContainerStyle={sharedStyles.container}
@ -31,7 +31,7 @@ const FormContainer = ({ children, theme }) => (
>
<StatusBar theme={theme} />
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
<SafeAreaView style={sharedStyles.container} forceInset={{ top: 'never' }}>
<SafeAreaView style={sharedStyles.container} forceInset={{ top: 'never' }} testID={testID}>
{children}
<AppVersion theme={theme} />
</SafeAreaView>
@ -41,6 +41,7 @@ const FormContainer = ({ children, theme }) => (
FormContainer.propTypes = {
theme: PropTypes.string,
testID: PropTypes.string,
children: PropTypes.element
};

View File

@ -92,7 +92,7 @@ const TwoFactor = React.memo(({ theme, split }) => {
isVisible={visible}
hideModalContentWhileAnimating
>
<View style={styles.container}>
<View style={styles.container} testID='two-factor'>
<View style={[styles.content, split && [sharedStyles.modal, sharedStyles.modalFormSheet], { backgroundColor: themes[theme].backgroundColor }]}>
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
@ -106,6 +106,7 @@ const TwoFactor = React.memo(({ theme, split }) => {
keyboardType={method?.keyboardType}
secureTextEntry={method?.secureTextEntry}
error={data.invalid && { error: 'totp-invalid', reason: I18n.t('Code_or_password_invalid') }}
testID='two-factor-input'
/>
{isEmail && <Text style={[styles.sendEmail, { color }]} onPress={sendEmail}>{I18n.t('Send_me_the_code_again')}</Text>}
<View style={styles.buttonContainer}>
@ -123,6 +124,7 @@ const TwoFactor = React.memo(({ theme, split }) => {
style={styles.button}
onPress={onSubmit}
theme={theme}
testID='two-factor-send'
/>
</View>
</View>

View File

@ -27,10 +27,12 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
rid: subscriptions[0].rid, name: username, fname: name, message
});
} else {
const room = yield RocketChat.createDirectMessage(username);
yield goRoom({
rid: room.rid, name: username, fname: name, message
});
const result = yield RocketChat.createDirectMessage(username);
if (result?.success) {
yield goRoom({
rid: result?.room.rid, t: 'd', name: username, fname: name, message
});
}
}
} catch (e) {
log(e);

View File

@ -85,7 +85,7 @@ class ForgotPasswordView extends React.Component {
const { theme } = this.props;
return (
<FormContainer theme={theme}>
<FormContainer theme={theme} testID='forgot-password-view'>
<FormContainerInner>
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Forgot_password')}</Text>
<TextInput

View File

@ -201,7 +201,7 @@ class LoginView extends React.Component {
render() {
const { Accounts_ShowFormLogin, theme } = this.props;
return (
<FormContainer theme={theme}>
<FormContainer theme={theme} testID='login-view'>
<FormContainerInner>
<LoginServices separator={Accounts_ShowFormLogin} />
{this.renderUserForm()}

View File

@ -69,7 +69,7 @@ class NewServerView extends React.Component {
const previousServer = navigation.getParam('previousServer', null);
const close = navigation.getParam('close', () => {});
return {
headerLeft: previousServer ? <CloseModalButton navigation={navigation} onPress={close} /> : undefined,
headerLeft: previousServer ? <CloseModalButton navigation={navigation} onPress={close} testID='new-server-view-close' /> : undefined,
title: I18n.t('Workspaces'),
...themedHeader(screenProps.theme)
};
@ -297,7 +297,7 @@ class NewServerView extends React.Component {
const { connecting, theme } = this.props;
const { text, connectingOpen } = this.state;
return (
<FormContainer theme={theme}>
<FormContainer theme={theme} testID='new-server-view'>
<FormContainerInner>
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Join_your_workspace')}</Text>
<TextInput
@ -321,8 +321,8 @@ class NewServerView extends React.Component {
disabled={!text || connecting}
loading={!connectingOpen && connecting}
style={styles.connectButton}
testID='new-server-view-button'
theme={theme}
testID='new-server-view-button'
/>
<OrSeparator theme={theme} />
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_join_open_description')}</Text>
@ -334,6 +334,7 @@ class NewServerView extends React.Component {
disabled={connecting}
loading={connectingOpen && connecting}
theme={theme}
testID='new-server-view-open'
/>
</FormContainerInner>
{ isIOS ? this.renderCertificatePicker() : null }

View File

@ -68,7 +68,7 @@ class OnboardingView extends React.Component {
render() {
const { theme } = this.props;
return (
<FormContainer theme={theme}>
<FormContainer theme={theme} testID='onboarding-view'>
<FormContainerInner>
<Image style={styles.onboarding} source={{ uri: 'logo' }} fadeDuration={0} />
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Onboarding_title')}</Text>
@ -80,6 +80,7 @@ class OnboardingView extends React.Component {
type='primary'
onPress={this.connectServer}
theme={theme}
testID='join-workspace'
/>
<Button
title={I18n.t('Create_a_new_workspace')}
@ -87,6 +88,7 @@ class OnboardingView extends React.Component {
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.createWorkspace}
theme={theme}
testID='create-workspace-button'
/>
</View>
</FormContainerInner>

View File

@ -58,7 +58,7 @@ class RegisterView extends React.Component {
return {
...themedHeader(screenProps.theme),
title,
headerRight: <LegalButton navigation={navigation} />
headerRight: <LegalButton navigation={navigation} testID='register-view-more' />
};
}
@ -230,7 +230,7 @@ class RegisterView extends React.Component {
const { saving } = this.state;
const { theme, showLoginButton } = this.props;
return (
<FormContainer theme={theme}>
<FormContainer theme={theme} testID='register-view'>
<FormContainerInner>
<LoginServices />
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text>

View File

@ -12,21 +12,25 @@ const Channel = ({ room, theme }) => {
label={I18n.t('Description')}
content={description || `__${ I18n.t('No_label_provided', { label: 'description' }) }__`}
theme={theme}
testID='room-info-view-description'
/>
<Item
label={I18n.t('Topic')}
content={topic || `__${ I18n.t('No_label_provided', { label: 'topic' }) }__`}
theme={theme}
testID='room-info-view-topic'
/>
<Item
label={I18n.t('Announcement')}
content={announcement || `__${ I18n.t('No_label_provided', { label: 'announcement' }) }__`}
theme={theme}
testID='room-info-view-announcement'
/>
<Item
label={I18n.t('Broadcast_Channel')}
content={room.broadcast && I18n.t('Broadcast_channel_Description')}
theme={theme}
testID='room-info-view-broadcast'
/>
</>
);

View File

@ -6,9 +6,11 @@ import styles from './styles';
import Markdown from '../../containers/markdown';
import { themes } from '../../constants/colors';
const Item = ({ label, content, theme }) => (
const Item = ({
label, content, theme, testID
}) => (
content ? (
<View style={styles.item}>
<View style={styles.item} testID={testID}>
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>{label}</Text>
<Markdown
style={[styles.itemContent, { color: themes[theme].auxiliaryText }]}
@ -21,7 +23,8 @@ const Item = ({ label, content, theme }) => (
Item.propTypes = {
label: PropTypes.string,
content: PropTypes.string,
theme: PropTypes.string
theme: PropTypes.string,
testID: PropTypes.string
};
export default Item;

View File

@ -224,7 +224,12 @@ class Sidebar extends Component {
<View style={styles.headerUsername}>
<Text numberOfLines={1} style={[styles.username, { color: themes[theme].titleText }]}>{useRealName ? user.name : user.username}</Text>
</View>
<Text style={[styles.currentServerText, { color: themes[theme].titleText }]} numberOfLines={1}>{Site_Name}</Text>
<Text
style={[styles.currentServerText, { color: themes[theme].titleText }]}
numberOfLines={1}
accessibilityLabel={`Connected to ${ baseUrl }`}
>{Site_Name}
</Text>
</View>
</View>

View File

@ -46,7 +46,7 @@ class WorkspaceView extends React.Component {
theme, Site_Name, Site_Url, Assets_favicon_512, server, registrationEnabled, registrationText, showLoginButton
} = this.props;
return (
<FormContainer theme={theme}>
<FormContainer theme={theme} testID='workspace-view'>
<FormContainerInner>
<View style={styles.alignItemsCenter}>
<ServerAvatar theme={theme} url={server} image={Assets_favicon_512 && Assets_favicon_512.defaultUrl} />
@ -60,6 +60,7 @@ class WorkspaceView extends React.Component {
type='primary'
onPress={this.login}
theme={theme}
testID='workspace-view-login'
/>
) : null}
{
@ -70,6 +71,7 @@ class WorkspaceView extends React.Component {
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.register}
theme={theme}
testID='workspace-view-register'
/>
) : (
<Text style={[styles.registrationText, { color: themes[theme].auxiliaryText }]}>{registrationText}</Text>

View File

@ -1,77 +0,0 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
describe('Onboarding', () => {
before(async() => {
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', async() => {
it('should have onboarding screen', async() => {
await expect(element(by.id('onboarding-view'))).toBeVisible();
});
it('should have "Connect to a server"', async() => {
await expect(element(by.id('connect-server-button'))).toBeVisible();
});
it('should have "Join the community"', async() => {
await expect(element(by.id('join-community-button'))).toBeVisible();
});
it('should have "Create a new workspace"', async() => {
await expect(element(by.id('create-workspace-button'))).toBeVisible();
});
});
describe('Usage', async() => {
it('should navigate to create new workspace', async() => {
// webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
});
it('should navigate to join community', async() => {
await element(by.id('join-community-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
// await waitFor(element(by.text('Rocket.Chat'))).toBeVisible().withTimeout(60000);
// await expect(element(by.text('Rocket.Chat'))).toBeVisible();
});
it('should navigate to new server', async() => {
await device.launchApp({ newInstance: true });
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('connect-server-button')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-server-view'))).toBeVisible();
});
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-button')).tap();
const errorText = 'Oops!';
await waitFor(element(by.text(errorText))).toBeVisible().withTimeout(60000);
await expect(element(by.text(errorText))).toBeVisible();
});
it('should enter a valid server with login services and navigate to welcome', async() => {
await element(by.text('OK')).tap();
await element(by.id('new-server-view-input')).replaceText('open');
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
});
it('should enter a valid server without login services and navigate to login', async() => {
await device.launchApp({ newInstance: true });
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('connect-server-button')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('login-view'))).toBeVisible();
});
});
});

View File

@ -1,50 +0,0 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { tapBack } = require('./helpers/app');
describe('Welcome screen', () => {
before(async() => {
await device.launchApp({ newInstance: true });
await element(by.id('join-community-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
})
describe('Render', async() => {
it('should have welcome screen', async() => {
await expect(element(by.id('welcome-view'))).toBeVisible();
});
it('should have register button', async() => {
await expect(element(by.id('welcome-view-register'))).toBeVisible();
});
it('should have login button', async() => {
await expect(element(by.id('welcome-view-login'))).toBeVisible();
});
// TODO: oauth
});
describe('Usage', async() => {
it('should navigate to login', async() => {
await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('login-view'))).toBeVisible();
});
it('should navigate to register', async() => {
await tapBack();
await element(by.id('welcome-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
});
it('should navigate to legal', async() => {
await tapBack();
await element(by.id('welcome-view-more')).tap();
await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('legal-view'))).toBeVisible();
});
});
});

View File

@ -1,47 +0,0 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { tapBack } = require('./helpers/app');
describe('Legal screen', () => {
before(async() => {
await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('legal-view'))).toBeVisible();
})
describe('Render', async() => {
it('should have legal screen', async() => {
await expect(element(by.id('legal-view'))).toBeVisible();
});
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();
});
});
describe('Usage', async() => {
// 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();
// });
it('should navigate to welcome', async() => {
await tapBack();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
});
});
});

View File

@ -3,27 +3,28 @@ const {
} = require('detox');
const data = require('../data');
async function addServer() {
async function navigateToWorkspace() {
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('connect-server-button')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-server-view'))).toBeVisible();
await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-button')).tap();
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('workspace-view'))).toBeVisible();
}
async function navigateToLogin() {
await addServer();
try {
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('login-view'))).toBeVisible();
} catch (error) {
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible();
await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('login-view'))).toBeVisible();
}
await navigateToWorkspace();
await element(by.id('workspace-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('login-view'))).toBeVisible();
}
async function navigateToRegister() {
await navigateToWorkspace();
await element(by.id('workspace-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
}
async function login() {
@ -51,6 +52,18 @@ async function logout() {
await expect(element(by.id('onboarding-view'))).toBeVisible();
}
async function createUser() {
await navigateToRegister();
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(data.email);
await element(by.id('register-view-password')).replaceText(data.password);
await sleep(300);
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
}
async function tapBack() {
await element(by.id('header-back')).atIndex(0).tap();
}
@ -60,10 +73,12 @@ async function sleep(ms) {
}
module.exports = {
addServer,
navigateToWorkspace,
navigateToLogin,
navigateToRegister,
login,
logout,
createUser,
tapBack,
sleep
};

View File

@ -1,15 +1,28 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
const { sleep, logout } = require('./helpers/app');
const data = require('../../data');
const { sleep, createUser } = require('../../helpers/app');
const checkServer = async(server) => {
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 expect(element(by.label(label))).toBeVisible();
await element(by.type('UIScrollView')).atIndex(1).swipe('left'); // close sidebar
}
describe('Change server', () => {
before(async() => {
await device.launchApp({ newInstance: true });
await createUser();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
});
it('should be logged in main server', async() => {
await checkServer(data.server);
})
it('should add server and create new user', async() => {
await sleep(5000);
await element(by.id('rooms-list-header-server-dropdown-button')).tap();
@ -17,29 +30,14 @@ describe('Change server', () => {
await expect(element(by.id('rooms-list-header-server-dropdown'))).toExist();
await sleep(1000);
await element(by.id('rooms-list-header-server-add')).tap();
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(60000);
await sleep(1000);
await element(by.id('connect-server-button')).tap();
// Add server
// TODO: refactor
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.alternateServer);
await sleep(1000);
await element(by.id('new-server-view-button')).tap();
// Navigate to register
// await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
// await element(by.id('welcome-view-register')).tap();
// await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
try {
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('login-view'))).toBeVisible();
await sleep(1000);
await element(by.id('login-view-register')).tap();
} catch (error) {
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible();
await sleep(1000);
await element(by.id('welcome-view-register')).tap();
}
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('workspace-view'))).toBeVisible();
await element(by.id('workspace-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
// Register new user
@ -51,13 +49,15 @@ describe('Change server', () => {
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
// For a sanity test, to make sure roomslist is showing correct rooms
// app CANNOT show public room created on previous tests
await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible();
// await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible().withTimeout(60000);
// await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible();
await checkServer(data.alternateServer);
});
it('should change server', async() => {
it('should change back', 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);
@ -65,9 +65,6 @@ describe('Change server', () => {
await sleep(1000);
await element(by.id(`rooms-list-header-server-${ data.server }`)).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
// For a sanity test, to make sure roomslist is showing correct rooms
// app MUST show public room created on previous tests
await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible();
await checkServer(data.server);
});
});

View File

@ -3,39 +3,14 @@ const {
} = require('detox');
const OTP = require('otp.js');
const GA = OTP.googleAuthenticator;
const { navigateToLogin, login, tapBack, sleep } = require('./helpers/app');
const data = require('./data');
const logout = async() => {
// previous tests added alternate server to the device
// so logout will only remove this server data and select alternate server
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('UIScrollView')).atIndex(1).scrollTo('bottom');
await element(by.id('settings-logout')).tap();
const logoutAlertMessage = 'You will be logged out of this application.';
await waitFor(element(by.text(logoutAlertMessage)).atIndex(0)).toExist().withTimeout(10000);
await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist();
await element(by.text('Logout')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await sleep(5000);
}
const localNavigateToLogin = 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 expect(element(by.id('rooms-list-header-server-dropdown'))).toExist();
await sleep(1000);
await element(by.id('rooms-list-header-server-add')).tap();
await navigateToLogin();
}
const { navigateToLogin, login, tapBack, sleep, createUser } = require('../../helpers/app');
const data = require('../../data');
describe('Broadcast room', () => {
before(async() => {
await device.launchApp({ newInstance: true });
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin();
await login();
});
it('should create broadcast room', async() => {
@ -43,6 +18,9 @@ describe('Broadcast room', () => {
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(data.alternateUser);
await waitFor(element(by.id(`select-users-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`select-users-view-item-${ data.alternateUser }`))).toBeVisible();
await element(by.id(`select-users-view-item-${ data.alternateUser }`)).tap();
await waitFor(element(by.id(`selected-user-${ data.alternateUser }`))).toBeVisible().withTimeout(5000);
await sleep(1000);
@ -62,44 +40,36 @@ describe('Broadcast room', () => {
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 Channel'))).toBeVisible();
await expect(element(by.label('Broadcast Channel').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);
// await tapBack();
// await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
// await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000);
// await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
});
it('should send message', async() => {
// await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(`${ data.random }message`);
await element(by.id('messagebox-send-message')).tap();
// await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible().withTimeout(60000);
// await expect(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible();
await sleep(5000);
await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toExist().withTimeout(60000);
await expect(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible();
await tapBack();
});
it('should login as user without write message authorization and enter room', async() => {
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await logout();
await localNavigateToLogin();
// 2FA login in stable:detox
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin();
await element(by.id('login-view-email')).replaceText(data.alternateUser);
await element(by.id('login-view-password')).replaceText(data.alternateUserPassword);
await sleep(2000);
await sleep(1000);
await element(by.id('login-view-submit')).tap();
await waitFor(element(by.id('two-factor'))).toBeVisible().withTimeout(5000);
await expect(element(by.id('two-factor'))).toBeVisible();
const code = GA.gen(data.alternateUserTOTPSecret);
await element(by.id('login-view-totp')).replaceText(code);
await sleep(2000);
await element(by.id('login-view-submit')).tap();
await element(by.id('two-factor-input')).replaceText(code);
await sleep(1000);
await element(by.id('two-factor-send')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top');
await element(by.id('rooms-list-view-search')).typeText(`broadcast${ data.random }`);
@ -140,19 +110,8 @@ describe('Broadcast room', () => {
it('should reply broadcasted message', async() => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(`${ data.random }broadcastreply`);
await sleep(1000);
await element(by.id('messagebox-send-message')).tap();
await sleep(1000);
await waitFor(element(by.label(`${ data.random }broadcastreply`)).atIndex(0)).toBeVisible().withTimeout(60000);
await expect(element(by.label(`${ data.random }broadcastreply`)).atIndex(0)).toBeVisible();
});
after(async() => {
// log back as main test user and left screen on RoomsListView
await tapBack();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await logout();
await localNavigateToLogin();
await login();
})
});

View File

@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { logout, navigateToLogin, login, sleep } = require('./helpers/app');
const data = require('./data');
const { logout, navigateToLogin, login, sleep } = require('../../helpers/app');
const data = require('../../data');
const scrollDown = 200;
@ -16,13 +16,15 @@ async function waitForToast() {
describe('Profile screen', () => {
before(async() => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin();
await login();
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 expect(element(by.id('sidebar-profile'))).toBeVisible();
await expect(element(by.id('sidebar-profile'))).toBeVisible();
await element(by.id('sidebar-profile')).tap();
await waitFor(element(by.id('profile-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', async() => {
@ -34,10 +36,6 @@ describe('Profile screen', () => {
await expect(element(by.id('profile-view-avatar')).atIndex(0)).toExist();
});
it('should have custom status', async() => {
await expect(element(by.id('profile-view-custom-status'))).toExist();
});
it('should have name', async() => {
await expect(element(by.id('profile-view-name'))).toExist();
});
@ -80,16 +78,6 @@ describe('Profile screen', () => {
});
describe('Usage', async() => {
it('should change custom status', async() => {
await element(by.type('UIScrollView')).atIndex(1).swipe('down');
await element(by.id('profile-view-custom-status')).replaceText(`${ data.user }new`);
await sleep(1000);
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
await sleep(1000);
await element(by.id('profile-view-submit')).tap();
await waitForToast();
});
it('should change name and username', async() => {
await element(by.type('UIScrollView')).atIndex(1).swipe('down');
await element(by.id('profile-view-name')).replaceText(`${ data.user }new`);

View File

@ -1,7 +1,7 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { logout, navigateToLogin, login } = require('./helpers/app');
const { logout, navigateToLogin, login } = require('../../helpers/app');
describe('Settings screen', () => {
before(async() => {

View File

@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
const { tapBack, sleep } = require('./helpers/app');
const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app');
const room = 'detox-public';

View File

@ -1,7 +1,7 @@
const {
expect, element, by, waitFor
} = require('detox');
const { sleep } = require('./helpers/app');
const { sleep } = require('../../helpers/app');
async function waitForToast() {
await sleep(5000);
@ -19,8 +19,8 @@ describe('Status screen', () => {
describe('Render', async() => {
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-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();
@ -29,9 +29,10 @@ describe('Status screen', () => {
describe('Usage', async() => {
it('should change status', async() => {
await element(by.id('status-view-busy')).tap();
sleep(1000);
await expect(element(by.id('status-view-current-busy'))).toExist();
await sleep(1000);
await element(by.id('status-view-busy')).tap();
await sleep(1000);
await expect(element(by.id('status-view-current-busy'))).toExist();
});
it('should change status text', async() => {
@ -39,6 +40,7 @@ describe('Status screen', () => {
await sleep(1000);
await element(by.id('status-view-submit')).tap();
await waitForToast();
await waitFor(element(by.label('status-text-new').withAncestor(by.id('sidebar-custom-status')))).toBeVisible().withTimeout(2000);
});
});
});

View File

@ -1,5 +1,5 @@
const detox = require('detox');
const config = require('../package.json').detox;
const config = require('../../../package.json').detox;
before(async() => {
await detox.init(config, { launchApp: false });

View File

@ -0,0 +1,62 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('../../data');
describe('Onboarding', () => {
before(async() => {
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', () => {
it('should have onboarding screen', async() => {
await expect(element(by.id('onboarding-view'))).toBeVisible();
});
it('should have "Join a workspace"', async() => {
await expect(element(by.id('join-workspace'))).toBeVisible();
});
it('should have "Create a new workspace"', async() => {
await expect(element(by.id('create-workspace-button'))).toBeVisible();
});
});
describe('Usage', () => {
// it('should navigate to create new workspace', async() => {
// // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
// });
it('should navigate to join a workspace', async() => {
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-server-view'))).toBeVisible();
});
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-button')).tap();
const errorText = 'Oops!';
await waitFor(element(by.text(errorText))).toBeVisible().withTimeout(60000);
await expect(element(by.text(errorText))).toBeVisible();
await element(by.text('OK')).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);
await expect(element(by.id('workspace-view'))).toBeVisible();
});
it('should enter a valid server without login services and navigate to login', async() => {
await device.launchApp({ newInstance: true });
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('workspace-view'))).toBeVisible();
});
});
});

View File

@ -0,0 +1,57 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { navigateToRegister, navigateToLogin } = require('../../helpers/app');
describe('Legal screen', () => {
it('should have legal button on login', async() => {
await device.launchApp({ newInstance: true });
await navigateToLogin();
await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('login-view-more'))).toBeVisible();
});
it('should navigate to legal from login', async() => {
await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000);
await element(by.id('login-view-more')).tap();
});
it('should have legal button on register', async() => {
await device.launchApp({ newInstance: true });
await navigateToRegister();
await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('register-view-more'))).toBeVisible();
});
it('should navigate to legal from register', async() => {
await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000);
await element(by.id('register-view-more')).tap();
});
it('should have legal screen', async() => {
await expect(element(by.id('legal-view'))).toBeVisible();
});
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();
// });
});

View File

@ -1,19 +1,20 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
const data = require('../../data');
const { navigateToLogin } = require('../../helpers/app');
describe('Forgot password screen', () => {
before(async() => {
await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await device.launchApp({ newInstance: true });
await navigateToLogin();
await element(by.id('login-view-forgot-password')).tap();
await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000);
});
describe('Render', async() => {
it('should have forgot password screen', async() => {
await expect(element(by.id('forgot-password-view'))).toBeVisible();
await expect(element(by.id('forgot-password-view'))).toExist();
});
it('should have email input', async() => {

View File

@ -1,20 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { logout, sleep } = require('./helpers/app');
const data = require('./data');
async function navigateToRegister() {
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('connect-server-button')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-button')).tap();
// we're assuming the server don't have login services and the navigation will jump to login
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000);
await element(by.id('login-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
}
const { navigateToRegister, sleep } = require('../../helpers/app');
const data = require('../../data');
describe('Create user screen', () => {
before(async() => {
@ -39,10 +27,6 @@ describe('Create user screen', () => {
await expect(element(by.id('register-view-password'))).toBeVisible();
});
it('should have show password icon', async() => {
await expect(element(by.id('register-view-password-icon-right'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('register-view-submit'))).toBeVisible();
});
@ -99,9 +83,5 @@ describe('Create user screen', () => {
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
after(async() => {
await logout();
});
});
});

View File

@ -1,11 +1,12 @@
const {
device, expect, element, by, waitFor
expect, element, by, waitFor
} = require('detox');
const { navigateToLogin, tapBack, sleep } = require('./helpers/app');
const data = require('./data');
const { navigateToLogin, tapBack, sleep } = require('../../helpers/app');
const data = require('../../data');
describe('Login screen', () => {
before(async() => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
await navigateToLogin();
});
@ -22,10 +23,6 @@ describe('Login screen', () => {
await expect(element(by.id('login-view-password'))).toBeVisible();
});
it('should have show password icon', async() => {
await expect(element(by.id('login-view-password-icon-right'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('login-view-submit'))).toBeVisible();
});
@ -53,8 +50,8 @@ describe('Login screen', () => {
it('should navigate to forgot password', async() => {
await element(by.id('login-view-forgot-password')).tap();
await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('forgot-password-view'))).toBeVisible();
await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000);
await expect(element(by.id('forgot-password-view'))).toExist();
await tapBack();
});

View File

@ -1,37 +1,31 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { login, logout, navigateToLogin, tapBack, sleep } = require('./helpers/app');
const data = require('./data');
const { logout, tapBack, sleep } = require('../../helpers/app');
describe('Rooms list screen', () => {
describe('Render', async() => {
describe('Render', () => {
it('should have rooms list screen', async() => {
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
// it('should have rooms list', async() => {
// await expect(element(by.id('rooms-list-view-list'))).toBeVisible();
// });
it('should have room item', async() => {
await expect(element(by.id('rooms-list-view-item-general')).atIndex(0)).toExist();
});
// Render - Header
describe('Header', async() => {
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();
// await expect(element(by.id('rooms-list-view-sidebar'))).toHaveLabel(`Connected to ${ data.server }. Tap to view servers list.`);
});
});
});
describe('Usage', async() => {
describe('Usage', () => {
it('should search room and navigate', async() => {
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top');
await waitFor(element(by.id('rooms-list-view-search'))).toExist().withTimeout(2000);
@ -53,29 +47,8 @@ describe('Rooms list screen', () => {
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toExist();
});
// Usage - Sidebar
describe('SidebarView', async() => {
it('should navigate to add 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(2000);
await expect(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible();
await expect(element(by.id('rooms-list-header-server-add'))).toBeVisible();
await element(by.id('rooms-list-header-server-add')).tap();
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('onboarding-view'))).toBeVisible();
await element(by.id('onboarding-close')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
it('should logout', async() => {
await logout();
});
});
after(async() => {
await navigateToLogin();
await login();
it('should logout', async() => {
await logout();
});
});
});

View File

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

View File

@ -1,14 +1,12 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
const { tapBack, sleep } = require('./helpers/app');
const data = require('../../data');
const { tapBack, sleep, createUser } = require('../../helpers/app');
describe('Create room screen', () => {
before(async() => {
await sleep(5000);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await device.launchApp({ newInstance: true });
await createUser();
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
});

View File

@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
const { tapBack, sleep } = require('./helpers/app');
const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app');
async function mockMessage(message) {
await element(by.id('messagebox-input')).tap();

View File

@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
const { tapBack, sleep } = require('./helpers/app');
const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app');
const scrollDown = 200;

View File

@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('./data');
const { tapBack, sleep } = require('./helpers/app');
const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app');
async function navigateToRoomInfo(type) {
let room;
@ -12,7 +12,7 @@ async function navigateToRoomInfo(type) {
room = `private${ data.random }`;
}
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top');
await element(by.type('UIScrollView')).atIndex(1).swipe('down');
await element(by.id('rooms-list-view-search')).typeText(room);
await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
@ -84,7 +84,7 @@ describe('Room info screen', () => {
await sleep(1000);
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
});
it('should have room info edit view', async() => {
@ -169,7 +169,8 @@ describe('Room info screen', () => {
// change name to original
await element(by.id('room-info-view-edit-button')).tap();
await sleep(1000);
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
await sleep(1000);
await element(by.id('room-info-edit-view-name')).replaceText(`${ room }`);
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
await sleep(1000);
@ -186,8 +187,11 @@ describe('Room info screen', () => {
await element(by.id('room-info-edit-view-password')).replaceText('abc');
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
await element(by.id('room-info-edit-view-t')).tap();
await sleep(1000);
await element(by.id('room-info-edit-view-ro')).tap();
await sleep(1000);
await element(by.id('room-info-edit-view-react-when-ro')).tap();
await sleep(1000);
await element(by.id('room-info-edit-view-reset')).tap();
// after reset
await expect(element(by.id('room-info-edit-view-name'))).toHaveText(room);
@ -210,14 +214,14 @@ describe('Room info screen', () => {
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await sleep(1000);
// await expect(element(by.id('room-info-view-description'))).toHaveLabel('new description');
await expect(element(by.label('new description'))).toBeVisible();
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.label('new description').withAncestor(by.id('room-info-view-description')))).toBeVisible();
});
it('should change room topic', async() => {
await sleep(1000);
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
await sleep(1000);
await element(by.id('room-info-edit-view-topic')).replaceText('new topic');
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
@ -226,14 +230,14 @@ describe('Room info screen', () => {
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await sleep(1000);
// await expect(element(by.id('room-info-view-topic'))).toHaveLabel('new topic');
await expect(element(by.label('new topic'))).toBeVisible();
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.label('new topic').withAncestor(by.id('room-info-view-topic')))).toBeVisible();
});
it('should change room announcement', async() => {
await sleep(1000);
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
await sleep(1000);
await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement');
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
@ -242,14 +246,14 @@ describe('Room info screen', () => {
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await sleep(1000);
// await expect(element(by.id('room-info-view-announcement'))).toHaveLabel('new announcement');
await expect(element(by.label('new announcement'))).toBeVisible();
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.label('new announcement').withAncestor(by.id('room-info-view-announcement')))).toBeVisible();
});
it('should change room password', async() => {
await sleep(1000);
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
await sleep(1000);
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
await element(by.id('room-info-edit-view-password')).replaceText('password');

11
e2e/tests/room/init.js Normal file
View File

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

View File

@ -41,8 +41,8 @@
"expo-av": "^8.1.0",
"expo-file-system": "^8.1.0",
"expo-haptics": "^8.1.0",
"expo-local-authentication": "9.0.0",
"expo-keep-awake": "^8.1.0",
"expo-local-authentication": "9.0.0",
"expo-web-browser": "8.2.1",
"hoist-non-react-statics": "3.3.2",
"i18n-js": "3.5.1",
@ -131,7 +131,7 @@
"babel-runtime": "^6.26.0",
"bugsnag-sourcemaps": "1.3.0",
"codecov": "3.6.5",
"detox": "^15.2.2",
"detox": "^16.5.0",
"emotion-theming": "10.0.27",
"eslint": "6.8.0",
"eslint-plugin-import": "2.20.2",
@ -143,7 +143,7 @@
"jest": "^24.9.0",
"jest-cli": "^23.6.0",
"metro-react-native-babel-preset": "^0.58.0",
"mocha": "^5.2.0",
"mocha": "7.1.2",
"otp.js": "1.2.0",
"patch-package": "6.2.2",
"react-dom": "16.11.0",
@ -178,13 +178,17 @@
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/RocketChatRN.app",
"build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 11 Pro"
"device": {
"type": "iPhone 11 Pro"
}
},
"ios.sim.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/RocketChatRN.app",
"build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 11 Pro"
"device": {
"type": "iPhone 11 Pro"
}
}
}
}

281
yarn.lock
View File

@ -3660,6 +3660,11 @@ ansi-align@^3.0.0:
dependencies:
string-width "^3.0.0"
ansi-colors@3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==
ansi-colors@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9"
@ -3789,6 +3794,14 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
app-root-dir@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118"
@ -4694,17 +4707,17 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
binary-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
binstring@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/binstring/-/binstring-0.2.1.tgz#8a174d301f6d54efda550dd98bb4cb524eacd75d"
integrity sha1-ihdNMB9tVO/aVQ3Zi7TLUk6s110=
bluebird@3.5.x:
version "3.5.5"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
bluebird@^3.3.5, bluebird@^3.5.5:
bluebird@^3.3.5, bluebird@^3.5.4, bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@ -4811,7 +4824,7 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
braces@^3.0.1:
braces@^3.0.1, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@ -5278,6 +5291,21 @@ child-process-promise@^2.2.0:
node-version "^1.0.0"
promise-polyfill "^6.0.1"
chokidar@3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6"
integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.2.0"
optionalDependencies:
fsevents "~2.1.1"
chokidar@^2.0.4, chokidar@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@ -5520,11 +5548,6 @@ command-exists@^1.2.8:
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291"
integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==
commander@2.15.1:
version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
commander@^2.11.0, commander@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
@ -6039,12 +6062,12 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.
dependencies:
ms "2.0.0"
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
debug@3.2.6, debug@^3.0.0, debug@^3.1.0, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "2.0.0"
ms "^2.1.1"
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
@ -6053,13 +6076,6 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "^2.1.1"
debug@^3.0.0, debug@^3.1.0, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@ -6241,10 +6257,10 @@ detect-port@^1.3.0:
address "^1.0.1"
debug "^2.6.0"
detox@^15.2.2:
version "15.2.2"
resolved "https://registry.yarnpkg.com/detox/-/detox-15.2.2.tgz#490a8c54944f658492490f8dc88c0946a57c9197"
integrity sha512-lmtRTrl7gW5noBaCJZr3WCEv5hvTw6DK2p81ATGUL1pImxpjeHhJJhTYAq5taz0kl706g9DOxFao8i/NgBR4bg==
detox@^16.5.0:
version "16.5.0"
resolved "https://registry.yarnpkg.com/detox/-/detox-16.5.0.tgz#81cd560de917fca9e6ecd10cfd68c1831e896da4"
integrity sha512-UQ4GuMyHv87+LOP0VQqoNIVRhren2xSGt2bXikVNjURlxBTT3GG+e+QmjCZtlUVntbsV0UHsbdNPzYveLS84mw==
dependencies:
"@babel/core" "^7.4.5"
bunyan "^1.8.12"
@ -6261,7 +6277,7 @@ detox@^15.2.2:
sanitize-filename "^1.6.1"
shell-utils "^1.0.9"
tail "^2.0.0"
telnet-client "0.15.3"
telnet-client "1.2.8"
tempfile "^2.0.0"
which "^1.3.1"
ws "^3.3.1"
@ -7589,6 +7605,13 @@ flat-cache@^2.0.1:
rimraf "2.6.3"
write "1.0.3"
flat@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2"
integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==
dependencies:
is-buffer "~2.0.3"
flatted@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
@ -7783,6 +7806,11 @@ fsevents@^1.2.7:
nan "^2.12.1"
node-pre-gyp "^0.12.0"
fsevents@~2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -7917,6 +7945,13 @@ glob-parent@^5.0.0:
dependencies:
is-glob "^4.0.1"
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
dependencies:
is-glob "^4.0.1"
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
@ -7934,10 +7969,10 @@ glob@7.0.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
glob@7.1.3, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@ -7969,18 +8004,6 @@ glob@^7.0.0, glob@^7.1.4:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
global-modules@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
@ -8244,12 +8267,7 @@ hastscript@^5.0.0:
property-information "^5.0.1"
space-separated-tokens "^1.0.0"
he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
he@^1.2.0:
he@1.2.0, he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@ -8794,6 +8812,13 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-boolean-object@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
@ -8804,6 +8829,11 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-buffer@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
is-builtin-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
@ -8991,7 +9021,7 @@ is-glob@^4.0.0:
dependencies:
is-extglob "^2.1.1"
is-glob@^4.0.1:
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
@ -10687,6 +10717,13 @@ lodash@4.x.x, lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, l
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
log-symbols@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
dependencies:
chalk "^2.4.2"
log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@ -11406,36 +11443,49 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
mkdirp@^0.5.3:
mkdirp@0.5.5, mkdirp@^0.5.3:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
mocha@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6"
integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==
mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
mocha@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.2.tgz#8e40d198acf91a52ace122cd7599c9ab857b29e6"
integrity sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==
dependencies:
ansi-colors "3.2.3"
browser-stdout "1.3.1"
commander "2.15.1"
debug "3.1.0"
chokidar "3.3.0"
debug "3.2.6"
diff "3.5.0"
escape-string-regexp "1.0.5"
glob "7.1.2"
find-up "3.0.0"
glob "7.1.3"
growl "1.10.5"
he "1.1.1"
he "1.2.0"
js-yaml "3.13.1"
log-symbols "3.0.0"
minimatch "3.0.4"
mkdirp "0.5.1"
supports-color "5.4.0"
mkdirp "0.5.5"
ms "2.1.1"
node-environment-flags "1.0.6"
object.assign "4.1.0"
strip-json-comments "2.0.1"
supports-color "6.0.0"
which "1.3.1"
wide-align "1.1.3"
yargs "13.3.2"
yargs-parser "13.1.2"
yargs-unparser "1.6.0"
moment@2.25.3:
version "2.25.3"
@ -11567,6 +11617,14 @@ no-case@^3.0.3:
lower-case "^2.0.1"
tslib "^1.10.0"
node-environment-flags@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088"
integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==
dependencies:
object.getownpropertydescriptors "^2.0.3"
semver "^5.7.0"
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@ -11727,7 +11785,7 @@ normalize-path@^2.0.1, normalize-path@^2.1.1:
dependencies:
remove-trailing-separator "^1.0.1"
normalize-path@^3.0.0:
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
@ -11857,7 +11915,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
object.assign@^4.1.0:
object.assign@4.1.0, object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
@ -12478,7 +12536,7 @@ phin@^2.9.1:
resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c"
integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==
picomatch@^2.0.5:
picomatch@^2.0.4, picomatch@^2.0.5:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
@ -13880,6 +13938,13 @@ readdirp@^2.2.1:
micromatch "^3.1.10"
readable-stream "^2.0.2"
readdirp@~3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839"
integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==
dependencies:
picomatch "^2.0.4"
realpath-native@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.2.tgz#cd51ce089b513b45cf9b1516c82989b51ccc6560"
@ -14547,6 +14612,11 @@ semver@7.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
semver@^5.7.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65"
@ -15329,16 +15399,16 @@ strip-indent@^1.0.1:
dependencies:
get-stdin "^4.0.1"
strip-json-comments@2.0.1, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
strip-json-comments@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
stubs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"
@ -15357,10 +15427,10 @@ sudo-prompt@^9.0.0:
resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0"
integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==
supports-color@5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==
supports-color@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a"
integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==
dependencies:
has-flag "^3.0.0"
@ -15478,12 +15548,12 @@ telejson@^3.2.0:
lodash "^4.17.15"
memoizerific "^1.11.3"
telnet-client@0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/telnet-client/-/telnet-client-0.15.3.tgz#99ec754e4acf6fa51dc69898f574df3c2550712e"
integrity sha512-GSfdzQV0BKIYsmeXq7bJFJ2wHeJud6icaIxCUf6QCGQUD6R0BBGbT1+yLDhq67JRdgRpwyPwUbV7JxFeRrZomQ==
telnet-client@1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/telnet-client/-/telnet-client-1.2.8.tgz#946c0dadc8daa3f19bb40a3e898cb870403a4ca4"
integrity sha512-W+w4k3QAmULVNhBVT2Fei369kGZCh/TH25M7caJAXW+hLxwoQRuw0di3cX4l0S9fgH3Mvq7u+IFMoBDpEw/eIg==
dependencies:
bluebird "3.5.x"
bluebird "^3.5.4"
temp-dir@^1.0.0:
version "1.0.0"
@ -16459,14 +16529,14 @@ which-typed-array@^1.1.2:
has-symbols "^1.0.1"
is-typed-array "^1.1.3"
which@^1.2.12, which@^1.2.9, which@^1.3.0, which@^1.3.1:
which@1.3.1, which@^1.2.12, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
wide-align@^1.1.0:
wide-align@1.1.3, wide-align@^1.1.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
@ -16716,6 +16786,14 @@ yaml@^1.7.2:
dependencies:
"@babel/runtime" "^7.9.2"
yargs-parser@13.1.2, yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
@ -16755,6 +16833,31 @@ yargs-parser@^9.0.2:
dependencies:
camelcase "^4.1.0"
yargs-unparser@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f"
integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==
dependencies:
flat "^4.1.0"
lodash "^4.17.15"
yargs "^13.3.0"
yargs@13.3.2:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
dependencies:
cliui "^5.0.0"
find-up "^3.0.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^3.0.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.2"
yargs@^11.0.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"