Expanded Example application

This commit is contained in:
Vijay Sharma 2016-05-08 10:39:09 -04:00
parent e85b0170cf
commit b607834e31
12 changed files with 771 additions and 127 deletions

View File

@ -61,13 +61,6 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
@ -75,6 +68,13 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/debug" />

View File

@ -1,61 +1,4 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, {
AppRegistry,
Component,
StyleSheet,
Text,
View
} from 'react-native';
const TestFairyBridge = require('react-native-testfairy');
class TestFairy extends Component {
componentWillMount() {
TestFairyBridge.begin('5b3af35e59a1e074e2d50675b1b629306cf0cfbd');
}
componentDidMount() {
TestFairyBridge.hideView(this.refs.instructions);
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions} ref="instructions">
To get started, edit index.android.js
</Text>
<Text style={styles.instructions}>
Shake or press menu button for dev menu
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
const {AppRegistry} = require('react-native');
const TestFairy = require('./js/app');
AppRegistry.registerComponent('TestFairy', () => TestFairy);

View File

@ -1,62 +1,4 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, {
AppRegistry,
Component,
StyleSheet,
Text,
View
} from 'react-native';
const TestFairyBridge = require('react-native-testfairy');
class TestFairy extends Component {
componentWillMount() {
TestFairyBridge.begin('5b3af35e59a1e074e2d50675b1b629306cf0cfbd');
}
componentDidMount() {
TestFairyBridge.hideView(this.refs.instructions);
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.ios.js
</Text>
<Text style={styles.instructions} ref="instructions">
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
const {AppRegistry} = require('react-native');
const TestFairy = require('./js/app');
AppRegistry.registerComponent('TestFairy', () => TestFairy);

53
example/js/actions.js Normal file
View File

@ -0,0 +1,53 @@
const base64 = require('base-64');
const endpoint = "https://app.testfairy.com/api/1";
module.exports = {
projects: function(login) {
var authentication = base64.encode(`${login.email}:${login.token}`);
var url = endpoint + "/projects"
return fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Basic ${authentication}`
}
})
.then(res => res.json());
},
builds: function(project, login) {
var authentication = base64.encode(`${login.email}:${login.token}`);
var url = endpoint + `/projects/${project.id}/builds`
return fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Basic ${authentication}`
}
})
.then(res => res.json());
},
sessions: function(project, build, login) {
var authentication = base64.encode(`${login.email}:${login.token}`);
var url = endpoint + `/projects/${project.id}/builds/${build.id}/sessions`
return fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Basic ${authentication}`
}
})
.then(res => res.json());
},
session: function(project, build, session, login) {
var authentication = base64.encode(`${login.email}:${login.token}`);
var url = endpoint + `/projects/${project.id}/builds/${build.id}/sessions/${session.id}`
return fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Basic ${authentication}`
}
})
.then(res => res.json());
}
}

22
example/js/app.js Normal file
View File

@ -0,0 +1,22 @@
'use strict';
import React from 'react-native'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import reducers from './reducers'
import Main from './main'
const createStoreWithMW = applyMiddleware(thunk)(createStore)
const store = createStoreWithMW(reducers)
module.exports = React.createClass({
render: function () {
return (
<Provider store={store}>
<Main />
</Provider>
);
}
});

118
example/js/list.js Normal file
View File

