...
 
......@@ -9,6 +9,7 @@ import {
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'));
// mock backhandler
......
import login from "./login";
import { waitForElement, tapElement, waitForAndTap } from "../helpers/waitFor";
export default async function() {
await waitFor(element(by.id('usernameInput'))).toBeVisible().withTimeout(5000);
export const capturePoster = async () => {
await waitForElement(by.id('usernameInput'));
await login(process.env.loginUser, process.env.loginPass);
await expect(element(by.id('NewsfeedScreen'))).toBeVisible();
await element(by.id('captureFab')).tap();
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 capturePoster from './actions/capturePoster';
import { waitForElement, waitForAndType, tapElement, waitForAndTap } from './helpers/waitFor';
const deletePost = async () => {
await waitForAndTap(by.id, 'ActivityMoreButton');
await waitForAndTap(by.id, 'deleteOption');
await waitForAndTap(by.text, 'Ok');
await waitForAndTap(by.text, 'Ok');
}
import { waitForElement, waitForAndType, tapElement, waitForAndTap } from './helpers/waitFor';
import { capturePoster, deletePost } from './actions/capturePoster';
describe('Comment Flow', () => {
beforeEach(async () => {
......@@ -29,25 +23,24 @@ describe('Comment Flow', () => {
const replyText = 'replyE2ETest';
// create post
await waitForAndType(by.id, 'PostInput', text);
await tapElement(by.id, 'CapturePostButton');
await waitForAndType(by.id('PostInput'), text);
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id, 'NewsfeedScreen');
await waitForElement(by.id('NewsfeedScreen'));
// add comment
await waitForAndTap(by.id, 'ActivityCommentButton');
await waitForAndType(by.id, 'CommentText', commentText);
await tapElement(by.id, 'PostCommentButton');
await waitForAndTap(by.id('ActivityCommentButton'));
await waitForAndType(by.id('CommentText'), commentText);
await tapElement(by.id('PostCommentButton'));
// add reply
await waitForAndTap(by.id, 'ReplyCommentButton');
await waitFor(element(by.id('CommentText').withAncestor(by.id('CommentParentView')))).toBeVisible().withTimeout(10000);
await element(by.id('CommentText').withAncestor(by.id('CommentParentView'))).typeText(replyText);
await element(by.id('PostCommentButton').withAncestor(by.id('CommentParentView'))).tap();
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 waitFor(element(by.label(`@${process.env.loginUser} ${replyText}`))).toBeVisible().withTimeout(10000);
await waitForElement(by.label(`@${process.env.loginUser} ${replyText}`));
// finish
await deletePost();
......
const TIME = 10000;
export const TIME = 10000;
export const waitForElement = async (by, needle) => {
await waitFor(element(by(needle))).toBeVisible().withTimeout(TIME);
export const waitForElement = async (e) => {
await waitFor(element(e)).toBeVisible().withTimeout(TIME);
}
export const tapElement = async (by, needle) => {
await element(by(needle)).tap();
export const tapElement = async (e) => {
await element(e).tap();
}
export const typeText = async (by, needle, text) => {
await element(by(needle)).typeText(text);
export const typeText = async (e, text) => {
await element(e).typeText(text);
}
export const waitForAndTap = async (by, needle) => {
await waitForElement(by, needle);
await tapElement(by, needle);
export const waitForAndTap = async (e) => {
await waitForElement(e);
await tapElement(e);
}
export const waitForAndType = async (by, needle, text) => {
await waitForElement(by, needle);
await typeText(by, needle, text);
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 capturePoster from './actions/capturePoster';
import { waitForElement, waitForAndType, tapElement, waitForAndTap } from './helpers/waitFor';
const deletePost = async () => {
await waitForAndTap(by.id, 'ActivityMoreButton');
await waitForAndTap(by.id, 'deleteOption');
await waitForAndTap(by.text, 'Ok');
await waitForAndTap(by.text, 'Ok');
}
import { deletePost, capturePoster } from './actions/capturePoster';
describe('Post Flow', () => {
beforeEach(async () => {
......@@ -27,11 +20,11 @@ describe('Post Flow', () => {
const text = 'e2eTest';
// create post
await waitForAndType(by.id, 'PostInput', text);
await tapElement(by.id, 'CapturePostButton');
await waitForAndType(by.id('PostInput'), text);
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id, 'NewsfeedScreen');
await waitForElement(by.id('NewsfeedScreen'));
await deletePost();
......@@ -41,15 +34,15 @@ describe('Post Flow', () => {
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');
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 waitForElement(by.id('NewsfeedScreen'));
await deletePost();
......@@ -59,15 +52,15 @@ describe('Post Flow', () => {
const text = 'e2eTest';
// create post
await waitForAndType(by.id, 'PostInput', text);
await tapElement(by.id, 'GalleryImage0');
await tapElement(by.id, 'GalleryImage0');
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');
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id, 'NewsfeedScreen');
await waitForElement(by.id('NewsfeedScreen'));
await deletePost();
......@@ -77,15 +70,15 @@ describe('Post Flow', () => {
const text = 'e2eTest';
// create post
await waitForAndType(by.id, 'PostInput', text);
await tapElement(by.id, 'GalleryImage0');
await tapElement(by.id, 'GalleryImage0');
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');
await waitForAndTap(by.id('AttachmentDeleteButton'));
await tapElement(by.id('CapturePostButton'));
// wait for newsfeed
await waitForElement(by.id, 'NewsfeedScreen');
await waitForElement(by.id('NewsfeedScreen'));
await deletePost();
......
......@@ -42,6 +42,7 @@ export default class NavNextButton extends Component {
style.button,
this.props.style,
]}
testID="NavNextButton"
>
<View style={style.row}>
{submitContent}
......
import React from 'react';
import { View, StyleSheet, Animated, Easing, Dimensions } from 'react-native';
const { height, width } = Dimensions.get('window');
export default class Pulse extends React.Component {
constructor(props) {
super(props);
this.anim = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.anim, {
toValue: 1,
duration: this.props.interval,
easing: Easing.in,
})
.start();
}
render() {
const { size, pulseMaxSize, borderColor, backgroundColor, getStyle } = this.props;
return (
<View style={[styles.circleWrapper, {
width: pulseMaxSize,
height: pulseMaxSize,
marginLeft: -pulseMaxSize/2,
marginTop: -pulseMaxSize/2,
}]}>
<Animated.View
style={[styles.circle, {
borderColor,
backgroundColor,
width: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [size, pulseMaxSize]
}),
height: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [size, pulseMaxSize]
}),
borderRadius: pulseMaxSize/2,
opacity: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 0]
})
}, getStyle && getStyle(this.anim)]}
/>
</View>
);
}
import { View, StyleSheet } from 'react-native';
import Animated, { Easing } from "react-native-reanimated";
import { bInterpolate, loop } from "react-native-redash";
const { Value, useCode, set } = Animated;
export default function(props) {
const animation = new Value(0);
useCode(
set(
animation,
loop({
toValue: 1,
duration: 1000,
easing: Easing.in(Easing.ease),
}),
),
[animation],
);
const scale = bInterpolate(animation, 1, 1.3);
const opacity = bInterpolate(animation, 1, 0);
const pulseMaxSize = Math.round(1.3 * props.size);
return (
<View
style={[
styles.circleWrapper,
{
width: pulseMaxSize,
height: pulseMaxSize,
marginLeft: -pulseMaxSize/2,
marginTop: -pulseMaxSize/2,
}
]}
>
<Animated.View
style={{
transform: [{scale}],
backgroundColor: 'red',
borderRadius: props.size / 2,
width: props.size,
height: props.size,
opacity,
}}
/>
</View>
);
}
const styles = StyleSheet.create({
circleWrapper: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
// left: width/8,
// top: height/2,
},
circle: {
borderWidth: 4 * StyleSheet.hairlineWidth,
},
circleWrapper: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
// left: width/8,
// top: height/2,
},
circle: {
borderWidth: 4 * StyleSheet.hairlineWidth,
},
});
\ No newline at end of file
import React from 'react';
import { View, Image, TouchableOpacity, Animated, Easing } from 'react-native';
import { View, TouchableOpacity, StyleSheet } from 'react-native';
import Pulse from './Pulse';
import FastImage from 'react-native-fast-image';
/**
* Based on https://github.com/wissile/react-native-pulse-anim
* Pulse avatar
*/
export default class PulseAnimAvatar extends React.Component {
constructor(props) {
super(props);
state = {};
this.state = {
circles: []
};
/**
* Derive state from props
* @param {object} nextProps
* @param {object} prevState
*/
static getDerivedStateFromProps(nextProps, prevState) {
if (
nextProps.size !== prevState.size ||
nextProps.avatar !== prevState.avatar
) {
return {
sizeStyle: {
width: nextProps.size,
height: nextProps.size,
},
imageStyle: {
width: nextProps.size,
height: nextProps.size,
borderRadius: nextProps.size / 2,
backgroundColor: nextProps.avatarBackgroundColor
},
avatarUri: {
uri: nextProps.avatar
},
};
}
return null;
}
this.counter = 1;
this.setInterval = null;
this.anim = new Animated.Value(1);
}
/**
* Render
*/
render() {
const {onPress} = this.props;
componentDidMount() {
this.setCircleInterval();
}
componentWillUnmount() {
clearInterval(this.setInterval);
}
setCircleInterval() {
this.setInterval = setInterval(this.addCircle.bind(this), this.props.interval);
this.addCircle();
}
addCircle() {
this.setState({ circles: [...this.state.circles, this.counter] });
this.counter++;
}
onPressIn() {
Animated.timing(this.anim, {
toValue: this.props.pressInValue,
duration: this.props.pressDuration,
easing: this.props.pressInEasing,
}).start(() => clearInterval(this.setInterval));
}
onPressOut() {
Animated.timing(this.anim, {
toValue: 1,
duration: this.props.pressDuration,
easing: this.props.pressOutEasing,
}).start(this.setCircleInterval.bind(this));
}
render() {
const { size, avatar, avatarBackgroundColor, interval, onPress } = this.props;
return (
<View style={{
// flex: 1,
backgroundColor: 'transparent',
// width: size,
// height: size,
justifyContent: 'center',
alignItems: 'center',
}}>
{this.state.circles.map((circle) => (
<Pulse
key={circle}
{...this.props}
/>
))}
<TouchableOpacity
activeOpacity={.5}
// onPressIn={this.onPressIn.bind(this)}
// onPressOut={this.onPressOut.bind(this)}
return (
<View style={styles.main}>
<Pulse
{...this.props}
/>
<TouchableOpacity
activeOpacity={.5}
onPress={onPress}
style={{
width: size,
height: size,
}}
>
<FastImage
source={{ uri: avatar }}
style={{
width: size,
height: size,
borderRadius: size/2,
backgroundColor: avatarBackgroundColor,
...this.props.style
}}
/>
</TouchableOpacity>
</View>
);
}
style={this.state.sizeStyle}
>
<FastImage
source={this.state.avatarUri}
style={[this.state.imageStyle, this.props.style]}
/>
</TouchableOpacity>
</View>
);
}
}
PulseAnimAvatar.defaultProps = {
interval: 2000,
size: 100,
pulseMaxSize: 200,
avatar: undefined,
avatarBackgroundColor: 'transparent',
pressInValue: 0.8,
pressDuration: 150,
pressInEasing: Easing.in,
pressOutEasing: Easing.in,
borderColor: '#1c1d1f',
backgroundColor: '#D1D1D1',
getStyle: undefined,
};
\ No newline at end of file
style: null,
};
const styles = StyleSheet.create({
main: {
backgroundColor: 'transparent',
justifyContent: 'center',
alignItems: 'center',
},
});
......@@ -50,6 +50,7 @@ export default class SearchView extends PureComponent {
'transparent'
}
style={styles.input}
testID="MessengerContactText"
/>
{rIcon}
</View>
......
......@@ -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}
......
......@@ -43,7 +43,7 @@ let screens = {
Messenger: {
screen: withErrorBoundaryScreen(MessengerScreen),
navigationOptions: {
tabBarTestID:'Messenger tab button',
tabBarTestID:'MessengerTabButton',
tabBarAccessibilityLabel: 'Messenger tab button',
},
},
......
......@@ -1493,6 +1493,11 @@ abort-controller@^3.0.0:
dependencies:
event-target-shim "^5.0.0"
abs-svg-path@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf"
integrity sha1-32Acjo0roQ1KdtYl4japo5wnI78=
absolute-path@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7"
......@@ -7423,6 +7428,13 @@ normalize-path@^2.1.1:
dependencies:
remove-trailing-separator "^1.0.1"
normalize-svg-path@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-1.0.1.tgz#6f729ad6b70bb4ca4eff2fe4b107489efe1d56fe"
integrity sha1-b3Ka1rcLtMpO/y/ksQdInv4dVv4=
dependencies:
svg-arc-to-cubic-bezier "^3.0.0"
normalize-url@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
......@@ -7969,6 +7981,11 @@ parse-path@^4.0.0:
is-ssh "^1.3.0"
protocols "^1.4.0"
parse-svg-path@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb"
integrity sha1-en7A0esG+lMlx9PgCbhZoJtdSes=
parse-url@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f"
......@@ -8790,6 +8807,16 @@ react-native-reanimated@^1.3.0:
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-1.4.0.tgz#7f1acbf9be08492d834f512700570978052be2f9"
integrity sha512-tO7nSNNP+iRLVbkcSS5GXyDBb7tSI02+XuRL3/S39EAr35rnvUy2JfeLUQG+fWSObJjnMVhasUDEUwlENk8IXw==
react-native-redash@^8.6.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/react-native-redash/-/react-native-redash-8.6.0.tgz#a99f1e714f076816b17f44cbe77661a915079feb"
integrity sha512-Hjaz+rcyK/8w84/cPgJVajcE/86y8njw/OVp74qXRnnzhEo+n4O5UGcmP+WyVxCZ2mtgfYJevoqGP5GczEzM8Q==
dependencies:
abs-svg-path "^0.1.1"
normalize-svg-path "^1.0.1"
parse-svg-path "^0.1.2"
use-memo-one "^1.1.1"
react-native-safe-area-view@^0.14.1, react-native-safe-area-view@^0.14.6:
version "0.14.8"
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.14.8.tgz#ef33c46ff8164ae77acad48c3039ec9c34873e5b"
......@@ -10380,6 +10407,11 @@ supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
svg-arc-to-cubic-bezier@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6"
integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==
swarm-js@0.1.39:
version "0.1.39"
resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.39.tgz#79becb07f291d4b2a178c50fee7aa6e10342c0e8"
......@@ -10935,6 +10967,11 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-memo-one@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c"
integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
......