...
 
Commits (6)
......@@ -144,6 +144,7 @@ exports[`Activity component renders correctly 1`] = `
},
}
}
testID=""
/>
<RemindAction
entity={
......
......@@ -210,6 +210,7 @@ exports[`Activity component renders correctly 1`] = `
}
}
onTranslate={[Function]}
testID=""
toggleEdit={[Function]}
/>
</View>
......
......@@ -7,6 +7,7 @@ exports[`cature poster component should renders correctly 1`] = `
"flex": 1,
}
}
testID="capturePosterView"
>
<View
style={
......
......@@ -127,6 +127,7 @@ exports[`Messenger setup component should render correctly for unlock 1`] = `
"padding": 16,
}
}
testID="MessengerSetupText"
underlineColorAndroid="transparent"
/>
</View>
......
......@@ -136,6 +136,8 @@ android {
versionCode Integer.parseInt(project.versionCode)
versionName project.versionName
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
signingConfigs {
debug {
......@@ -244,6 +246,9 @@ dependencies {
implementation("com.github.bumptech.glide:annotations:${glideVersion}") {
exclude group: "com.android.support", module: "annotations"
}
androidTestImplementation('com.wix:detox:+') { transitive = true }
androidTestImplementation 'junit:junit:4.12'
}
// Run this once to be able to run the application with BUCK
......
package com.minds.mobile;
import com.wix.detox.Detox;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class DetoxTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
@Test
public void runDetoxTests() {
Detox.runTests(mActivityRule);
}
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ buildscript {
supportLibVersion = "28.0.0"
androidXCore = "1.0.2"
glideVersion = "4.7.1"
kotlinVersion = '1.3.10'
}
repositories {
google()
......@@ -19,6 +20,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.google.gms:google-services:4.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
......@@ -27,6 +29,10 @@ buildscript {
allprojects {
repositories {
mavenLocal()
maven {
// All of Detox' artifacts are provided via the npm module
url "$rootDir/../node_modules/detox/Detox-android"
}
maven {
url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
}
......@@ -56,6 +62,10 @@ subprojects {
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.supportLibVersion
defaultConfig {
minSdkVersion 21
}
}
}
}
......
import login from "./login";
import { waitForElement, tapElement, waitForAndTap } from "../helpers/waitFor";
export const capturePoster = async () => {
await waitForElement(by.id('usernameInput'));
await login(process.env.loginUser, process.env.loginPass);
await waitForElement(by.id('NewsfeedScreen'));
await tapElement(by.id('captureFab'));
}
export const deletePost = async () => {
await waitForAndTap(by.id('ActivityMoreButton'));
await waitForAndTap(by.id('deleteOption'));
await waitForAndTap(by.text('Ok'));
await waitForAndTap(by.text('Ok'));
}
import sleep from '../src/common/helpers/sleep';
import { waitForElement, waitForAndType, tapElement, waitForAndTap } from './helpers/waitFor';
import { capturePoster, deletePost } from './actions/capturePoster';
describe('Comment Flow', () => {
beforeEach(async () => {
await device.launchApp({
newInstance: true,
permissions: {
notifications: 'YES',
camera: 'YES',
medialibrary: 'YES',
photos: 'YES',
},
});
await capturePoster();
});
it('should be able to create post and comment', async () => {
const text = 'e2eTest';
const commentText = 'commentE2ETest';
const replyText = 'replyE2ETest';
// create post
await waitForAndType(by.id('PostInput'), text);
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id('NewsfeedScreen'));
// add comment
await waitForAndTap(by.id('ActivityCommentButton'));
await waitForAndType(by.id('CommentText'), commentText);
await tapElement(by.id('PostCommentButton'));
// add reply
await waitForAndTap(by.id('ReplyCommentButton'));
await waitForAndType(by.id('CommentText').withAncestor(by.id('CommentParentView')), replyText);
await tapElement(by.id('PostCommentButton').withAncestor(by.id('CommentParentView')));
// check reply
await waitForElement(by.label(`@${process.env.loginUser} ${replyText}`));
// finish
await deletePost();
});
});
export const TIME = 10000;
export const waitForElement = async (e) => {
await waitFor(element(e)).toBeVisible().withTimeout(TIME);
}
export const tapElement = async (e) => {
await element(e).tap();
}
export const typeText = async (e, text) => {
await element(e).typeText(text);
}
export const waitForAndTap = async (e) => {
await waitForElement(e);
await tapElement(e);
}
export const waitForAndType = async (e, text) => {
await waitForElement(e);
await typeText(e, text);
}
\ No newline at end of file
import sleep from '../src/common/helpers/sleep';
import { waitForElement, waitForAndType, tapElement, waitForAndTap } from './helpers/waitFor';
import login from './actions/login';
describe('Messenger Flow', () => {
beforeEach(async () => {
await device.launchApp({
newInstance: true,
permissions: {
notifications: 'YES',
camera: 'YES',
medialibrary: 'YES',
photos: 'YES',
},
});
await waitFor(element(by.id('usernameInput'))).toBeVisible().withTimeout(5000);
await login(process.env.loginUser, process.env.loginPass);
await expect(element(by.id('NewsfeedScreen'))).toBeVisible();
});
it('should be able to open messenger, unblock and send message', async () => {
const userName = 'JUANMSOLARO_TEST5';
const messageText = 'This is an auto generated message for testing purpose';
// wait for newsfeed
await waitForElement(by.id('NewsfeedScreen'));
await tapElement(by.id('MessengerTabButton'));
await waitForAndType(by.id('MessengerContactText'), userName);
await waitForAndTap(by.id(userName));
await tapElement(by.id(userName));
await waitForAndType(by.id('MessengerSetupText'), process.env.messengerpass);
await tapElement(by.id('NavNextButton'));
await waitForAndType(by.id('ConversationTextInput'), messageText);
await tapElement(by.id('ConversationSendButton'));
await waitForElement(by.label(messageText));
});
});
import sleep from '../src/common/helpers/sleep';
import { waitForElement, waitForAndType, tapElement, waitForAndTap } from './helpers/waitFor';
import { deletePost, capturePoster } from './actions/capturePoster';
describe('Post Flow', () => {
beforeEach(async () => {
await device.launchApp({
newInstance: true,
permissions: {
notifications: 'YES',
camera: 'YES',
medialibrary: 'YES',
photos: 'YES',
},
});
await capturePoster();
});
it('should be able to create a text only post', async () => {
const text = 'e2eTest';
// create post
await waitForAndType(by.id('PostInput'), text);
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id('NewsfeedScreen'));
await deletePost();
});
it('should be able to create a nsfw post with text', async () => {
const text = 'e2eTest';
// create post
await waitForAndType(by.id('PostInput'), text);
await tapElement(by.id('NsfwToggle'));
await tapElement(by.id('NsfwToggle'));
await waitForAndTap(by.id('NsfwReasonNudity'));
await tapElement(by.id('NsfwToggle'));
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id('NewsfeedScreen'));
await deletePost();
});
it('should be able to create a text and image post', async () => {
const text = 'e2eTest';
// create post
await waitForAndType(by.id('PostInput'), text);
await tapElement(by.id('GalleryImage0'));
await tapElement(by.id('GalleryImage0'));
await waitFor(element(by.id('CapturePostButton'))).toBeVisible().withTimeout(120000);
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id('NewsfeedScreen'));
await deletePost();
});
it('should be able to cancel image upload and then post', async () => {
const text = 'e2eTest';
// create post
await waitForAndType(by.id('PostInput'), text);
await tapElement(by.id('GalleryImage0'));
await tapElement(by.id('GalleryImage0'));
await waitForAndTap(by.id('AttachmentDeleteButton'));
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id('NewsfeedScreen'));
await deletePost();
});
});
......@@ -360,7 +360,7 @@ DEPENDENCIES:
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
trunk:
- boost-for-react-native
- JitsiMeetSDK
- libwebp
......@@ -539,4 +539,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 1f47b505eab73a09e6446d8196944df88cfdecc2
COCOAPODS: 1.7.5
COCOAPODS: 1.8.4
......@@ -22,6 +22,7 @@
"@react-native-community/netinfo": "^4.4.0",
"@sentry/react-native": "^1.0.9",
"crypto-js": "^3.1.9-1",
"detox": "^14.8.1",
"entities": "^2.0.0",
"ethjs-signer": "^0.1.1",
"i18n-js": "^3.2.2",
......@@ -121,6 +122,22 @@
"device": {
"type": "iPhone 11"
}
},
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.attached",
"device": {
"name": "ZY2234PLHJ"
}
},
"android.emu.release": {
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"build": "cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..",
"type": "android.attached",
"device": {
"name": "ZY2234PLHJ"
}
}
},
"test-runner": "jest"
......
......@@ -36,7 +36,7 @@ export default class CaptureFab extends Component {
size={28}
containerStyle={ settingsStore.leftHanded ? styles.leftSide : styles.rightSide }
onPress={() => this.navToCapture()}
{...testID('CaptureButton')}
testID={this.props.testID}
/>
);
......
......@@ -159,7 +159,7 @@ export default class CaptureGallery extends PureComponent {
});
}
}
{...testID(`Gallery ${node.type}`)}
testID={`GalleryImage${item.index}`}
>
<Image
source={{ uri : node.image.uri }}
......
......@@ -46,7 +46,7 @@ class CapturePostButton extends Component {
: CS.borderGreyed,
CS.border,
]}
{...testID('Capture Post Button')}
testID={this.props.testID}
>
<Text style={[styles.buttonText, connectivityService.isConnected ? CS.colorPrimary : CS.colorGreyed]}>{text}</Text>
</TouchableOpacity>
......
......@@ -73,6 +73,7 @@ export default class CapturePoster extends Component {
headerRight: <CapturePostButton
onPress={() => !params.isRemind ? this.submit() : this.remind()}
text={params.isRemind ? i18n.t('capture.remind').toUpperCase() : i18n.t('capture.post').toUpperCase()}
testID="CapturePostButton"
/>
});
}
......@@ -186,7 +187,7 @@ export default class CapturePoster extends Component {
multiline={true}
selectTextOnFocus={false}
onSelectionChange={this.onSelectionChanges}
{...testID('PostInput')}
testID="PostInput"
/>
</View>
{showAttachmentFeatures && this.getAttachFeature()}
......@@ -212,7 +213,7 @@ export default class CapturePoster extends Component {
const params = navigation.state.params || {};
return (
<View style={CS.flexContainer}>
<View style={CS.flexContainer} testID="capturePosterView">
<CaptureGallery
onSelected={this.onAttachedMedia}
header={this.getHeader(true)}
......@@ -292,7 +293,7 @@ export default class CapturePoster extends Component {
uri={attachment.uri}
type={attachment.type}
/>
<Icon raised reverse name="md-close" type="ionicon" color='#4690DF' size={18} containerStyle={styles.deleteAttachment} onPress={() => this.deleteAttachment()} {...testID('Attachment Delete Button')} />
<Icon raised reverse name="md-close" type="ionicon" color='#4690DF' size={18} containerStyle={styles.deleteAttachment} onPress={() => this.deleteAttachment()} testID="AttachmentDeleteButton" />
</View>}
<CaptureTabs onSelectedMedia={this.onAttachedMedia} />
</React.Fragment>
......
......@@ -361,7 +361,7 @@ export default class CapturePosterFlags extends Component {
}
renderNsfw() {
if (GOOGLE_PLAY_STORE || Platform.OS === 'ios') return null;
//if (GOOGLE_PLAY_STORE || Platform.OS === 'ios') return null;
return (
<NsfwToggle
containerStyle={styles.cell}
......
......@@ -337,7 +337,7 @@ class CommentList extends React.Component<PropsType, StateType> {
return (
<View>
<View style={[CS.rowJustifyCenter, CS.margin, CS.padding, CS.backgroundWhite, CS.borderRadius12x, CS.borderGreyed, CS.borderHair]}>
<View style={[CS.rowJustifyCenter, CS.margin, CS.padding, CS.backgroundWhite, CS.borderRadius12x, CS.borderGreyed, CS.borderHair]} testID={this.props.parent ? 'CommentParentView' : ''}>
<Image source={avatarImg} style={CmpStyle.posterAvatar} />
<TextInput
style={[CS.flexContainer, CS.marginLeft, inputStyle, {paddingVertical: 2}]}
......@@ -352,6 +352,7 @@ class CommentList extends React.Component<PropsType, StateType> {
maxHeight={110}
value={comments.text}
onSelectionChange={this.onSelectionChanges}
testID='CommentText'
/>
{ attachment.uploading ?
<Progress.Pie progress={attachment.progress} size={36} /> :
......@@ -359,7 +360,7 @@ class CommentList extends React.Component<PropsType, StateType> {
<ActivityIndicator size={'large'} /> :
<View style={[CS.rowJustifyEnd, CS.centered]}>
<TouchableOpacity onPress={this.showAttachment} style={CS.paddingRight2x}><Icon name="md-attach" size={24} style={CS.paddingRight2x} /></TouchableOpacity>
<TouchableOpacity onPress={this.postComment} style={CS.paddingRight2x}><Icon name="md-send" size={24} /></TouchableOpacity>
<TouchableOpacity onPress={this.postComment} style={CS.paddingRight2x} testID='PostCommentButton'><Icon name="md-send" size={24} /></TouchableOpacity>
</View>
}
</View>
......
......@@ -49,7 +49,7 @@ export default class ReplyAction extends Component {
const textStyle = {color};
return (
<TouchableOpacityCustom style={[CommonStyle.flexContainer, CommonStyle.centered, CommonStyle.paddingRight2x, this.props.orientation == 'column' ? CommonStyle.columnAlignCenter : CommonStyle.rowJustifyCenter ]} onPress={this.toggleExpand}>
<TouchableOpacityCustom style={[CommonStyle.flexContainer, CommonStyle.centered, CommonStyle.paddingRight2x, this.props.orientation == 'column' ? CommonStyle.columnAlignCenter : CommonStyle.rowJustifyCenter ]} onPress={this.toggleExpand} testID='ReplyCommentButton'>
<Icon color={color} name={this.iconName} size={this.props.size} />
<Text style={textStyle}>{i18n.t('reply')}</Text>
<Counter size={this.props.size * 0.75} count={entity.replies_count} orientation={this.props.orientation}/>
......
......@@ -42,6 +42,7 @@ export default class NavNextButton extends Component {
style.button,
this.props.style,
]}
testID="NavNextButton"
>
<View style={style.row}>
{submitContent}
......
......@@ -50,6 +50,7 @@ export default class SearchView extends PureComponent {
'transparent'
}
style={styles.input}
testID="MessengerContactText"
/>
{rIcon}
</View>
......
......@@ -58,7 +58,7 @@ export default class NsfwToggle extends Component {
render() {
const isActive = Boolean(this.props.value && this.props.value.length);
const button = (
<Touchable style={this.props.containerStyle} onPress={this.showDropdown} {...testID('NSFW button')}>
<Touchable style={this.props.containerStyle} onPress={this.showDropdown} testID="NsfwToggle">
<MdIcon
name="explicit"
color={isActive ? Colors.explicit : Colors.darkGreyed}
......@@ -80,7 +80,7 @@ export default class NsfwToggle extends Component {
key={i}
onPress={() => this.toggleDropdownOption(reason)}
textStyle={[styles.menuItemText, this.isReasonActive(reason) && styles.menuItemTextActive]}
{...testID(`NSFW ${reason.label}`)}
testID={`NsfwReason${reason.label}`}
>{this.isReasonActive(reason) && <MdIcon name="check" />} {reason.label}</MenuItem>
))}
</Menu>
......
......@@ -210,8 +210,9 @@ export default class ConversationScreen extends Component {
autogrow={true}
maxHeight={110}
value={this.state.text}
testID='ConversationTextInput'
/>
<TouchableOpacity onPress={this.send} style={styles.sendicon}><Icon name="md-send" size={24} style={{ color: '#444' }}/></TouchableOpacity>
<TouchableOpacity onPress={this.send} style={styles.sendicon} testID='ConversationSendButton'><Icon name="md-send" size={24} style={{ color: '#444' }}/></TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
......
......@@ -158,6 +158,7 @@ export default class MessengerScreen extends Component {
refreshing={messengerList.refreshing}
style={styles.listView}
ListEmptyComponent={empty}
testID="MessengerList"
/>
</View>
);
......@@ -210,6 +211,7 @@ export default class MessengerScreen extends Component {
item={row.item}
styles={styles}
navigation={this.props.navigation}
testID={row.item.username.toUpperCase()}
/>
);
}
......
......@@ -112,6 +112,7 @@ export default class MessengerSetup extends Component {
placeholder={i18n.t('passwordPlaceholder')}
secureTextEntry={true}
onChangeText={(password) => this.password = password}
testID="MessengerSetupText"
/>
</View>
......
......@@ -44,7 +44,7 @@ export default class ConversationView extends Component {
}
return (
<TouchableOpacity style={styles.row} onPress={this._navToConversation}>
<TouchableOpacity style={styles.row} onPress={this._navToConversation} testID={this.props.testID}>
<Image source={avatarImg} style={styles.avatar} />
<Text style={styles.body}>{item.username.toUpperCase()}</Text>
{unread}
......
......@@ -115,7 +115,7 @@ export default class NewsfeedScreen extends Component {
header={header}
navigation={this.props.navigation}
/>
<CaptureFab navigation={this.props.navigation}/>
<CaptureFab navigation={this.props.navigation} testID="captureFab"/>
</View>
);
}
......@@ -127,7 +127,7 @@ export default class NewsfeedScreen extends Component {
header={header}
navigation={this.props.navigation}
/>
<CaptureFab navigation={this.props.navigation}/>
<CaptureFab navigation={this.props.navigation} testID="captureFab"/>
</View>
);
}
......
......@@ -37,7 +37,7 @@ export default class Actions extends PureComponent {
<ThumbUpAction entity={entity} me={this.props.user.me}/>
<ThumbDownAction entity={entity} me={this.props.user.me}/>
{!isOwner && hasCrypto && <WireAction owner={entity.ownerObj} navigation={this.props.navigation}/>}
<CommentsAction entity={entity} navigation={this.props.navigation}/>
<CommentsAction entity={entity} navigation={this.props.navigation} testID={this.props.entity.text==='e2eTest' ? 'ActivityCommentButton' : ''}/>
<RemindAction entity={entity} navigation={this.props.navigation}/>
{isOwner && hasCrypto && !isScheduled && <BoostAction entity={entity} navigation={this.props.navigation}/>}
</View> }
......
......@@ -179,6 +179,7 @@ export default class Activity extends Component {
entity={this.props.entity}
navigation={this.props.navigation}
onTranslate={this.showTranslate}
testID={this.props.entity.text==='e2eTest' ? 'ActivityMoreButton' : ''}
/>
</View>
)
......@@ -219,6 +220,7 @@ export default class Activity extends Component {
entity={this.props.entity}
navigation={this.props.navigation}
onTranslate={this.showTranslate}
testID={this.props.entity.text==='e2eTest' ? 'ActivityMoreButton' : ''}
/>}
</View>
</View>
......
......@@ -38,7 +38,7 @@ export default class ActivityActionSheet extends Component {
*/
constructor(props) {
super(props);
this.deleteOption = <Text style={[CS.colorDanger, CS.fontXL]}>{i18n.t('delete')}</Text>
this.deleteOption = <Text testID='deleteOption' style={[CS.colorDanger, CS.fontXL]}>{i18n.t('delete')}</Text>
}
/**
......@@ -310,7 +310,7 @@ export default class ActivityActionSheet extends Component {
onPress={() => this.showActionSheet()}
size={26}
style={CS.colorDarkGreyed}
{...testID('Activity Menu button')}
testID={this.props.testID}
/>
<ActionSheet
ref={o => this.ActionSheet = o}
......
......@@ -39,7 +39,7 @@ class CommentsAction extends Component {
const color = canComment ? (this.props.entity['comments:count'] > 0 ? CS.colorPrimary : CS.colorAction) : CS.colorLightGreyed;
return (
<TouchableOpacityCustom style={[CS.flexContainer, CS.centered, CS.rowJustifyCenter]} onPress={this.openComments}>
<TouchableOpacityCustom style={[CS.flexContainer, CS.centered, CS.rowJustifyCenter]} onPress={this.openComments} testID={this.props.testID}>
<Icon style={[color, CS.marginRight]} name={icon} size={this.props.size} />
<Counter size={this.props.size * 0.70} count={this.props.entity['comments:count']} />
</TouchableOpacityCustom>
......
......@@ -43,7 +43,7 @@ let screens = {
Messenger: {
screen: withErrorBoundaryScreen(MessengerScreen),
navigationOptions: {
tabBarTestID:'Messenger tab button',
tabBarTestID:'MessengerTabButton',
tabBarAccessibilityLabel: 'Messenger tab button',
},
},
......
......@@ -3363,10 +3363,10 @@ detect-newline@^2.1.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
detox@^14.7.1:
version "14.7.1"
resolved "https://registry.yarnpkg.com/detox/-/detox-14.7.1.tgz#14aa533078f88407238a41a53ac0556fadcaf39f"
integrity sha512-XEkficJ5GlMVTbQzJAiuoGVGasgJb5/uFzeuhPrNW7oZ/ar4toOEp57tu95YteFvLEI1ikXe6j7Rp0shAbZKjg==
detox@^14.8.1:
version "14.8.1"
resolved "https://registry.yarnpkg.com/detox/-/detox-14.8.1.tgz#031493f6dd20273862419a13f71487668b525821"
integrity sha512-tyCs/13E9xoNX8d8OPLmti7vVsbZUdQRQXLlkxhdLavILjG3lrABeJDUM0zKiTvUjVDsbcrVKAXVwlONwTJa6g==
dependencies:
"@babel/core" "^7.4.5"
bunyan "^1.8.12"
......