@ -0,0 +1,118 @@
'use strict';
var React = require('react-native');
var {
View,
Text,
StyleSheet,
ListView,
RefreshControl
} = React;
import NavBar, { NavButton, NavButtonText, NavTitle } from 'react-native-nav'
module.exports = React.createClass({
getInitialState: function() {
return {
refreshing: false,
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
})
}
},
componentDidMount: function() {
this.onRefresh();
},
componentWillReceiveProps: function(props) {
const {dataSource} = this.state;
this.setState({
dataSource: dataSource.cloneWithRows(props.items)
});
},
render: function () {
const {dataSource} = this.state;
var navigation;
if (this.props.hasBack) {
navigation = (
<NavBar style={styles} statusBar={{ barStyle: 'light-content' }}>
<NavButton onPress={() => this.props.navigator.pop()}><NavButtonText>{"Back"}</NavButtonText></NavButton>
<NavTitle style={styles.title}>{this.props.title}</NavTitle>
<NavButton onPress={() => this.props.onLogout()}><NavButtonText>{'Logout'}</NavButtonText></NavButton>
</NavBar>
);
} else {
navigation = (
<NavBar style={styles} statusBar={{ barStyle: 'light-content' }}>
<NavButton><NavButtonText>{' '}</NavButtonText></NavButton>
<NavTitle style={styles.title}>{this.props.title}</NavTitle>
<NavButton onPress={() => this.props.onLogout()}><NavButtonText>{'Logout'}</NavButtonText></NavButton>
</NavBar>
);
}
if (dataSource.getRowCount() == 0 && this.state.refreshing == false) {
return (
<View style={styles.container}>
{navigation}
<View style={styles.emptyView}>
<Text>No {this.props.title} found!</Text>
</View>
</View>
);
}
return (
<View style={styles.container}>
{navigation}
<ListView
dataSource={dataSource}
renderRow={this.renderRow}
style={styles.listView}>
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.onRefresh}/>
</ListView>
</View>
);
},
renderRow: function(item) {
return this.props.renderRow(item, this.props);
},
onRefresh: function() {
const {fetch} = this.props;
this.setState({refreshing: true});
fetch(this.props)
.then(() => {
this.setState({refreshing: false});
});
}
});
var styles = StyleSheet.create({
emptyView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
container: {
flex: 1
},
statusBar: {
backgroundColor: '#000',
},
navBar: {
backgroundColor: '#212121',
},
title: {
color: '#fff',
flex: 1
},
listView: {
}
});

243
example/js/main.js Normal file
View File

@ -0,0 +1,243 @@
'use strict';
const React = require('react-native');
const {Platform, StyleSheet, Navigator} = React;
const {View, Text, TouchableHighlight} = React;
const {connect} = require('react-redux');
const TestFairyBridge = require('react-native-testfairy');
const SignIn = require('./signin');
const Projects = require('./projects');
const List = require('./list');
const Session = require('./session');
const actions = require('./actions');
var Main = React.createClass({
componentWillMount() {
TestFairyBridge.begin('5b3af35e59a1e074e2d50675b1b629306cf0cfbd');
},
render: function () {
return (
<Navigator
ref='navigator'
style={styles.container}
initialRoute={{}}
configureScene={(route) => {
if (Platform.OS === 'android') {
return Navigator.SceneConfigs.FloatFromBottomAndroid;
} else {
return Navigator.SceneConfigs.FloatFromRight;
}
}}
renderScene={this.renderScene}
/>
);
},
isLoggedIn: function() {
var {login} = this.props;
return login.email && login.token;
},
renderScene: function(route, navigator) {
var {
login,
fetchProjects, projects,
fetchBuilds, builds,
fetchSessions, sessions,
fetchSession, session
} = this.props;
if (route.session) {
return (<Session
login={login}
project={route.project}
build={route.build}
session={route.session}
fetch={fetchSession}
current={session}
navigator={navigator}
hasBack={true}
onLogout={this.props.doLogout}
/>)
}
if (route.sessions) {
return (<List
title={`${route.project.name} Sessions`}
login={login}
fetch={fetchSessions}
items={sessions}
navigator={navigator}
renderRow={this.renderSessionItem}
build={route.build}
project={route.project}
hasBack={true}
onLogout={this.props.doLogout}
/>
);
}
if (route.builds) {
return (<List
title={`${route.project.name} Builds`}
login={login}
fetch={fetchBuilds}
items={builds}
navigator={navigator}
renderRow={this.renderBuildItem}
project={route.project}
hasBack={true}
onLogout={this.props.doLogout}
/>
);
}
// if (route.projects) {
if (this.isLoggedIn()) {
return (
<List
title={'Projects'}
login={login}
fetch={fetchProjects}
items={projects}
navigator={navigator}
renderRow={this.renderProjectItem}
hasBack={false}
onLogout={this.props.doLogout}
/>
);
}
return (
<SignIn
login={login}
onLogin={this.props.doLogin}/>
);
},
renderProjectItem: function(project, props) {
return (
<TouchableHighlight onPress={() => {
this.refs.navigator.push({builds: true, project: project})
}} underlayColor='#CCC'>
<View style={styles.projectRow}>
<Text style={styles.projectRowTitle}>{project.name}</Text>
<Text style={styles.projectRowSubTitle}>{project.packageName}</Text>
</View>
</TouchableHighlight>
);
},
renderBuildItem: function(build, props) {
return (
<TouchableHighlight onPress={() => {
this.refs.navigator.push({sessions: true, build: build, project: props.project})
}} underlayColor='#CCC'>
<View style={styles.projectRow}>
<Text style={styles.projectRowTitle}>{build.appName}</Text>
<Text style={styles.projectRowSubTitle}>{build.version}</Text>
</View>
</TouchableHighlight>
);
},
renderSessionItem: function(session, props) {
return (
<TouchableHighlight onPress={() => {
this.refs.navigator.push({session: true, build: props.build, project: props.project, session: session})
}} underlayColor='#CCC'>
<View style={styles.projectRow}>
<Text style={styles.projectRowTitle}>{session.testerEmail}</Text>
<Text style={styles.projectRowSubTitle}>Session #{session.id} - {session.duration}</Text>
</View>
</TouchableHighlight>
);
}
});
module.exports = connect(state => {
return {...state}
}, (dispatch) => {
return {
doLogin: function(email, token) {
dispatch({
type: 'LOGIN',
item: {email, token}
});
},
doLogout: function() {
dispatch({
type: 'LOGOUT'
});
},
fetchProjects: function(props) {
var login = props.login;
return actions.projects(login)
.then(res => {
dispatch({
type: 'NEW_PROJECTS',
items: res.projects
})
}, err => {
console.log(err);
})
},
fetchBuilds: function(props) {
var {project, login} = props;
return actions.builds(project, login)
.then(res => {
dispatch({
type: 'NEW_BUILDS',
items: res.builds
});
}, err => {
console.log(err);
})
},
fetchSessions: function(props) {
var {project, build, login} = props;
return actions.sessions(project, build, login)
.then(res => {
dispatch({
type: 'NEW_SESSIONS',
items: res.sessions
});
}, err => {
console.log(err);
})
},
fetchSession: function(props) {
var {project, build, login, session} = props;
return actions.session(project, build, session, login)
.then(res => {
console.log(res);
return res;
})
.then(res => {
dispatch({
type: 'SET_SELECTED_SESSION',
item: res.session
});
}, err => {
console.log(err);
})
}
}
})(Main);
const styles = StyleSheet.create({
container: {
flex: 1,
},
projectRow: {
padding: 16
},
projectRowTitle: {
fontSize: 18
},
projectRowSubTitle: {
color: '#666'
}
});

