Skip to content
Commits on Source (526)
version: 2
jobs:
test:
working_directory: ~/mobile-native
docker:
- image: circleci/node:10
steps:
- checkout
- restore_cache:
key: yarn-v1-{{ checksum "yarn.lock" }}-{{ arch }}
- restore_cache:
key: node-v1-{{ checksum "package.json" }}-{{ arch }}
- run: yarn install
- save_cache:
key: yarn-v1-{{ checksum "yarn.lock" }}-{{ arch }}
paths:
- ~/.cache/yarn
- save_cache:
key: node-v1-{{ checksum "package.json" }}-{{ arch }}
paths:
- node_modules
- run:
name: jest tests
command: |
mkdir -p test-results/jest
yarn run test --maxWorkers=2
environment:
JEST_JUNIT_OUTPUT: test-results/jest/junit.xml
- persist_to_workspace:
root: ~/mobile-native
paths:
- node_modules
- store_test_results:
path: test-results
- store_artifacts:
path: test-results
ios:
macos:
xcode: "11.1.0"
working_directory: ~/mobile-native
# use a --login shell so our "set Ruby version" command gets picked up for later steps
shell: /bin/bash --login -o pipefail
steps:
- checkout
- run:
name: Install sentry cli for fastlane plugin
command: brew install getsentry/tools/sentry-cli
- run:
name: set Ruby version
command: echo "ruby-2.6" > ~/.ruby-version
- restore_cache:
key: yarn-v1-{{ checksum "yarn.lock" }}-{{ arch }}
- restore_cache:
key: node-v1-{{ checksum "package.json" }}-{{ arch }}
# remove detox from CI until is fixed
# - run:
# name: Install detox
# command:
# |
# HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew
# HOMEBREW_NO_AUTO_UPDATE=1 brew install --HEAD applesimutils
# npm install -g detox-cli
# npm install -g detox
# not using a workspace here as Node and Yarn versions
# differ between our macOS executor image and the Docker containers above
- run: yarn install --frozen-lockfile
- save_cache:
key: yarn-v1-{{ checksum "yarn.lock" }}-{{ arch }}
paths:
- ~/.cache/yarn
- save_cache:
key: node-v1-{{ checksum "package.json" }}-{{ arch }}
paths:
- node_modules
- restore_cache:
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}-{{ arch }}
- run:
command: gem update --system && gem install bundler && bundle install
working_directory: ios
- save_cache:
key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}-{{ arch }}
paths:
- vendor/bundle
- restore_cache:
key: pods-v1-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
- run:
name: Install CocoaPods
command: pod install --verbose
working_directory: ios
- save_cache:
key: pods-v1-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
paths:
- ios/Pods
# remove detox from CI until is fixed
# Run e2e
# - run: detox build -c ios.sim.release
# - run: detox test -c ios.sim.release --cleanup
### TODO- get tests running with fastlane
#- run:
# command: bundle exec fastlane test
# working_directory: ios
#- run:
# name: set up test results
# working_directory: ios
# when: always
# command: |
# mkdir -p test-results/fastlane test-results/xcode
# mv fastlane/report.xml test-results/fastlane
# mv fastlane/test_output/report.junit test-results/xcode/junit.xml
#- store_test_results:
# path: ios/test-results
#- store_artifacts:
# path: ios/test-results
- run:
name: Build release .ipa
command: fastlane buildrelease
working_directory: ios
branches:
only:
- /stable-*/
- /release-*/
- test/circle-ci
- run:
name: Upload to crashalytics
command: echo "TODO"
working_directory: ios
branches:
only:
- /release-*/
- run:
name: Prepare sentry release
command: fastlane preparesentry
working_directory: ios
- persist_to_workspace:
root: ~/mobile-native/ios
paths:
- version
- run:
name: Upload to Testflight release
command: fastlane testflight
working_directory: ios
sentry:
docker:
- image: getsentry/sentry-cli
working_directory: ~/mobile-native
steps:
- attach_workspace:
at: /tmp/workspace
- run:
name: Install git
command: |
apk add git
- checkout
- run:
name: Tag sentry release
command: |
version=`cat /tmp/workspace/version`
echo Tagging release with ${version}
ls -a
# release created by fastlane preparesentry
sentry-cli releases set-commits --commit "Minds / Minds Mobile@${CIRCLE_SHA1}" ${version} --log-level=debug
sentry-cli releases finalize ${version}
workflows:
version: 2
node-ios:
jobs:
- test
- ios:
requires:
- test
filters:
branches:
only:
- /stable-*/
- /test-*/
- test/circle-ci
- sentry:
requires:
- ios
module.exports = {
"parser": "babel-eslint",
"plugins": [
"react-native",
"flowtype"
],
"extends": ["plugin:react-native/all"],
"rules": {
"flowtype/boolean-style": [
2,
"boolean"
],
"flowtype/define-flow-type": 1,
"flowtype/delimiter-dangle": [
2,
"never"
],
"flowtype/generic-spacing": [
2,
"never"
],
"flowtype/no-mixed": 0,
"flowtype/no-primitive-constructor-types": 2,
"flowtype/no-types-missing-file-annotation": 2,
"flowtype/no-weak-types": 0,
"flowtype/object-type-delimiter": [
2,
"comma"
],
"flowtype/require-parameter-type": 2,
"flowtype/require-readonly-react-props": 0,
"flowtype/require-return-type": [
2,
"always",
{
"annotateUndefined": "never"
}
],
"flowtype/require-valid-file-annotation": 2,
"flowtype/semi": [
2,
"always"
],
"flowtype/space-after-type-colon": [
2,
"always"
],
"flowtype/space-before-generic-bracket": [
2,
"never"
],
"flowtype/space-before-type-colon": [
2,
"never"
],
"flowtype/type-id-match": [
2,
"^([A-Z][a-z0-9]+)+Type$"
],
"flowtype/union-intersection-spacing": [
2,
"always"
],
"flowtype/use-flow-type": 1,
"flowtype/valid-syntax": 1
},
"settings": {
"flowtype": {
"onlyFilesWithFlowAnnotation": true
}
}
}
\ No newline at end of file
root: true,
extends: '@react-native-community',
};
\ No newline at end of file
......@@ -5,26 +5,24 @@
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*
; Ignore polyfills
.*/Libraries/polyfills/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
; Ignore metro
.*/node_modules/metro/.*
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow/
node_modules/react-native/flow-github/
[options]
emoji=true
......@@ -32,39 +30,46 @@ emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
module.system=haste
module.system.haste.use_name_reducers=true
# get basename
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
# strip .js or .js.flow suffix
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
# strip .ios suffix
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
module.system.haste.paths.blacklist=.*/__tests__/.*
module.system.haste.paths.blacklist=.*/__mocks__/.*
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
module.file_ext=.js
module.file_ext=.jsx
module.file_ext=.json
module.file_ext=.native.js
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
inexact-spread=warn
unnecessary-invariant=warn
signature-verification-failure=warn
deprecated-utility=error
[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import
[version]
^0.92.0
^0.107.0
#!/bin/sh
if git commit -v --dry-run | grep '!testcode' >/dev/null 2>&1
then
echo "Trying to commit test code."
exit 1
else
if git diff --cached locales/en.json | grep '\\n' >/dev/null 2>&1
then
echo "New line characters are forbiden in en.json, please split the lines in different translation terms."
exit 1
else
exit 0
fi
fi
\ No newline at end of file
......@@ -5,7 +5,6 @@
# Xcode
#
ios/Podfile.lock
build/
*.pbxuser
!default.pbxuser
......@@ -42,6 +41,7 @@ yarn-error.log
buck-out/
\.buckd/
*.keystore
!debug.keystore
# fastlane
#
......@@ -57,7 +57,15 @@ buck-out/
# Bundle artifact
*.jsbundle
# CocoaPods
/ios/Pods/
# Jest cache
.jest/
coverage/
\ No newline at end of file
coverage/
# Sentry secrets
sentry.properties
!/.githooks
\ No newline at end of file
......@@ -2,73 +2,174 @@
stages:
- test
- build
- test_e2e
- i18n
- deploy
variables:
GRADLE_USER_HOME: $CI_PROJECT_DIR/.gradle
test_spec:
image: node:10.10.0
# Spec test
test:jest:
image: node:10.16.3
stage: test
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .jest/cache/
before_script:
- yarn install --frozen-lockfile
script:
- echo $CI_PROJECT_NAMESPACE
- yarn install
- yarn test
tags:
- docker
build:
image: reactnativecommunity/react-native-android
stage: build
# Upload new terms to poeditor
i18n:upload:
image: node:10.16.3
stage: i18n
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .gradle/wrapper
- .gradle/caches
- node_modules/
before_script:
- echo 999999 > /proc/sys/fs/inotify/max_user_instances
- echo 999999 > /proc/sys/fs/inotify/max_user_watches
- echo 999999 > /proc/sys/fs/inotify/max_queued_events
- echo $ANDROID_KEYSTORE | base64 --decode > android/app/minds.keystore
- echo "MYAPP_RELEASE_STORE_PASSWORD=${KEYSTORE_PASSWORD}" >> ./android/gradle.properties
- echo "MYAPP_RELEASE_KEY_PASSWORD=${KEYSTORE_PASSWORD}" >> ./android/gradle.properties
- yarn install --frozen-lockfile
script:
- yarn locale upload --poeditor-key=${CI_POEDITOR_KEY} --overwrite=1
only:
refs:
- /^release-*/
# Upload new terms and remove the deleted
i18n:uploadsync:
image: node:10.16.3
stage: i18n
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- yarn install --frozen-lockfile
script:
- yarn install
- cd android && chmod +x gradlew
- ./gradlew assembleRelease
- cp app/build/outputs/apk/release/app-release.apk ../Minds.apk
- yarn locale upload --poeditor-key=${CI_POEDITOR_KEY} --overwrite=1 --sync_terms=1
only:
refs:
- master
# Web dev version using cache and without sentry maps upload
build:android:
image: circleci/android:api-28-node
stage: build
cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
- android/vendor/bundle
- .gradle/caches
- .gradle/wrapper
- .android/build-cache/
before_script:
- 'sed -i ''s/^apply from: "..\/..\/node_modules\/\@sentry\/react-native\/sentry.gradle"//'' android/app/build.gradle'
- export ANDROID_SDK_HOME=$CI_PROJECT_DIR
- export GRADLE_USER_HOME="$CI_PROJECT_DIR/.gradle"
- sudo sysctl fs.inotify.max_user_watches=524288
- sudo sysctl -p
- yarn install --frozen-lockfile
- cd android
- bundle install --path=vendor/bundle
script:
- bundle exec fastlane assemble_build
- mv app/build/outputs/apk/release/app-release.apk ../Minds-$CI_COMMIT_REF_SLUG-dev.apk
artifacts:
name: "Minds APK"
paths:
- Minds.apk
- Minds-$CI_COMMIT_REF_SLUG-dev.apk
expire_in: 7 days
when: on_success
only:
variables:
- $CI_PROJECT_NAMESPACE == "minds"
refs:
- /^release-*/
# Web version (Higher version code)
build:androidproduction:
image: circleci/android:api-28-node
stage: build
before_script:
- export ANDROID_SDK_HOME=$CI_PROJECT_DIR
- export GRADLE_USER_HOME="$CI_PROJECT_DIR/.gradle"
- sudo sysctl fs.inotify.max_user_watches=524288
- sudo sysctl -p
- yarn install --frozen-lockfile
- cd android
- bundle install --path=vendor/bundle
script:
- bundle exec fastlane assemble_build
- mv app/build/outputs/apk/release/app-release.apk ../Minds-$CI_COMMIT_REF_SLUG.apk
artifacts:
name: "Minds APK"
paths:
- Minds-$CI_COMMIT_REF_SLUG.apk
expire_in: 7 days
when: on_success
only:
refs:
- /^stable-*/
- /^test-*/
test_e2e:
image: node:10.10.0
stage: test_e2e
cache:
# Play store version (Lowest version code)
build:androidproduction-playstore:
image: circleci/android:api-28-node
stage: build
before_script:
- 'sed -i ''s/^versionCode=/# versionCode=/'' android/gradle.properties'
- 'sed -i ''s/^## versionCode/versionCode/'' android/gradle.properties'
- export ANDROID_SDK_HOME=$CI_PROJECT_DIR
- export GRADLE_USER_HOME="$CI_PROJECT_DIR/.gradle"
- sudo sysctl fs.inotify.max_user_watches=524288
- sudo sysctl -p
- yarn install --frozen-lockfile
- cd android
- bundle install --path=vendor/bundle
script:
- bundle exec fastlane assemble_build
- mv app/build/outputs/apk/release/app-release.apk ../Minds-$CI_COMMIT_REF_SLUG-play_store.apk
artifacts:
name: "Minds APK"
paths:
- node_modules/
- .jest/cache/
- Minds-$CI_COMMIT_REF_SLUG-play_store.apk
expire_in: 7 days
when: on_success
only:
refs:
- /^stable-*/
- /^test-*/
# Deploy Web/PlayStore versions to s3 and browserstack
deploy:s3andbrowserstack:
image: minds/ci:latest
stage: deploy
script:
- echo "Upload Minds-$CI_COMMIT_REF_SLUG.apk"
- aws s3 cp Minds-$CI_COMMIT_REF_SLUG.apk s3://minds-repo/mobile/Minds-$CI_COMMIT_REF_SLUG.apk
- aws s3 cp Minds-$CI_COMMIT_REF_SLUG-play_store.apk s3://minds-repo/mobile/Minds-$CI_COMMIT_REF_SLUG-play_store.apk
- curl -u $CI_BROWSERSTACK_APIKEY -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@Minds-$CI_COMMIT_REF_SLUG.apk"
- curl -u $CI_BROWSERSTACK_APIKEY -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@Minds-$CI_COMMIT_REF_SLUG-play_store.apk"
dependencies:
- build:androidproduction
- build:androidproduction-playstore
only:
refs:
- /^stable-*/
- /^test-*/
deploy:google_play:
image: circleci/android:api-28-node
stage: deploy
before_script:
- cd android
- bundle install --path=vendor/bundle
- 'echo $ANDROID_PLAYSTORE_JSON | base64 --decode > app/play-store.json'
script:
- yarn install
- export bsAPP=`curl -u "${bsUSER}:${bsKEY}" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@./Minds.apk"| grep -o 'bs\:\/\/.*"' | sed 's/.$//'`
- yarn e2e
tags:
- docker
- echo "Upload to the play store Minds-$CI_COMMIT_REF_SLUG-play_store.apk"
- bundle exec fastlane supply --apk ../Minds-$CI_COMMIT_REF_SLUG-play_store.apk --track beta
dependencies:
- build
- build:androidproduction-playstore
only:
variables:
- $CI_PROJECT_NAMESPACE == "minds"
refs:
- /^stable-*/
- /^test-*/
module.exports = {
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
};
......@@ -5,21 +5,17 @@
* @format
* @flow
*/
import './global';
import './shim'
import crypto from "crypto"; // DO NOT REMOVE!
import codePush from "react-native-code-push"; // For auto updates
import React, {
Component
Component,
} from 'react';
import {
Observer,
Provider,
} from 'mobx-react/native' // import from mobx-react/native instead of mobx-react fix test
} from 'mobx-react/native'; // import from mobx-react/native instead of mobx-react fix test
import NavigationService from './src/navigation/NavigationService';
import RNBootSplash from "react-native-bootsplash";
import {
BackHandler,
......@@ -29,10 +25,10 @@ import {
Text,
Alert,
Clipboard,
StatusBar,
} from 'react-native';
import FlashMessage from "react-native-flash-message";
import CookieManager from 'react-native-cookies';
import FlashMessage from 'react-native-flash-message';
import KeychainModalScreen from './src/keychain/KeychainModalScreen';
import BlockchainTransactionModalScreen from './src/blockchain/transaction-modal/BlockchainTransactionModalScreen';
......@@ -48,9 +44,9 @@ import sessionService from './src/common/services/session.service';
import deeplinkService from './src/common/services/deeplinks-router.service';
import badgeService from './src/common/services/badge.service';
import authService from './src/auth/AuthService';
import NotificationsService from "./src/notifications/NotificationsService";
import NotificationsService from './src/notifications/NotificationsService';
import getMaches from './src/common/helpers/getMatches';
import {CODE_PUSH_TOKEN, GOOGLE_PLAY_STORE} from './src/config/Config';
import { GOOGLE_PLAY_STORE } from './src/config/Config';
import updateService from './src/common/services/update.service';
import ErrorBoundary from './src/common/components/ErrorBoundary';
import { CommonStyle as CS } from './src/styles/Common';
......@@ -63,23 +59,36 @@ import feedsStorage from './src/common/services/sql/feeds.storage';
import connectivityService from './src/common/services/connectivity.service';
import sqliteStorageProviderService from './src/common/services/sqlite-storage-provider.service';
import commentStorageService from './src/comments/CommentStorageService';
import * as Sentry from '@sentry/react-native';
import apiService from './src/common/services/api.service';
import boostedContentService from './src/common/services/boosted-content.service';
import translationService from './src/common/services/translation.service';
let deepLinkUrl = '';
const statusBarStyle = Platform.OS === 'ios' ? 'dark-content' : 'default';
// init push service
pushService.init();
// fire sqlite init
sqliteStorageProviderService.get();
CookieManager.clearAll();
apiService.clearCookies();
// On app login (runs if the user login or if it is already logged in)
sessionService.onLogin(async () => {
const user = sessionService.getUser();
Sentry.configureScope(scope => {
scope.setUser({id: user.guid});
});
logService.info('[App] Getting minds settings and onboarding progress');
// load minds settings and onboarding progresss on login
const results = await Promise.all([mindsService.getSettings(), stores.onboarding.getProgress()]);
// load minds settings and boosted content
await Promise.all([mindsService.getSettings(), boostedContentService.load()]);
logService.info('[App] updatting features');
// reload fatures on login
......@@ -89,15 +98,15 @@ sessionService.onLogin(async () => {
pushService.registerToken();
// get onboarding progress
const onboarding = results[1];
logService.info('[App] navigating to initial screen', sessionService.initialScreen);
if (onboarding && onboarding.show_onboarding) {
sessionService.setInitialScreen('OnboardingScreen');
}
// hide splash
RNBootSplash.hide({ duration: 250 });
logService.info('[App] navigating to initial screen', sessionService.initialScreen);
NavigationService.reset(sessionService.initialScreen);
NavigationService.navigate(sessionService.initialScreen);
// check onboarding progress and navigate if necessary
stores.onboarding.getProgress();
// check update
if (Platform.OS !== 'ios' && !GOOGLE_PLAY_STORE) {
......@@ -138,15 +147,12 @@ sessionService.onLogout(() => {
// clear app badge
badgeService.setUnreadConversations(0);
badgeService.setUnreadNotifications(0);
// clear minds settings
mindsService.clear();
// clear offline cache
entitiesStorage.removeAll();
feedsStorage.removeAll();
stores.notifications.clearLocal();
stores.groupsBar.clearLocal();
translationService.purgeLanguagesCache();
});
// disable yellow boxes
......@@ -163,7 +169,6 @@ type Props = {
/**
* App
*/
@codePush
export default class App extends Component<Props, State> {
state = {
......@@ -182,10 +187,14 @@ export default class App extends Component<Props, State> {
}
/**
* On component will mount
* contructor
*/
componentWillMount() {
if (!Text.defaultProps) Text.defaultProps = {};
constructor(props) {
super(props);
if (!Text.defaultProps) {
Text.defaultProps = {};
}
Text.defaultProps.style = {
fontFamily: 'Roboto',
color: '#444',
......@@ -197,8 +206,9 @@ export default class App extends Component<Props, State> {
*/
async componentDidMount() {
try {
// load app setting before start
const results = await Promise.all([settingsStore.init(), await Linking.getInitialURL()]),
const results = await Promise.all([settingsStore.init(), await Linking.getInitialURL()]);
deepLinkUrl = results[1];
......@@ -208,17 +218,17 @@ export default class App extends Component<Props, State> {
if (!this.handlePasswordResetDeepLink()) {
logService.info('[App] initializing session');
const token = await sessionService.init();
if (!token) {
logService.info('[App] there is no active session');
NavigationService.reset('Login');
RNBootSplash.hide({ duration: 250 });
NavigationService.navigate('Login');
} else {
logService.info('[App] session initialized');
}
}
await this.checkForUpdates();
} catch(err) {
logService.exception('[App] Error initializing the app', err);
Alert.alert(
......@@ -283,21 +293,6 @@ export default class App extends Component<Props, State> {
}
}
async checkForUpdates() {
try {
const params = {
updateDialog: Platform.OS !== 'ios',
installMode: codePush.InstallMode.ON_APP_RESUME,
};
if (CODE_PUSH_TOKEN) params.deploymentKey = CODE_PUSH_TOKEN;
let response = await codePush.sync(params);
} catch (err) {
logService.exception('[App] Error checking for code push updated', err);
}
}
/**
* Render
*/
......@@ -305,13 +300,13 @@ export default class App extends Component<Props, State> {
const app = (
<Provider key="app" {...stores}>
<ErrorBoundary message="An error occurred" containerStyle={CS.centered}>
<StatusBar barStyle={statusBarStyle} />
<NavigationStack
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
<FlashMessage renderCustomContent={this.renderNotification}
/>
<FlashMessage renderCustomContent={this.renderNotification} />
</ErrorBoundary>
</Provider>
);
......@@ -325,7 +320,7 @@ export default class App extends Component<Props, State> {
);
const tosModal = (
<TosModal user={stores.user}/>
<TosModal user={stores.user} key="tosModal"/>
)
return [ app, keychainModal, blockchainTransactionModal, tosModal];
......
import {
Alert,
} from 'react-native';
import {Alert} from 'react-native';
import {
setNativeExceptionHandler,
setJSExceptionHandler
setJSExceptionHandler,
} from 'react-native-exception-handler';
import { onError } from "mobx-react";
import {onError} from 'mobx-react';
import logService from './src/common/services/log.service';
import * as Sentry from '@sentry/react-native';
import shouldReportToSentry from './src/common/helpers/errors';
// Init Sentry (if not running test)
if (process.env.JEST_WORKER_ID === undefined) {
Sentry.init({
dsn: 'https://16c9b543563140a0936cc3cd3714481d@sentry.io/1766867',
ignoreErrors: [
'Non-Error exception captured with keys: code, domain, localizedDescription', // ignore initial error of sdk
],
beforeSend(event, hint) {
if (hint.originalException) {
if (!shouldReportToSentry(hint.originalException)) {
return null;
}
}
// for dev only log into the console
if (__DEV__) {
console.log('sentry', event, hint);
return null;
}
return event;
},
});
}
// Log Mobx global errors
onError(error => {
console.log(error);
logService.exception(error);
})
// react-native-exception-handler global handlers
if (!__DEV__) {
/**
* Globar error handlers
......@@ -48,7 +75,9 @@ if (!__DEV__) {
* Native Errors
*/
setNativeExceptionHandler((exceptionString) => {
Sentry.captureException(new Error(exceptionString), {
logger: 'NativeExceptionHandler',
});
console.log(exceptionString);
logService.exception(exceptionString);
});
}
......@@ -19,8 +19,6 @@ import keychain from './src/keychain/KeychainStore';
import blockchainTransaction from './src/blockchain/transaction-modal/BlockchainTransactionStore';
import blockchainWallet from './src/blockchain/wallet/BlockchainWalletStore';
import blockchainWalletSelector from './src/blockchain/wallet/BlockchainWalletSelectorStore';
import payments from './src/payments/PaymentsStore';
import checkoutModal from './src/payments/checkout/CheckoutModalStore';
import capture from './src/capture/CaptureStore';
import withdraw from './src/wallet/tokens/WithdrawStore';
import hashtag from './src/common/stores/HashtagStore';
......@@ -29,11 +27,13 @@ import groupsBar from './src/groups/GroupsBarStore';
import sessionService from './src/common/services/session.service';
import logService from './src/common/services/log.service';
import SubscriptionRequestStore from './src/channel/subscription/SubscriptionRequestStore';
/**
* App stores
*/
const stores = {
subscriptionRequest: new SubscriptionRequestStore(),
newsfeed: new newsfeed(),
notifications: new notifications(),
notificationsSettings: new notificationsSettings(),
......@@ -55,8 +55,6 @@ const stores = {
blockchainWallet: new blockchainWallet(),
blockchainWalletSelector: new blockchainWalletSelector(),
channelSubscribersStore: new channelSubscribersStore(),
payments: new payments(),
checkoutModal: new checkoutModal(),
capture: new capture(),
withdraw: new withdraw(),
hashtag: new hashtag(),
......
# Minds Mobile Apps
## Branch Structure
| Branch | |
|-----------|------------------------------------------------------------------------------------------------------------------------------------|
| master | Approved code ready to merged into the next stable release. All tests should pass and be in a 'ready' state |
| stable/* | Stable builds, inherited from `release/*` branches. Fastlane automatically deploys these builds. |
| release/* | WIP builds. Run `fastlane run increment_version_number` upon creating the branch. |
| feat/* | New branches should be made for each Gitlab issue. Merge requests should be opened pointing towards the respective release branch. |
## Increasing the version number
### Patch
`fastlane run increment_version_number bump_type:patch`
### Minor
`fastlane run increment_version_number bump_type:minor`
### Major
`fastlane run increment_version_number bump_type:major`
## Platforms
- iOS
- Android
## Building
## Install dependencies
- `yarn install`
- `react-native run-ios` or `react-native run-android`
- `cd ios && pod install` (iOS only)
## Building
- `yarn android` or `yarn ios`
## Testing
- `yarn test`
## Testing e2e (macOS)
Install the detox cli
- `brew tap wix/brew`
- `brew install applesimutils`
- `yarn global add detox-cli`
Run the tests
- `detox build -c ios.sim.debug`
- `detox test -c ios.sim.debug`
You can use -c ios.sim.release for e2e test a production build
### _Copyright Minds 2018_
\ No newline at end of file
### _Copyright Minds 2018_
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver) => {
// should ask for permissions
const permmision = await driver.waitForElementById('com.android.packageinstaller:id/permission_allow_button', wd.asserters.isDisplayed, 10000)
// we accept
permmision.click();
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver) => {
// select first image
const firstImage = await driver.waitForElementByAccessibilityId('Gallery Image 0', wd.asserters.isDisplayed, 5000);
await firstImage.click();
await sleep(3000);
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver, amount) => {
const lockButton = await driver.waitForElementByAccessibilityId('Post lock button', wd.asserters.isDisplayed, 5000);
await lockButton.click();
const postInput = await driver.waitForElementByAccessibilityId('Poster lock amount input', wd.asserters.isDisplayed, 5000);
await postInput.type(amount);
// we press post button
const postButton = await driver.elementByAccessibilityId('Poster lock done button');
await postButton.click();
}
\ No newline at end of file
export default async(driver) => {
const username = await driver.elementByAccessibilityId('username input');
const password = await driver.elementByAccessibilityId('password input');
const loginButton = await driver.elementByAccessibilityId('login button');
await username.type(process.env.loginUser);
await password.type(process.env.loginPass);
await loginButton.click();
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver, text) => {
// post screen must be shown
const postInput = await driver.waitForElementByAccessibilityId('PostInput', wd.asserters.isDisplayed, 5000);
await postInput.type(text);
// we press post button
const postButton = await driver.elementByAccessibilityId('Capture Post Button');
await postButton.click();
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver) => {
// tap the capture button
const button = await driver.waitForElementByAccessibilityId('CaptureButton', wd.asserters.isDisplayed, 10000);
button.click();
return button;
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver, options) => {
// tap the toggle button
const button = await driver.waitForElementByAccessibilityId('NSFW button', wd.asserters.isDisplayed, 5000);
await button.click();
// wait until the menu is shown
await sleep(500);
for (let index = 0; index < options.length; index++) {
const name = options[index];
const element = await driver.elementByAccessibilityId(`NSFW ${name}`);
await element.click();
}
let action = new wd.TouchAction(driver);
action.tap({x:100, y:170});
await action.release().perform();
}
\ No newline at end of file
import wd from 'wd';
import reporterFactory from '../tests-helpers/browserstack-reporter.factory';
import post from './actions/post';
import login from './actions/login';
import { driver, capabilities} from './config';
import sleep from '../src/common/helpers/sleep';
import pressCapture from './actions/pressCapture';
import acceptPermissions from './actions/acceptPermissions';
import attachPostGalleryImage from './actions/attachPostGalleryImage';
import selectNsfw from './actions/selectNsfw';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
const data = {sessiondID: null};
jasmine.getEnv().addReporter(reporterFactory(data));
//TODO: add support for ios to this test (xpath)
describe('Activity flow tests', () => {
beforeAll(async () => {
await driver.init(capabilities);
data.sessiondID = await driver.getSessionId();
console.log('BROWSERSTACK_SESSION: ' + data.sessiondID);
await driver.waitForElementByAccessibilityId('username input', wd.asserters.isDisplayed, 5000);
// we should login
await login(driver);
});
afterAll(async () => {
await driver.quit();
});
it('should post a text and see it in the newsfeed', async () => {
const str = 'My e2e activity';
// press capture button
await pressCapture(driver);
// accept gallery permissions
await acceptPermissions(driver);
// make the post
await post(driver, str);
// should post and return to the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 10000);
// the first element of the list should be the post
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[2]');
expect(await textElement.text()).toBe(str);
});
it('should like the post', async() => {
const likeButton = await driver.waitForElementByAccessibilityId('Thumb up activity button', 5000);
await likeButton.click();
const likeCount = await driver.waitForElementByAccessibilityId('Thumb up count', 5000);
expect(await likeCount.text()).toBe('1');
});
it('should unlike the post', async() => {
const likeButton = await driver.waitForElementByAccessibilityId('Thumb down activity button', 5000);
await likeButton.click();
const likeCount = await driver.waitForElementByAccessibilityId('Thumb down count', 5000);
expect(await likeCount.text()).toBe('1');
});
});
\ No newline at end of file
import factory from '../tests-helpers/e2e-driver.factory';
const customCapabilities = {
'device' : 'Samsung Galaxy S9',
'os_version' : '8.0'
};
let driver, capabilities;
if (process.env.e2elocal) {
[driver, capabilities] = factory('androidLocal', {});
} else {
[driver, capabilities] = factory('browserStack', customCapabilities);
}
export {driver, capabilities} ;