Merge remote-tracking branch 'origin/redux-saga' into redux-saga

This commit is contained in:
Guilherme Gazzo 2017-08-30 00:05:15 -03:00
commit a6207f504d
No known key found for this signature in database
GPG Key ID: 1F85C9AD922D0829
16 changed files with 256 additions and 199 deletions

View File

@ -36,6 +36,7 @@
"react/forbid-prop-types": 0,
"jsx-quotes": [2, "prefer-single"],
"jsx-a11y/href-no-hash": 0,
"import/prefer-default-export": 0,
"no-underscore-dangle": 0,
"no-return-assign": 0,
"no-param-reassign": 0,

View File

@ -1,3 +1,9 @@
branches:
only:
- develop
- master
- "/^\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$/"
matrix:
include:
- os: linux

View File

@ -1,3 +1 @@
module.exports = {
CachedImage: 'CachedImage'
};
export const CachedImage = 'CachedImage';

View File

@ -1,6 +1,4 @@
module.exports = {
Navigation: {
registerComponent: () => {},
startSingleScreenApp: () => {}
}
export const Navigation = {
registerComponent: () => {},
startSingleScreenApp: () => {}
};

View File

@ -5,40 +5,28 @@ import RoomItem from '../app/components/RoomItem';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
jest.mock('react-native-img-cache', () => {
return {
CachedImage: 'View'
}
});
const component = props => <RoomItem {...props} />
jest.mock('react-native-img-cache', () => { return { CachedImage: 'View' } });
it('renders correctly', () => {
const tree = renderer.create(component({ type: 'd', name: 'name' })).toJSON();
expect(tree).toMatchSnapshot();
expect(renderer.create(<RoomItem type="d" name="name" />).toJSON()).toMatchSnapshot();
});
it('render unread', () => {
const tree = renderer.create(component({ type: 'd', name: 'name', unread: 1 })).toJSON();
expect(tree).toMatchSnapshot();
expect(renderer.create(<RoomItem type="d" name="name" unread={1} />).toJSON()).toMatchSnapshot();
});
it('render unread +999', () => {
const tree = renderer.create(component({ type: 'd', name: 'name', unread: 1000 })).toJSON();
expect(tree).toMatchSnapshot();
expect(renderer.create(<RoomItem type="d" name="name" unread={1000} />).toJSON()).toMatchSnapshot();
});
it('render no icon', () => {
const tree = renderer.create(component({ type: 'X', name: 'name' })).toJSON();
expect(tree).toMatchSnapshot();
expect(renderer.create(<RoomItem type="X" name="name" />).toJSON()).toMatchSnapshot();
});
it('render private group', () => {
const tree = renderer.create(component({ type: 'g', name: 'private-group' })).toJSON();
expect(tree).toMatchSnapshot();
expect(renderer.create(<RoomItem type="g" name="private-group" /> ).toJSON()).toMatchSnapshot();
});
it('render channel', () => {
const tree = renderer.create(component({ type: 'c', name: 'general' })).toJSON();
expect(tree).toMatchSnapshot();
expect(renderer.create(<RoomItem type="c" name="general" />).toJSON()).toMatchSnapshot();
});

View File

@ -4,15 +4,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<RCTScrollView>
<View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -81,15 +99,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -158,15 +194,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -256,15 +310,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -354,15 +426,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -452,15 +542,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -550,15 +658,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -648,15 +774,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -725,15 +869,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
@ -802,15 +964,33 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
</Text>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 56,
"opacity": 1,
"paddingLeft": 16,
"paddingRight": 16,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={

View File

@ -30,124 +30,3 @@ export function login() {
type: 'LOGIN'
};
}
// // GENRES
// export function retrieveMoviesGenresSuccess(res) {
// return {
// type: types.RETRIEVE_MOVIES_GENRES_SUCCESS,
// moviesGenres: res.data
// };
// }
// export function retrieveMoviesGenres() {
// return function (dispatch) {
// return axios.get(`${TMDB_URL}/genre/movie/list?api_key=${TMDB_API_KEY}`)
// .then(res => {
// dispatch(retrieveMoviesGenresSuccess(res));
// })
// .catch(error => {
// console.log(error); //eslint-disable-line
// });
// };
// }
// // POPULAR
// export function retrievePopularMoviesSuccess(res) {
// return {
// type: types.RETRIEVE_POPULAR_MOVIES_SUCCESS,
// popularMovies: res.data
// };
// }
// export function retrievePopularMovies(page) {
// return function (dispatch) {
// return axios.get(`${TMDB_URL}/movie/popular?api_key=${TMDB_API_KEY}&page=${page}`)
// .then(res => {
// dispatch(retrievePopularMoviesSuccess(res));
// })
// .catch(error => {
// console.log('Popular', error); //eslint-disable-line
// });
// };
// }
// // NOW PLAYING
// export function retrieveNowPlayingMoviesSuccess(res) {
// return {
// type: types.RETRIEVE_NOWPLAYING_MOVIES_SUCCESS,
// nowPlayingMovies: res.data
// };
// }
// export function retrieveNowPlayingMovies(page) {
// return function (dispatch) {
// return axios.get(`${TMDB_URL}/movie/now_playing?api_key=${TMDB_API_KEY}&page=${page}`)
// .then(res => {
// dispatch(retrieveNowPlayingMoviesSuccess(res));
// })
// .catch(error => {
// console.log('Now Playing', error); //eslint-disable-line
// });
// };
// }
// // MOVIES LIST
// export function retrieveMoviesListSuccess(res) {
// return {
// type: types.RETRIEVE_MOVIES_LIST_SUCCESS,
// list: res.data
// };
// }
// export function retrieveMoviesList(type, page) {
// return function (dispatch) {
// return axios.get(`${TMDB_URL}/movie/${type}?api_key=${TMDB_API_KEY}&page=${page}`)
// .then(res => {
// dispatch(retrieveMoviesListSuccess(res));
// })
// .catch(error => {
// console.log('Movies List', error); //eslint-disable-line
// });
// };
// }
// // SEARCH RESULTS
// export function retrieveMoviesSearchResultsSuccess(res) {
// return {
// type: types.RETRIEVE_MOVIES_SEARCH_RESULT_SUCCESS,
// searchResults: res.data
// };
// }
// export function retrieveMoviesSearchResults(query, page) {
// return function (dispatch) {
// return axios.get(`${TMDB_URL}/search/movie?api_key=${TMDB_API_KEY}&query=${query}&page=${page}`)
// .then(res => {
// dispatch(retrieveMoviesSearchResultsSuccess(res));
// })
// .catch(error => {
// console.log('Movies Search Results', error); //eslint-disable-line
// });
// };
// }
// // MOVIE DETAILS
// export function retrieveMovieDetailsSuccess(res) {
// return {
// type: types.RETRIEVE_MOVIE_DETAILS_SUCCESS,
// details: res.data
// };
// }
// export function retrieveMovieDetails(movieId) {
// return function (dispatch) {
// return axios.get(`${TMDB_URL}/movie/${movieId}?api_key=${TMDB_API_KEY}&append_to_response=casts,images,videos`)
// .then(res => {
// dispatch(retrieveMovieDetailsSuccess(res));
// })
// .catch(error => {
// console.log('Movie Details', error); //eslint-disable-line
// });
// };
// }

View File

@ -7,7 +7,6 @@ export function setServer(server) {
};
}
export function serverRequest(server) {
console.log(server);
return {
type: SERVER.REQUEST,
server
@ -15,7 +14,6 @@ export function serverRequest(server) {
}
export function addServer(server) {
console.log(server);
return {
type: SERVER.ADD,
server

View File

@ -37,7 +37,6 @@ export default class MessageBox extends React.PureComponent {
}
submit(message) {
// console.log(this.state);
const text = message;
if (text.trim() === '') {
return;
@ -56,8 +55,6 @@ export default class MessageBox extends React.PureComponent {
};
ImagePicker.showImagePicker(options, (response) => {
// console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
@ -84,8 +81,6 @@ export default class MessageBox extends React.PureComponent {
<TextInput
ref={component => this.component = component}
style={styles.textBoxInput}
// value={this.state.text}
// onChangeText={text => this.setState({ text })}
returnKeyType='send'
onSubmitEditing={event => this.submit(event.nativeEvent.text)}
blurOnSubmit={false}

View File

@ -1,10 +1,2 @@
// export const RETRIEVE_MOVIES_GENRES_SUCCESS = 'RETRIEVE_MOVIES_GENRES_SUCCESS';
// export const RETRIEVE_POPULAR_MOVIES_SUCCESS = 'RETRIEVE_POPULAR_MOVIES_SUCCESS';
// export const RETRIEVE_NOWPLAYING_MOVIES_SUCCESS = 'RETRIEVE_NOWPLAYING_MOVIES_SUCCESS';
// export const RETRIEVE_MOVIES_LIST_SUCCESS = 'RETRIEVE_MOVIES_LIST_SUCCESS';
// export const RETRIEVE_MOVIE_DETAILS_SUCCESS = 'RETRIEVE_MOVIE_DETAILS_SUCCESS';
// export const RETRIEVE_MOVIES_SEARCH_RESULT_SUCCESS = 'RETRIEVE_MOVIES_SEARCH_RESULT_SUCCESS';
export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
export const SET_ALL_SETTINGS = 'SET_ALL_SETTINGS';

View File

@ -13,16 +13,6 @@ import styles from './views/Styles';
import store from './lib/createStore';
//
// export const authenticated = (view) => {
// if (!store.getState().login.authenticated) {
// return store.getState().navigator.resetTo({
// screen: 'Login'
// });
// }
// return view;
// };
export const authenticated = WrappedComponent => class _p extends React.PureComponent {
constructor() {
super();
@ -41,12 +31,6 @@ export const authenticated = WrappedComponent => class _p extends React.PureComp
};
//
export class PublicScreen extends React.PureComponent {
// componentWillMount() {
// this.props.setNavigator(this.props.navigator);
// if (this.props.currentServer) {
// return this.props.navigator.navigate('private');
// }
// }
render() {
return ((this.login.isAuthenticated || this.login.user) && <ListServerView {...this.props} />);
}
@ -57,20 +41,13 @@ export class PublicScreen extends React.PureComponent {
setNavigator: navigator => dispatch(setNavigator(navigator))
}))
export class PrivateScreen extends React.PureComponent {
componentWillMount() {
// this.props.setNavigator(this.props.navigator);
}
render() {
// if (this.props.logged) {
// return (<Text>oi</Text>);
// }
return (<LoginView {...this.props} />);
}
}
@connect(() => ({
// logged: state.login.isAuthenticated
}), dispatch => ({
// navigate: routeName => dispatch(NavigationActions.navigate({ routeName })),
setNavigator: navigator => dispatch(setNavigator(navigator)),
appInit: () => dispatch(appInit())
}))

View File

@ -33,7 +33,6 @@ const subscriptionSchema = {
name: 'string',
fname: { type: 'string', optional: true },
rid: 'string',
// u: { _id: 'hKCY2XGzHYk89SAaM', username: 'rodrigo', name: null },
open: { type: 'bool', optional: true },
alert: { type: 'bool', optional: true },
// roles: [ 'owner' ],

View File

@ -50,7 +50,6 @@ const RocketChat = {
const url = `${ _url }/websocket`;
Meteor.connect(url, { autoConnect: true, autoReconnect: true });
// , { autoConnect: false, autoReconnect: false }
Meteor.ddp.on('disconnected', () => {
reduxStore.dispatch(disconnect());
});
@ -58,9 +57,6 @@ const RocketChat = {
reduxStore.dispatch(connectSuccess());
resolve();
});
// Meteor.ddp.on('loggin', () => {
// reduxStore.dispatch(loginSuccess({}));
// });
Meteor.ddp.on('connected', () => {
Meteor.call('public-settings/get', (err, data) => {
if (err) {
@ -86,19 +82,16 @@ const RocketChat = {
});
Meteor.ddp.on('changed', (ddbMessage) => {
// console.log('changed', ddbMessage);
if (ddbMessage.collection === 'stream-room-messages') {
realm.write(() => {
const message = ddbMessage.fields.args[0];
message.temp = false;
message._server = { id: reduxStore.getState().server.server };
// write('messages', message);
realm.create('messages', message, true);
});
}
if (ddbMessage.collection === 'stream-notify-user') {
// console.log(ddbMessage);
realm.write(() => {
const data = ddbMessage.fields.args[1];
data._server = { id: reduxStore.getState().server.server };

View File

@ -1,8 +1,16 @@
import { put, takeEvery, call } from 'redux-saga/effects';
import { put, takeEvery, call, takeLatest, all, race, take } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import { AsyncStorage } from 'react-native';
import { Navigation } from 'react-native-navigation';
import { SERVER } from '../actions/actionsTypes';
import { connectRequest, disconnect } from '../actions/connect';
import { changedServer } from '../actions/server';
import { changedServer, serverSuccess, serverFailure, serverRequest } from '../actions/server';
import RocketChat from '../lib/rocketchat';
import realm from '../lib/realm';
const validate = function* validate(server) {
return yield RocketChat.testServer(server);
};
const selectServer = function* selectServer({ server }) {
yield put(disconnect());
@ -10,7 +18,40 @@ const selectServer = function* selectServer({ server }) {
yield call([AsyncStorage, 'setItem'], 'currentServer', server);
yield put(connectRequest(server));
};
const validateServer = function* validateServer({ server }) {
try {
yield delay(1000);
yield call(validate, server);
yield put(serverSuccess());
} catch (e) {
console.log(e);
yield put(serverFailure(e));
}
};
const addServer = function* addServer({ server }) {
yield call(serverRequest, server);
const { error } = race({
error: take(SERVER.FAILURE),
success: take(SERVER.SUCCESS)
});
if (!error) {
realm.write(() => {
realm.create('servers', { id: server, current: false }, true);
});
Navigation.dismissModal({
animationType: 'slide-down'
});
}
};
const root = function* root() {
yield takeLatest(SERVER.REQUEST, validateServer);
yield takeEvery(SERVER.SELECT, selectServer);
yield takeEvery(SERVER.ADD, addServer);
};
export default root;

17
package-lock.json generated
View File

@ -9631,6 +9631,12 @@
"react-native-drawer-layout": "1.3.2"
}
},
"react-native-easy-markdown": {
"version": "git+https://github.com/lappalj4/react-native-easy-markdown.git#0571414f113346d4a4f4ba32715d87595f8b9a70",
"requires": {
"simple-markdown": "0.1.2"
}
},
"react-native-fetch-blob": {
"version": "0.10.8",
"resolved": "https://registry.npmjs.org/react-native-fetch-blob/-/react-native-fetch-blob-0.10.8.tgz",
@ -9676,9 +9682,9 @@
}
},
"react-native-loading-spinner-overlay": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/react-native-loading-spinner-overlay/-/react-native-loading-spinner-overlay-0.5.1.tgz",
"integrity": "sha512-LOgWzd1AJ4SYeqoomjYcHA0A/ngtBR49gt23GKzHKtiO4I1MFcccJXek774K/xhlIA4qfcwx9ufj5f2HuyBbEw=="
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/react-native-loading-spinner-overlay/-/react-native-loading-spinner-overlay-0.5.2.tgz",
"integrity": "sha512-wIi8PMvD/KnzEgZN865Cm0VhyIba4Zrfwbyi9OPlBYi1+qQDq4MZtDCmKgH8ct7iXE7biTrcBzUxFAAPk9CvCw=="
},
"react-native-meteor": {
"version": "1.1.0",
@ -10645,6 +10651,11 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simple-markdown": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.1.2.tgz",
"integrity": "sha1-PBUQ/kC9nqBncXuKUzyc82MltBM="
},
"simple-plist": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.2.1.tgz",

View File

@ -5,6 +5,7 @@
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"updateSnapshot": "jest --updateSnapshot",
"lint": "eslint .",
"ci": "eslint . && jest && codecov",
"ios": "react-native run-ios",