74
example/js/projects.js Normal file
View File

@ -0,0 +1,74 @@
'use strict';
var React = require('react-native');
var {
View,
Text,
StyleSheet,
ListView
} = React;
import NavBar, { NavButton, NavButtonText, NavTitle } from 'react-native-nav'
module.exports = React.createClass({
getInitialState: function() {
return {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
})
}
},
componentDidMount: function() {
const {login, fetchProjects} = this.props;
fetchProjects(login);
},
componentWillReceiveProps: function(props) {
const {dataSource} = this.state;
this.setState({
dataSource: dataSource.cloneWithRows(props.projects)
});
},
render: function () {
const {dataSource} = this.state;
return (
<View style={styles.container}>
<NavBar style={styles} statusBar={{ barStyle: 'light-content' }}>
<NavTitle style={styles.title}>TestFairy</NavTitle>
</NavBar>
<ListView
dataSource={dataSource}
renderRow={this.renderRow}
style={styles.listView}
/>
</View>
);
},
renderRow: function(project) {
return (
<Text>{project.name}</Text>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1
},
statusBar: {
backgroundColor: '#000',
},
navBar: {
backgroundColor: '#212121',
},
title: {
color: '#fff',
flex: 1
},
listView: {
}
});

View File

@ -0,0 +1,47 @@
'use strict';
var { combineReducers } = require('redux');
var initial = {
email: undefined,
token: undefined
};
module.exports = combineReducers({
login: function(state = initial, action) {
if (action.type == 'LOGIN') {
return action.item;
} else if (action.type == 'LOGOUT') {
return {};
}
return state;
},
projects: function(state = [], action) {
if (action.type === 'NEW_PROJECTS') {
return action.items;
}
return state;
},
builds: function(state = [], action) {
if (action.type === 'NEW_BUILDS') {
return action.items;
}
return state;
},
sessions: function(state = [], action) {
if (action.type === 'NEW_SESSIONS') {
return action.items;
}
return state;
},
session: function(state = {}, action) {
if (action.type === 'SET_SELECTED_SESSION') {
return action.item;
} else if (action.type === 'CLEAR_SELECTED_SESSION') {
return {};
}
return state;
}
});

79
example/js/session.js Normal file
View File

@ -0,0 +1,79 @@
'use strict';
var React = require('react-native');
var {
View,
Text,
StyleSheet,
ScrollView
} = React;
import NavBar, { NavButton, NavButtonText, NavTitle } from 'react-native-nav'
module.exports = React.createClass({
getInitialState: function() {
return {
session: this.props.session
};
},
componentWillReceiveProps: function(props) {
var session = Object.assign({}, this.state.session, props.session);
this.setState({
session: session
});
},
componentDidMount: function() {
this.onRefresh();
},
onRefresh: function() {
const {fetch} = this.props;
fetch(this.props);
},
render: function () {
return (
<View style={styles.container}>
<NavBar style={styles} statusBar={{ barStyle: 'light-content' }}>
<NavButton onPress={() => this.props.navigator.pop()}><NavButtonText>{"Back"}</NavButtonText></NavButton>
<NavTitle style={styles.title}>{this.props.project.name} (#{this.props.session.id})</NavTitle>
<NavButton onPress={() => this.props.onLogout()}><NavButtonText>{'Logout'}</NavButtonText></NavButton>
</NavBar>
<ScrollView style={styles.container}>
<View>
{this.sessionItem('Package: ', this.props.project.packageName)}
{this.sessionItem('Version: ', this.props.build.version)}
{this.sessionItem('Tester: ', this.props.session.testerEmail)}
{this.sessionItem('Duration: ', this.props.session.duration)}
{this.sessionItem('Device: ', this.props.session.device)}
</View>
</ScrollView>
</View>
);
},
sessionItem: function(key, value) {
return <View style={{flexDirection: 'row'}}>
<Text style={styles.key}>{key}</Text><Text style={styles.value}>{value}</Text>
</View>
}
});
var styles = StyleSheet.create({
container: {
flex: 1
},
statusBar: {
backgroundColor: '#000',
},
navBar: {
backgroundColor: '#212121',
},
title: {
color: '#fff'
},
key: {
color: '#CCC',
fontSize: 20
},
value: {
fontSize: 20
}
});

118
example/js/signin.js Normal file
View File

@ -0,0 +1,118 @@
'use strict';
var React = require('react-native');
var {
View,
Text,
StatusBar,
StyleSheet,
TextInput,
TouchableHighlight
} = React;
const TestFairyBridge = require('react-native-testfairy');
module.exports = React.createClass({
getInitialState: function() {
return {
email: this.props.login.email,
token: this.props.login.token
};
},
componentDidMount: function() {
TestFairyBridge.hideView(this.refs.emailInput);
TestFairyBridge.hideView(this.refs.apiTokenInput);
},
render: function () {
return (
<View style={styles.container}>
<StatusBar
translucent={true}
/>
<View style={styles.top}>
<Text style={styles.header}>TestFairy</Text>
<View style={styles.inputWrapper}>
<TextInput
ref='emailInput'
style={styles.input}
onChangeText={(text) => this.setState({email: text})}
value={this.state.email}
placeholder={'Email'}
autoCapitalize={'none'}
keyboardType={'email-address'}
autoCorrect={false}
/>
</View>
<View style={styles.inputWrapper}>
<TextInput
ref='apiTokenInput'
style={[styles.input]}
onChangeText={(text) => this.setState({token: text})}
value={this.state.token}
placeholder={'API Token'}
/>
</View>
<TouchableHighlight style={styles.buttonWrapper} onPress={this.doLogin} underlayColor='#CCC'>
<Text style={styles.button}>Sign In</Text>
</TouchableHighlight>
</View>
<View style={styles.bottom}>
</View>
</View>
);
},
doLogin: function() {
this.props.onLogin(this.state.email, this.state.token);
}
});
var styles = StyleSheet.create({
container: {
flex: 1
},
top: {
flex: 5,
marginTop: 100,
},
bottom: {
flex: 4
},
header: {
fontSize: 25,
padding: 4,
textAlign: 'center',
marginBottom: 12
},
inputWrapper: {
},
input: {
padding: 4,
height: 40,
width: 350,
borderColor: 'gray',
borderWidth: 1,
borderRadius: 5,
margin: 5,
alignSelf: 'center',
flex: 1,
textAlign: 'center'
},
buttonWrapper: {
margin: 12,
alignItems: 'center'
},
button: {
padding: 8,
textAlign: 'center',
fontSize: 20,
backgroundColor: '#000000',
color: '#FFFFFF',
borderRadius: 5,
width: 350
}
});

View File

@ -6,8 +6,13 @@
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"base-64": "^0.1.0",
"react": "^0.14.8",
"react-native": "^0.23.1",
"react-native-testfairy": "file:../"
"react-native-nav": "^1.1.2",
"react-native-testfairy": "file:../",
"react-redux": "~4.3.0",
"redux": "~3.2.1",
"redux-thunk": "~1.0.3"
}
}