mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-04-24 04:24:52 +08:00
[tests] Move test suite into same repo
This commit is contained in:
17
tests/src/actions/AppActions.js
Normal file
17
tests/src/actions/AppActions.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export const APP_SET_NETWORK_STATE: string = 'APP_SET_NETWORK_STATE';
|
||||
export const APP_SET_APP_STATE: string = 'APP_SET_APP_STATE';
|
||||
|
||||
export function setNetworkState(isConnected: boolean): Object {
|
||||
return {
|
||||
type: APP_SET_NETWORK_STATE,
|
||||
isConnected,
|
||||
};
|
||||
}
|
||||
|
||||
export function setAppState(appState: 'active' | 'background' | 'inactive'): Object {
|
||||
return {
|
||||
type: APP_SET_APP_STATE,
|
||||
appState,
|
||||
};
|
||||
}
|
||||
|
||||
8
tests/src/actions/FCMActions.js
Normal file
8
tests/src/actions/FCMActions.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export const FCM_SET_TOKEN: string = 'FCM_SET_TOKEN';
|
||||
|
||||
export function setToken(token: string): Object {
|
||||
return {
|
||||
type: FCM_SET_TOKEN,
|
||||
token,
|
||||
};
|
||||
}
|
||||
26
tests/src/actions/TestActions.js
Normal file
26
tests/src/actions/TestActions.js
Normal file
@@ -0,0 +1,26 @@
|
||||
export const TEST_SET_SUITE_STATUS: string = 'TEST_SET_SUITE_STATUS';
|
||||
export const TEST_SET_STATUS: string = 'TEST_SET_STATUS';
|
||||
|
||||
export function setSuiteStatus({ suiteId, status, time, message, progress }) {
|
||||
return {
|
||||
type: TEST_SET_SUITE_STATUS,
|
||||
suiteId,
|
||||
|
||||
status,
|
||||
message,
|
||||
|
||||
time,
|
||||
progress,
|
||||
};
|
||||
}
|
||||
|
||||
export function setTestStatus({ testId, status, time = 0, message = null }) {
|
||||
return {
|
||||
type: TEST_SET_STATUS,
|
||||
testId,
|
||||
|
||||
status,
|
||||
message,
|
||||
time,
|
||||
};
|
||||
}
|
||||
51
tests/src/components/Banner.js
Normal file
51
tests/src/components/Banner.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, View, Text } from 'react-native';
|
||||
|
||||
function Banner({ type, children, style, textStyle }) {
|
||||
return (
|
||||
<View style={[styles.banner, styles[type || 'default'], style]}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.bannerText, textStyle]}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Banner.propTypes = {
|
||||
type: React.PropTypes.oneOf([
|
||||
'success',
|
||||
'warning',
|
||||
'error',
|
||||
'info',
|
||||
]),
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.array,
|
||||
]).isRequired,
|
||||
style: View.propTypes.style,
|
||||
textStyle: Text.propTypes.style,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
banner: {
|
||||
alignItems: 'center',
|
||||
elevation: 3,
|
||||
},
|
||||
bannerText: {
|
||||
color: '#ffffff',
|
||||
},
|
||||
warning: {
|
||||
backgroundColor: '#FFC107',
|
||||
},
|
||||
error: {
|
||||
backgroundColor: '#f44336',
|
||||
},
|
||||
success: {
|
||||
backgroundColor: '#4CAF50',
|
||||
},
|
||||
});
|
||||
|
||||
export default Banner;
|
||||
75
tests/src/components/Icon.js
Normal file
75
tests/src/components/Icon.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { View, TouchableHighlight } from 'react-native';
|
||||
import VectorIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
size?: number,
|
||||
color?: string,
|
||||
allowFontScaling?: boolean,
|
||||
style?: Object,
|
||||
rotate?: number,
|
||||
onPress?: () => void,
|
||||
underlayColor?: string,
|
||||
};
|
||||
|
||||
// TODO Spin?
|
||||
class Icon extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.measured = false;
|
||||
this.state = {
|
||||
width: 0,
|
||||
};
|
||||
}
|
||||
|
||||
setDimensions(e) {
|
||||
if (!this.measured) {
|
||||
this.measured = true;
|
||||
this.setState({
|
||||
width: e.nativeEvent.layout.width,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
props: Props;
|
||||
|
||||
render() {
|
||||
const { name, size = 24, color = '#757575', allowFontScaling = true, style, rotate, onPress, underlayColor } = this.props;
|
||||
|
||||
const icon = (
|
||||
<View
|
||||
onLayout={(e) => this.setDimensions(e)}
|
||||
style={[
|
||||
style,
|
||||
rotate ? { transform: [{ rotate: `${rotate}deg` }] } : null,
|
||||
]}
|
||||
>
|
||||
<VectorIcon
|
||||
name={name.toLowerCase().replace(/\s+/g, '-')}
|
||||
size={size}
|
||||
color={color}
|
||||
allowFontScaling={allowFontScaling}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (!onPress) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
underlayColor={underlayColor || 'rgba(0, 0, 0, 0.054)'}
|
||||
onPress={onPress}
|
||||
style={{ padding: 8, borderRadius: (this.state.width + 8) / 2 }}
|
||||
>
|
||||
{icon}
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
70
tests/src/components/OverviewControlButton.js
Normal file
70
tests/src/components/OverviewControlButton.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import some from 'lodash.some';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Toast from 'react-native-simple-toast';
|
||||
|
||||
import { runTests } from '../tests/index';
|
||||
import RunStatus from '../../lib/RunStatus';
|
||||
|
||||
import Icon from '../components/Icon';
|
||||
|
||||
class OverviewControlButton extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.handleOnPress = this.handleOnPress.bind(this);
|
||||
}
|
||||
|
||||
testSuitesAreRunning() {
|
||||
const { testSuites } = this.props;
|
||||
|
||||
return some(Object.values(testSuites), ({ status }) => status === RunStatus.RUNNING);
|
||||
}
|
||||
|
||||
handleOnPress() {
|
||||
const { focusedTestIds, pendingTestIds, tests } = this.props;
|
||||
runTests(tests, { focusedTestIds, pendingTestIds });
|
||||
Toast.show('Running all suite tests.');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.testSuitesAreRunning()) {
|
||||
return (
|
||||
<Icon
|
||||
color={'#ffffff'}
|
||||
size={28}
|
||||
name="autorenew"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Icon
|
||||
color={'#ffffff'}
|
||||
size={28}
|
||||
name="play circle filled"
|
||||
onPress={this.handleOnPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
OverviewControlButton.propTypes = {
|
||||
tests: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
testSuites: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
focusedTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
pendingTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
};
|
||||
|
||||
|
||||
function mapStateToProps({ tests, testSuites, focusedTestIds, pendingTestIds }) {
|
||||
return {
|
||||
tests,
|
||||
testSuites,
|
||||
focusedTestIds,
|
||||
pendingTestIds,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(OverviewControlButton);
|
||||
52
tests/src/components/StatusIndicator.js
Normal file
52
tests/src/components/StatusIndicator.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { View, Text } from 'react-native';
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
|
||||
import RunStatus from '../../lib/RunStatus';
|
||||
import Icon from './Icon';
|
||||
|
||||
class StatusIndicator extends Component {
|
||||
|
||||
render() {
|
||||
const { status, progress } = this.props;
|
||||
|
||||
switch (status) {
|
||||
case RunStatus.RUNNING:
|
||||
if (progress > 0) {
|
||||
return (
|
||||
<View style={{ width: 30, flex: 1, justifyContent: 'flex-end' }}>
|
||||
<Text style={{ fontSize: 11, marginBottom: 20 }}>
|
||||
{progress.toFixed(0)}%
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Icon color={'rgba(0, 0, 0, 0.2)'} name="autorenew" />
|
||||
);
|
||||
case RunStatus.OK:
|
||||
return (
|
||||
<Icon name={'done'} />
|
||||
);
|
||||
case RunStatus.ERR:
|
||||
return (
|
||||
<Icon color={'#f44336'} name="clear" />
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StatusIndicator.propTypes = {
|
||||
status: PropTypes.oneOf(Object.values(RunStatus)),
|
||||
progress: PropTypes.number,
|
||||
};
|
||||
|
||||
StatusIndicator.defaultProps = {
|
||||
status: null,
|
||||
progress: 0
|
||||
};
|
||||
|
||||
module.exports = StatusIndicator;
|
||||
71
tests/src/components/TestControlButton.js
Normal file
71
tests/src/components/TestControlButton.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Toast from 'react-native-simple-toast';
|
||||
|
||||
import RunStatus from '../../lib/RunStatus';
|
||||
import { runTest } from '../tests/index';
|
||||
|
||||
import Icon from './Icon';
|
||||
|
||||
class TestControlButton extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.handleOnPress = this.handleOnPress.bind(this);
|
||||
}
|
||||
|
||||
testIsPending() {
|
||||
const { test: { id }, pendingTestIds } = this.props;
|
||||
return !!pendingTestIds[id];
|
||||
}
|
||||
|
||||
handleOnPress() {
|
||||
const { test: { id, description } } = this.props;
|
||||
|
||||
runTest(id);
|
||||
Toast.show(`Running ${description}.`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { test: { status } } = this.props;
|
||||
|
||||
if (status !== RunStatus.STARTED && !this.testIsPending()) {
|
||||
return (
|
||||
<Icon
|
||||
color={'#ffffff'}
|
||||
size={28}
|
||||
name="play circle filled"
|
||||
onPress={this.handleOnPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TestControlButton.propTypes = {
|
||||
test: PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
status: PropTypes.string,
|
||||
description: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
|
||||
pendingTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
};
|
||||
|
||||
TestControlButton.defaultProps = {
|
||||
|
||||
};
|
||||
|
||||
function mapStateToProps({ tests, pendingTestIds }, { testId }) {
|
||||
const test = tests[testId];
|
||||
|
||||
return {
|
||||
test,
|
||||
pendingTestIds,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps)(TestControlButton);
|
||||
96
tests/src/components/TestSuiteControlButton.js
Normal file
96
tests/src/components/TestSuiteControlButton.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Toast from 'react-native-simple-toast';
|
||||
|
||||
import RunStatus from '../../lib/RunStatus';
|
||||
import { runTests } from '../tests/index';
|
||||
|
||||
import Icon from './Icon';
|
||||
|
||||
class TestSuiteControlButton extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.toggleOnlyShowFailingTests = this.toggleOnlyShowFailingTests.bind(this);
|
||||
this.startTestSuite = this.startTestSuite.bind(this);
|
||||
}
|
||||
|
||||
startTestSuite() {
|
||||
const { testSuite: { name, testIds }, tests, focusedTestIds, pendingTestIds } = this.props;
|
||||
|
||||
const testSuiteTests = testIds.reduce((memo, testId) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
memo[testId] = tests[testId];
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
runTests(testSuiteTests, { focusedTestIds, pendingTestIds });
|
||||
|
||||
Toast.show(`Running ${name} tests.`);
|
||||
}
|
||||
|
||||
toggleOnlyShowFailingTests() {
|
||||
const { onlyShowFailingTests, onFilterChange } = this.props;
|
||||
onFilterChange({ onlyShowFailingTests: !onlyShowFailingTests });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { testSuite: { status }, onlyShowFailingTests } = this.props;
|
||||
|
||||
if (status === RunStatus.ERR) {
|
||||
return (
|
||||
<Icon
|
||||
color={onlyShowFailingTests ? '#ffffff' : 'rgba(255, 255, 255, 0.54)'}
|
||||
size={28}
|
||||
name="error outline"
|
||||
onPress={this.toggleOnlyShowFailingTests}
|
||||
/>
|
||||
);
|
||||
} else if (status !== RunStatus.RUNNING) {
|
||||
return (
|
||||
<Icon
|
||||
color={'#ffffff'}
|
||||
size={28}
|
||||
name="play circle filled"
|
||||
onPress={this.startTestSuite}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TestSuiteControlButton.propTypes = {
|
||||
testSuite: PropTypes.shape({
|
||||
status: PropTypes.oneOf(Object.values(RunStatus)),
|
||||
}).isRequired,
|
||||
|
||||
tests: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
focusedTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
pendingTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
|
||||
onlyShowFailingTests: PropTypes.bool,
|
||||
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
TestSuiteControlButton.defaultProps = {
|
||||
onlyShowFailingTests: false,
|
||||
};
|
||||
|
||||
|
||||
function mapStateToProps({ tests, testSuites, focusedTestIds, pendingTestIds }, { testSuiteId }) {
|
||||
const testSuite = testSuites[testSuiteId];
|
||||
|
||||
return {
|
||||
tests,
|
||||
testSuite,
|
||||
focusedTestIds,
|
||||
pendingTestIds,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps)(TestSuiteControlButton);
|
||||
82
tests/src/containers/CoreContainer.js
Normal file
82
tests/src/containers/CoreContainer.js
Normal file
@@ -0,0 +1,82 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { View, Text, AppState, NetInfo, StatusBar, Platform } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Navigator from '../navigator';
|
||||
import { setNetworkState, setAppState } from '../actions/AppActions';
|
||||
|
||||
type Props = {
|
||||
dispatch: () => void,
|
||||
};
|
||||
|
||||
class CoreContainer extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._isConnected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* On app mount, listen for changes to app & network state
|
||||
*/
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
StatusBar.setBackgroundColor('#0279ba');
|
||||
}
|
||||
if (Platform.OS === 'ios') {
|
||||
StatusBar.setBarStyle('light-content')
|
||||
}
|
||||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
NetInfo.isConnected.fetch().then((isConnected) => {
|
||||
this.handleAppStateChange('active'); // Force connect (react debugger issue)
|
||||
this.props.dispatch(setNetworkState(isConnected));
|
||||
NetInfo.isConnected.addEventListener('change', this.handleNetworkChange);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners on app unmount
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
AppState.removeEventListener('change', this.handleAppStateChange);
|
||||
NetInfo.isConnected.removeEventListener('change', this.handleNetworkChange);
|
||||
}
|
||||
|
||||
props: Props;
|
||||
|
||||
/**
|
||||
* Handle app state changes
|
||||
* https://facebook.github.io/react-native/docs/appstate.html
|
||||
* @param state
|
||||
*/
|
||||
handleAppStateChange = (state) => {
|
||||
this.props.dispatch(setAppState(state));
|
||||
if (state === 'active' && this._isConnected) {
|
||||
// firestack.database().goOnline();
|
||||
} else if (state === 'background') {
|
||||
// firestack.database().goOffline();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle app network changes
|
||||
* https://facebook.github.io/react-native/docs/netinfo.html
|
||||
* @param isConnected
|
||||
*/
|
||||
handleNetworkChange = (isConnected) => {
|
||||
this._isConnected = isConnected;
|
||||
this.props.dispatch(setNetworkState(isConnected));
|
||||
if (isConnected) {
|
||||
// firestack.database().goOnline();
|
||||
} else {
|
||||
// firestack.database().goOffline();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <Navigator />;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(CoreContainer);
|
||||
32
tests/src/firebase.js
Normal file
32
tests/src/firebase.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import firebase from 'firebase';
|
||||
import RNfirebase from 'react-native-firebase';
|
||||
|
||||
import DatabaseContents from './tests/support/DatabaseContents';
|
||||
|
||||
const config = {
|
||||
apiKey: 'AIzaSyDnVqNhxU0Biit9nCo4RorAh5ulQQwko3E',
|
||||
authDomain: 'rnfirebase-b9ad4.firebaseapp.com',
|
||||
databaseURL: 'https://rnfirebase-b9ad4.firebaseio.com',
|
||||
storageBucket: 'rnfirebase-b9ad4.appspot.com',
|
||||
messagingSenderId: '305229645282',
|
||||
};
|
||||
|
||||
const instances = {
|
||||
web: firebase.initializeApp(config),
|
||||
native: RNfirebase.initializeApp({
|
||||
debug: __DEV__ ? '*' : false,
|
||||
errorOnMissingPlayServices: false,
|
||||
persistence: true,
|
||||
}),
|
||||
};
|
||||
|
||||
instances.web.database().ref('tests/types').set(DatabaseContents.DEFAULT);
|
||||
|
||||
instances.web.database().ref('tests/priority').setWithPriority({
|
||||
foo: 'bar',
|
||||
}, 666);
|
||||
|
||||
|
||||
// instances.native.messaging().subscribeToTopic('fcm_test');
|
||||
|
||||
export default instances;
|
||||
3
tests/src/helpers.js
Normal file
3
tests/src/helpers.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
64
tests/src/main.js
Normal file
64
tests/src/main.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import CoreContainer from './containers/CoreContainer';
|
||||
import setupStore from './store/setup';
|
||||
import { setupSuites } from './tests/index';
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
|
||||
type State = {
|
||||
loading: boolean,
|
||||
store: any,
|
||||
};
|
||||
|
||||
function bootstrap() {
|
||||
// Remove logging on production
|
||||
if (!__DEV__) {
|
||||
console.log = () => {
|
||||
};
|
||||
console.warn = () => {
|
||||
};
|
||||
console.error = () => {
|
||||
};
|
||||
console.disableYellowBox = true;
|
||||
}
|
||||
|
||||
class Root extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: true,
|
||||
store: null,
|
||||
};
|
||||
}
|
||||
|
||||
state: State;
|
||||
|
||||
componentDidMount() {
|
||||
setupStore((store) => {
|
||||
setupSuites(store);
|
||||
this.setState({
|
||||
store,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider store={this.state.store}>
|
||||
<CoreContainer />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Root;
|
||||
}
|
||||
|
||||
export default bootstrap();
|
||||
20
tests/src/navigator.js
Normal file
20
tests/src/navigator.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { StackNavigator } from 'react-navigation';
|
||||
|
||||
import Overview from './screens/Overview';
|
||||
import Suite from './screens/Suite';
|
||||
import Test from './screens/Test';
|
||||
|
||||
export default StackNavigator({
|
||||
Overview: { screen: Overview },
|
||||
Suite: { screen: Suite },
|
||||
Test: { screen: Test },
|
||||
});
|
||||
|
||||
export const initialNavState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
key: 'Overview',
|
||||
},
|
||||
],
|
||||
};
|
||||
35
tests/src/reducers/device.js
Normal file
35
tests/src/reducers/device.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as fcmTypes from '../actions/FCMActions';
|
||||
import * as appTypes from '../actions/AppActions';
|
||||
|
||||
type State = {
|
||||
appState: 'string',
|
||||
isConnected: boolean,
|
||||
fcmToken: string,
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
appState: 'active',
|
||||
isConnected: true,
|
||||
fcmToken: '',
|
||||
};
|
||||
|
||||
function device(state: State = initialState, action: Object): State {
|
||||
|
||||
if (action.type === appTypes.APP_SET_NETWORK_STATE) {
|
||||
return {
|
||||
...state,
|
||||
isConnected: action.isConnected,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === appTypes.APP_SET_APP_STATE) {
|
||||
return {
|
||||
...state,
|
||||
appState: action.appState,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export default device;
|
||||
9
tests/src/reducers/focusedTestIdsReducers.js
Normal file
9
tests/src/reducers/focusedTestIdsReducers.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { initialState } from '../tests/index';
|
||||
|
||||
const initState = initialState();
|
||||
|
||||
function focusedTestIdsReducers(state = initState.focusedTestIds): State {
|
||||
return state;
|
||||
}
|
||||
|
||||
export default focusedTestIdsReducers;
|
||||
17
tests/src/reducers/index.js
Normal file
17
tests/src/reducers/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import device from './device';
|
||||
import tests from './testsReducers';
|
||||
import testContexts from './testContextsReducers';
|
||||
import testSuites from './testSuitesReducers';
|
||||
import pendingTestIds from './pendingTestIdsReducers';
|
||||
import focusedTestIds from './focusedTestIdsReducers';
|
||||
|
||||
export default combineReducers({
|
||||
device,
|
||||
pendingTestIds,
|
||||
focusedTestIds,
|
||||
testContexts,
|
||||
tests,
|
||||
testSuites,
|
||||
});
|
||||
9
tests/src/reducers/pendingTestIdsReducers.js
Normal file
9
tests/src/reducers/pendingTestIdsReducers.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { initialState } from '../tests';
|
||||
|
||||
const initState = initialState();
|
||||
|
||||
function pendingTestIdsReducers(state = initState.pendingTestIds): State {
|
||||
return state;
|
||||
}
|
||||
|
||||
export default pendingTestIdsReducers;
|
||||
9
tests/src/reducers/testContextsReducers.js
Normal file
9
tests/src/reducers/testContextsReducers.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { initialState } from '../tests/index';
|
||||
|
||||
const initState = initialState();
|
||||
|
||||
function testsReducers(state = initState.testContexts): State {
|
||||
return state;
|
||||
}
|
||||
|
||||
export default testsReducers;
|
||||
23
tests/src/reducers/testSuitesReducers.js
Normal file
23
tests/src/reducers/testSuitesReducers.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as testActions from '../actions/TestActions';
|
||||
import { flatten, unflatten } from 'deeps';
|
||||
import { initialState } from '../tests/index';
|
||||
|
||||
const initState = initialState();
|
||||
|
||||
function testsReducers(state = initState.testSuites, action: Object): State {
|
||||
|
||||
if (action.type === testActions.TEST_SET_SUITE_STATUS) {
|
||||
const flattened = flatten(state);
|
||||
|
||||
if (action.status) flattened[`${action.suiteId}.status`] = action.status;
|
||||
if (action.message) flattened[`${action.suiteId}.message`] = action.message;
|
||||
if (action.progress) flattened[`${action.suiteId}.progress`] = action.progress;
|
||||
if (!isNaN(action.time)) flattened[`${action.suiteId}.time`] = action.time;
|
||||
|
||||
return unflatten(flattened);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export default testsReducers;
|
||||
22
tests/src/reducers/testsReducers.js
Normal file
22
tests/src/reducers/testsReducers.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as testActions from '../actions/TestActions';
|
||||
import { flatten, unflatten } from 'deeps';
|
||||
import { initialState } from '../tests/index';
|
||||
|
||||
const initState = initialState();
|
||||
|
||||
function testsReducers(state = initState.tests, action: Object): State {
|
||||
|
||||
if (action.type === testActions.TEST_SET_STATUS) {
|
||||
const flattened = flatten(state);
|
||||
|
||||
flattened[`${action.testId}.status`] = action.status;
|
||||
flattened[`${action.testId}.message`] = action.message;
|
||||
flattened[`${action.testId}.time`] = action.time;
|
||||
|
||||
return unflatten(flattened);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export default testsReducers;
|
||||
299
tests/src/screens/Overview.js
Normal file
299
tests/src/screens/Overview.js
Normal file
@@ -0,0 +1,299 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { StyleSheet, View, Text, ListView, TouchableHighlight } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import some from 'lodash.some';
|
||||
|
||||
import RunStatus from '../../lib/RunStatus';
|
||||
|
||||
import Banner from '../components/Banner';
|
||||
import StatusIndicator from '../components/StatusIndicator';
|
||||
import OverviewControlButton from '../components/OverviewControlButton';
|
||||
|
||||
class Overview extends React.Component {
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
static navigationOptions = {
|
||||
title: 'Test Suites',
|
||||
header: () => {
|
||||
return {
|
||||
style: { backgroundColor: '#0288d1' },
|
||||
tintColor: '#ffffff',
|
||||
right: (
|
||||
<View style={{ marginRight: 8 }}>
|
||||
<OverviewControlButton />
|
||||
</View>
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders separator between ListView sections
|
||||
* @param {String} sectionID
|
||||
* @param {String} rowID
|
||||
* @returns {XML} JSX component used as ListView separator
|
||||
*/
|
||||
static renderSeparator(sectionID, rowID) {
|
||||
return (
|
||||
<View
|
||||
key={`separator_${sectionID}_${rowID}`}
|
||||
style={styles.separator}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters test suites to those that have one or more tests that should be visible.
|
||||
* If one or more tests are focused it only returns test suites with focused tests,
|
||||
* otherwise, it returns all test suites.
|
||||
* @param {IndexedTestSuiteGroup} testSuites - group of available test suites
|
||||
* @param {IdLookup} focusedTestIds - lookup for focused tests
|
||||
* @returns {IndexedTestSuiteGroup} - indexed group of test suites that should be shown
|
||||
*/
|
||||
static testSuitesToShow({ testSuites, focusedTestIds }) {
|
||||
if (Object.keys(focusedTestIds).length > 0) {
|
||||
return Object.keys(testSuites).reduce((memo, testSuiteId) => {
|
||||
const testSuite = testSuites[testSuiteId];
|
||||
|
||||
const testSuiteHasFocusedTests = some(testSuite.testIds, (testId) => {
|
||||
return focusedTestIds[testId];
|
||||
});
|
||||
|
||||
if (testSuiteHasFocusedTests) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
memo[testSuiteId] = testSuite;
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return testSuites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies initial values for test suites from props into state, so they may be
|
||||
* rendered as a ListView
|
||||
* @param {Object} props - props used to render component
|
||||
* @param {Object} context - context used to render component
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.dataSource = new ListView.DataSource({
|
||||
rowHasChanged: (r1, r2) => JSON.stringify(r1) !== JSON.stringify(r2),
|
||||
});
|
||||
|
||||
this.state = {
|
||||
dataBlob: this.dataSource.cloneWithRows(Overview.testSuitesToShow(props)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies latest test suite status into state so they may be rendered as a ListView
|
||||
* @param {Object} nextProps - next props used to render component
|
||||
* @param {Object.<number,TestSuite>} nextProps.testSuites - test suites to render
|
||||
* @param {IdLookup} nextProps.focusedTestIds - lookup for focus tests
|
||||
*/
|
||||
componentWillReceiveProps({ testSuites, focusedTestIds }) {
|
||||
this.setState({
|
||||
dataBlob: this.dataSource.cloneWithRows(Overview.testSuitesToShow({ testSuites, focusedTestIds })),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to test suite screen
|
||||
* @param {TestSuiteId} testSuiteId - id of test suite to navigate to
|
||||
*/
|
||||
goToTestSuite(testSuite) {
|
||||
const { navigation: { navigate } } = this.props;
|
||||
|
||||
navigate('Suite', { testSuiteId: testSuite.id, title: testSuite.name });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param testSuite
|
||||
* @param sectionId
|
||||
* @param rowId
|
||||
* @param highlight
|
||||
* @returns {XML}
|
||||
*/
|
||||
renderRow(testSuite, sectionId, rowId, highlight) {
|
||||
const { description, name, status, progress } = testSuite;
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
key={`row_${rowId}`}
|
||||
underlayColor={'rgba(0, 0, 0, 0.054)'}
|
||||
onPress={() => {
|
||||
this.goToTestSuite(testSuite);
|
||||
highlight();
|
||||
}}
|
||||
>
|
||||
<View style={[styles.row, status === RunStatus.ERR ? styles.error : null]}>
|
||||
<View>
|
||||
<Text style={styles.title}>{name}</Text>
|
||||
<Text
|
||||
style={styles.description}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.statusContainer}>
|
||||
<StatusIndicator status={status} progress={progress} />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a warning toast banner if there are one or more tests that are pending
|
||||
* @returns {null|XML} Toast banner if there are test pending, else null
|
||||
*/
|
||||
renderPendingTestsBanner() {
|
||||
const { pendingTestIds } = this.props;
|
||||
|
||||
const pendingTestsCount = Object.keys(pendingTestIds).length;
|
||||
|
||||
if (pendingTestsCount > 0) {
|
||||
return (
|
||||
<Banner type="warning">
|
||||
{pendingTestsCount} pending test(s).
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderStatusBanner() {
|
||||
const { testSuites } = this.props;
|
||||
|
||||
let totalProgress = 0;
|
||||
let isRunning = false;
|
||||
let isErrors = false;
|
||||
let totalTime = 0;
|
||||
|
||||
Object.values(testSuites).forEach(({ progress, status, time }) => {
|
||||
totalProgress += progress;
|
||||
totalTime += time;
|
||||
|
||||
if (status === RunStatus.RUNNING) {
|
||||
isRunning = true;
|
||||
} else if (status === RunStatus.ERR) {
|
||||
isErrors = true;
|
||||
}
|
||||
});
|
||||
|
||||
totalProgress /= Object.keys(testSuites).length;
|
||||
|
||||
if (isRunning) {
|
||||
return (
|
||||
<Banner type={isErrors ? 'error' : 'warning'}>Running ({(totalTime / 1000).toFixed(0)}s) {totalProgress.toFixed(2)}%</Banner>
|
||||
);
|
||||
} else if (totalProgress > 0) {
|
||||
if (isErrors) {
|
||||
return (
|
||||
<Banner type={'error'}>Tests Complete with errors</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Banner type={'success'}>Tests Complete</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders ListView of test suites that should be visible, taking into consideration
|
||||
* any focused tests
|
||||
* @returns {XML} ListView of test suites
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{ this.renderPendingTestsBanner() }
|
||||
{ this.renderStatusBanner() }
|
||||
<ListView
|
||||
enableEmptySections
|
||||
dataSource={this.state.dataBlob}
|
||||
renderRow={(...args) => this.renderRow(...args)}
|
||||
renderSeparator={(...args) => Overview.renderSeparator(...args)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Overview.propTypes = {
|
||||
testSuites: PropTypes.objectOf(PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
status: PropTypes.oneOf(Object.values(RunStatus)),
|
||||
})).isRequired,
|
||||
|
||||
tests: PropTypes.objectOf(PropTypes.shape({
|
||||
testSuiteId: PropTypes.number.isRequired,
|
||||
})).isRequired,
|
||||
|
||||
navigation: PropTypes.shape({
|
||||
navigate: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
|
||||
running: PropTypes.bool.isRequired,
|
||||
|
||||
pendingTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
focusedTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
};
|
||||
const styles = StyleSheet.create({
|
||||
rightContainer: {
|
||||
marginRight: 16,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
title: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
},
|
||||
description: {
|
||||
fontSize: 11,
|
||||
},
|
||||
statusContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
row: {
|
||||
height: 56,
|
||||
paddingHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
error: {
|
||||
backgroundColor: 'rgba(255, 0, 0, 0.054)',
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
});
|
||||
|
||||
function mapStateToProps({ testSuites, tests, pendingTestIds, focusedTestIds }) {
|
||||
return {
|
||||
testSuites,
|
||||
tests,
|
||||
pendingTestIds,
|
||||
focusedTestIds,
|
||||
running: Object.values(testSuites).filter(suite => suite.status === RunStatus.RUNNING).length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Overview);
|
||||
404
tests/src/screens/Suite.js
Normal file
404
tests/src/screens/Suite.js
Normal file
@@ -0,0 +1,404 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { StyleSheet, View, Text, ListView, TouchableHighlight } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import RunStatus from '../../lib/RunStatus';
|
||||
|
||||
import Banner from '../components/Banner';
|
||||
|
||||
import StatusIndicator from '../components/StatusIndicator';
|
||||
import TestSuiteControlButton from '../components/TestSuiteControlButton';
|
||||
|
||||
class Suite extends React.Component {
|
||||
|
||||
static navigationOptions = {
|
||||
title: ({ state: { params: { title } } }) => {
|
||||
return title;
|
||||
},
|
||||
header: ({ state: { params: { testSuiteId, onlyShowFailingTests } }, setParams }) => {
|
||||
return {
|
||||
style: { backgroundColor: '#0288d1' },
|
||||
tintColor: '#ffffff',
|
||||
right: (
|
||||
<View style={{ flexDirection: 'row', marginRight: 8 }}>
|
||||
<TestSuiteControlButton
|
||||
testSuiteId={testSuiteId}
|
||||
onlyShowFailingTests={onlyShowFailingTests}
|
||||
onFilterChange={setParams}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Render test group header
|
||||
* @param data
|
||||
* @param title
|
||||
* @returns {XML}
|
||||
*/
|
||||
static renderHeader(data, title) {
|
||||
return (
|
||||
<View
|
||||
key={`header_${title}`}
|
||||
style={styles.header}
|
||||
>
|
||||
<Text style={styles.headerText}>
|
||||
{title.toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.dataSource = new ListView.DataSource({
|
||||
rowHasChanged: (r1, r2) => JSON.stringify(r1) !== JSON.stringify(r2),
|
||||
sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
|
||||
});
|
||||
|
||||
this.state = {
|
||||
dataBlob: this.dataSource.cloneWithRowsAndSections(buildRowsWithSections(props)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* componentWillReceiveProps
|
||||
* @param nextProps
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {
|
||||
tests,
|
||||
testContexts,
|
||||
navigation: { state: { params: { onlyShowFailingTests } } },
|
||||
} = nextProps;
|
||||
|
||||
const newRowsWithSections = (() => {
|
||||
if (onlyShowFailingTests) {
|
||||
return Object.values(testContexts).reduce((sections, context) => {
|
||||
const { name } = context;
|
||||
|
||||
context.testIds.forEach((testId) => {
|
||||
const test = tests[testId];
|
||||
|
||||
if (test && test.status === RunStatus.ERR) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sections[name] = sections[name] || [];
|
||||
sections[name].push(test);
|
||||
}
|
||||
});
|
||||
|
||||
return sections;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return buildRowsWithSections(nextProps);
|
||||
})();
|
||||
|
||||
this.setState({
|
||||
dataBlob: this.dataSource.cloneWithRowsAndSections(newRowsWithSections),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to a single test
|
||||
* @param testId
|
||||
*/
|
||||
goToTest(test) {
|
||||
const { navigation: { navigate } } = this.props;
|
||||
|
||||
navigate('Test', { testId: test.id, title: test.description });
|
||||
}
|
||||
|
||||
/**
|
||||
* Render test row
|
||||
* @param test
|
||||
* @param sectionId
|
||||
* @param rowId
|
||||
* @param highlight
|
||||
* @returns {XML}
|
||||
*/
|
||||
renderRow(test, sectionId, rowId, highlight) {
|
||||
const { pendingTestIds } = this.props;
|
||||
const { status, description, id } = test;
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
key={`row_${rowId}`}
|
||||
underlayColor={'rgba(0, 0, 0, 0.054)'}
|
||||
onPress={() => {
|
||||
this.goToTest(test);
|
||||
highlight();
|
||||
}}
|
||||
>
|
||||
<View style={[styles.row, status === RunStatus.ERR ? styles.error : null]}>
|
||||
<View
|
||||
style={[{ flex: 9 }, styles.rowContent]}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={pendingTestIds[id] ? styles.disabledRow : {}}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[{ flex: 1 }, styles.rowContent, [{ alignItems: 'center' }]]}>
|
||||
<StatusIndicator status={status} />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sectionID
|
||||
* @param rowID
|
||||
* @returns {XML}
|
||||
*/
|
||||
renderSeparator(sectionID, rowID) {
|
||||
return (
|
||||
<View
|
||||
key={`separator_${sectionID}_${rowID}`}
|
||||
style={styles.separator}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPendingTestsBanner() {
|
||||
const { testSuite: { testIds }, pendingTestIds } = this.props;
|
||||
|
||||
let pendingTestsCount = 0;
|
||||
|
||||
testIds.forEach((testId) => {
|
||||
if (pendingTestIds[testId]) {
|
||||
pendingTestsCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (pendingTestsCount) {
|
||||
return (
|
||||
<Banner type="warning">
|
||||
{pendingTestsCount} pending test(s).
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderStatusBanner() {
|
||||
const { testSuite: { status, progress, time, message } } = this.props;
|
||||
|
||||
switch (status) {
|
||||
case RunStatus.RUNNING:
|
||||
|
||||
return (
|
||||
<Banner type={'warning'}>
|
||||
Tests are currently running ({ progress.toFixed(2) }%).
|
||||
</Banner>
|
||||
);
|
||||
|
||||
case RunStatus.OK:
|
||||
|
||||
return (
|
||||
<Banner type={'success'}>
|
||||
Tests passed. ({ time }ms)
|
||||
</Banner>
|
||||
);
|
||||
|
||||
case RunStatus.ERR:
|
||||
|
||||
return (
|
||||
<Banner type={'error'}>
|
||||
{message} ({time}ms)
|
||||
</Banner>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {XML}
|
||||
*/
|
||||
render() {
|
||||
const { dataBlob } = this.state;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
||||
{ this.renderPendingTestsBanner() }
|
||||
{ this.renderStatusBanner() }
|
||||
|
||||
<ListView
|
||||
dataSource={dataBlob}
|
||||
renderSectionHeader={(...args) => Suite.renderHeader(...args)}
|
||||
renderRow={(...args) => this.renderRow(...args)}
|
||||
renderSeparator={(...args) => this.renderSeparator(...args)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Suite.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
setParams: PropTypes.func.isRequired,
|
||||
navigate: PropTypes.func.isRequired,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.object,
|
||||
onlyShowFailingTests: PropTypes.bool,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
|
||||
testSuite: PropTypes.shape({
|
||||
status: PropTypes.string,
|
||||
progress: PropTypes.number,
|
||||
time: PropTypes.number,
|
||||
message: PropTypes.string,
|
||||
}).isRequired,
|
||||
|
||||
testContexts: PropTypes.objectOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
testIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
})).isRequired,
|
||||
|
||||
tests: PropTypes.objectOf(PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
description: PropTypes.string,
|
||||
status: PropTypes.oneOf(Object.values(RunStatus)),
|
||||
})).isRequired,
|
||||
|
||||
pendingTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
focusedTestIds: PropTypes.objectOf(PropTypes.bool).isRequired,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
banner: {
|
||||
alignItems: 'center',
|
||||
elevation: 3,
|
||||
},
|
||||
bannerText: {
|
||||
color: '#ffffff',
|
||||
},
|
||||
inProgress: {
|
||||
backgroundColor: '#FFC107',
|
||||
},
|
||||
errorBanner: {
|
||||
backgroundColor: '#f44336',
|
||||
},
|
||||
header: {
|
||||
elevation: 3,
|
||||
justifyContent: 'center',
|
||||
height: 25,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: '#ECEFF1',
|
||||
},
|
||||
headerText: {
|
||||
fontWeight: '800',
|
||||
},
|
||||
row: {
|
||||
paddingHorizontal: 16,
|
||||
height: 48,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
rowContent: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
disabledRow: {
|
||||
color: '#c3c3c3',
|
||||
},
|
||||
error: {
|
||||
backgroundColor: 'rgba(255, 0, 0, 0.054)',
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
});
|
||||
|
||||
function buildRowsWithSections({ testContexts, tests, focusedTestIds }) {
|
||||
const someTestsAreFocused = Object.keys(focusedTestIds).length > 0;
|
||||
|
||||
return Object.values(testContexts).reduce((sections, testContext) => {
|
||||
const { testIds } = testContext;
|
||||
|
||||
const contextTestsToShow = testIds.reduce((memo, testId) => {
|
||||
const test = tests[testId];
|
||||
|
||||
if (someTestsAreFocused) {
|
||||
if (focusedTestIds[testId]) {
|
||||
memo.push(test);
|
||||
}
|
||||
} else {
|
||||
memo.push(test);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
if (contextTestsToShow.length > 0) {
|
||||
const effectiveContext = highestNonRootAncestor(testContext, testContexts);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sections[effectiveContext.name] = sections[effectiveContext.name] || [];
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sections[effectiveContext.name] = sections[effectiveContext.name].concat(contextTestsToShow);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function highestNonRootAncestor(testContext, testContexts) {
|
||||
const parentContextId = testContext.parentContextId;
|
||||
|
||||
if (parentContextId) {
|
||||
const parentContext = testContexts[parentContextId];
|
||||
const parentContextIsNotRoot = parentContext && parentContext.parentContextId;
|
||||
|
||||
if (parentContextIsNotRoot) {
|
||||
return highestNonRootAncestor(parentContext, testContexts);
|
||||
}
|
||||
}
|
||||
|
||||
return testContext;
|
||||
}
|
||||
|
||||
function mapStateToProps(state, { navigation: { state: { params: { testSuiteId } } } }) {
|
||||
const { tests, testContexts, testSuites, pendingTestIds, focusedTestIds } = state;
|
||||
const testSuite = testSuites[testSuiteId];
|
||||
|
||||
const testSuiteContexts = testSuite.testContextIds.reduce((suiteContexts, contextId) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
suiteContexts[contextId] = testContexts[contextId];
|
||||
|
||||
return suiteContexts;
|
||||
}, {});
|
||||
|
||||
const testSuiteTests = testSuite.testIds.reduce((suiteTests, testId) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
suiteTests[testId] = tests[testId];
|
||||
|
||||
return suiteTests;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
testSuite,
|
||||
testContexts: testSuiteContexts,
|
||||
tests: testSuiteTests,
|
||||
pendingTestIds,
|
||||
focusedTestIds,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Suite);
|
||||
149
tests/src/screens/Test.js
Normal file
149
tests/src/screens/Test.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { StyleSheet, View, Text, ScrollView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { js_beautify as beautify } from 'js-beautify';
|
||||
|
||||
import Banner from '../components/Banner';
|
||||
import RunStatus from '../../lib/RunStatus';
|
||||
import TestControlButton from '../components/TestControlButton';
|
||||
|
||||
class Test extends React.Component {
|
||||
|
||||
static navigationOptions = {
|
||||
title: ({ state: { params: { title } } }) => {
|
||||
return title;
|
||||
},
|
||||
header: ({ state: { params: { testId } } }) => {
|
||||
return {
|
||||
style: { backgroundColor: '#0288d1' },
|
||||
tintColor: '#ffffff',
|
||||
right: (
|
||||
<View style={{ marginRight: 8 }}>
|
||||
<TestControlButton testId={testId} />
|
||||
</View>
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
static renderBanner({ status, time }) {
|
||||
switch (status) {
|
||||
case RunStatus.RUNNING:
|
||||
return (
|
||||
<Banner type={'warning'}>
|
||||
Test is currently running.
|
||||
</Banner>
|
||||
);
|
||||
case RunStatus.OK:
|
||||
return (
|
||||
<Banner type={'success'}>
|
||||
Test passed. ({time}ms)
|
||||
</Banner>
|
||||
);
|
||||
case RunStatus.ERR:
|
||||
return (
|
||||
<Banner type={'error'}>
|
||||
Test failed. ({time}ms)
|
||||
</Banner>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation: { setParams }, test } = this.props;
|
||||
|
||||
setParams({ test });
|
||||
}
|
||||
|
||||
renderError() {
|
||||
const { test: { message } } = this.props;
|
||||
|
||||
if (message) {
|
||||
return (
|
||||
<ScrollView>
|
||||
<Text style={styles.codeHeader}>Test Error</Text>
|
||||
<Text style={styles.code}>
|
||||
<Text>{message}</Text>
|
||||
</Text>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { test: { func, status, time } } = this.props;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{Test.renderBanner({ status, time })}
|
||||
<View style={styles.content}>
|
||||
{this.renderError()}
|
||||
<Text style={styles.codeHeader}>Test Code Preview</Text>
|
||||
<ScrollView>
|
||||
<Text style={styles.code}>
|
||||
{beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Test.propTypes = {
|
||||
test: PropTypes.shape({
|
||||
status: PropTypes.string,
|
||||
time: PropTypes.number,
|
||||
message: PropTypes.string,
|
||||
func: PropTypes.function,
|
||||
}).isRequired,
|
||||
|
||||
navigation: PropTypes.shape({
|
||||
setParams: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
content: {},
|
||||
code: {
|
||||
backgroundColor: '#3F373A',
|
||||
color: '#c3c3c3',
|
||||
padding: 5,
|
||||
fontSize: 12,
|
||||
},
|
||||
codeHeader: {
|
||||
fontWeight: '600',
|
||||
fontSize: 18,
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
padding: 5,
|
||||
},
|
||||
});
|
||||
|
||||
function select({ tests }, { navigation: { state: { params: { testId } } } }) {
|
||||
const test = tests[testId];
|
||||
|
||||
return {
|
||||
test,
|
||||
};
|
||||
}
|
||||
|
||||
function removeLastLine(multiLineString) {
|
||||
const index = multiLineString.lastIndexOf('\n');
|
||||
return multiLineString.substring(0, index);
|
||||
}
|
||||
|
||||
function removeFirstLine(multiLineString) {
|
||||
return multiLineString.substring(multiLineString.indexOf('\n') + 1);
|
||||
}
|
||||
|
||||
export default connect(select)(Test);
|
||||
43
tests/src/store/setup.js
Normal file
43
tests/src/store/setup.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { AsyncStorage } from 'react-native';
|
||||
import { applyMiddleware, createStore, compose } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import reduxLogger from 'redux-logger';
|
||||
import { persistStore, autoRehydrate } from 'redux-persist';
|
||||
|
||||
import whitelist from './whitelist';
|
||||
import reducers from '../reducers';
|
||||
|
||||
function setup(done) {
|
||||
const isDev = global.isDebuggingInChrome || __DEV__;
|
||||
|
||||
const logger = reduxLogger({
|
||||
predicate: () => isDev,
|
||||
collapsed: true,
|
||||
duration: true,
|
||||
});
|
||||
|
||||
// AsyncStorage.clear();
|
||||
|
||||
// Setup redux middleware
|
||||
const middlewares = [autoRehydrate()];
|
||||
|
||||
middlewares.push(applyMiddleware(...[thunk]));
|
||||
|
||||
if (isDev) {
|
||||
middlewares.push(applyMiddleware(...[logger]));
|
||||
middlewares.push(applyMiddleware(require('redux-immutable-state-invariant')()));
|
||||
}
|
||||
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
const store = createStore(reducers, {}, composeEnhancers(...middlewares));
|
||||
|
||||
// Attach the store to the Chrome debug window
|
||||
if (global.isDebuggingInChrome) {
|
||||
window.store = store;
|
||||
}
|
||||
|
||||
persistStore(store, { whitelist, storage: AsyncStorage }, () => done(store));
|
||||
}
|
||||
|
||||
export default setup;
|
||||
1
tests/src/store/whitelist.js
Normal file
1
tests/src/store/whitelist.js
Normal file
@@ -0,0 +1 @@
|
||||
export default [''];
|
||||
64
tests/src/tests/analytics/analytics.js
Normal file
64
tests/src/tests/analytics/analytics.js
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
export default function addTests({ describe, it, firebase }) {
|
||||
describe('Analytics', () => {
|
||||
it('logEvent: it should log a text event without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().logEvent('test_event');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('logEvent: it should log a text event with parameters without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().logEvent('test_event', {
|
||||
boolean: true,
|
||||
number: 1,
|
||||
string: 'string',
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('setAnalyticsCollectionEnabled: it should run without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().setAnalyticsCollectionEnabled(true);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('setCurrentScreen: it should run without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().setCurrentScreen('test screen', 'test class override');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('setMinimumSessionDuration: it should run without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().setMinimumSessionDuration(10000);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('setSessionTimeoutDuration: it should run without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().setSessionTimeoutDuration(1800000);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('setUserId: it should run without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().setUserId('test-id');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('setUserProperty: it should run without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.analytics().setUserProperty('test-property', 'test-value');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
9
tests/src/tests/analytics/index.js
Normal file
9
tests/src/tests/analytics/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import firebase from '../../firebase';
|
||||
import TestSuite from '../../../lib/TestSuite';
|
||||
import analyticsTests from './analytics';
|
||||
|
||||
const suite = new TestSuite('Analytics', 'firebase.analytics()', firebase);
|
||||
|
||||
suite.addTests(analyticsTests);
|
||||
|
||||
export default suite;
|
||||
311
tests/src/tests/auth/authTests.js
Normal file
311
tests/src/tests/auth/authTests.js
Normal file
@@ -0,0 +1,311 @@
|
||||
import should from 'should';
|
||||
|
||||
function randomString(length, chars) {
|
||||
let mask = '';
|
||||
if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz';
|
||||
if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
if (chars.indexOf('#') > -1) mask += '0123456789';
|
||||
if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\';
|
||||
let result = '';
|
||||
for (let i = length; i > 0; --i) result += mask[Math.round(Math.random() * (mask.length - 1))];
|
||||
return result;
|
||||
}
|
||||
|
||||
function authTests({ tryCatch, describe, it, firebase }) {
|
||||
describe('Anonymous', () => {
|
||||
it('it should sign in anonymously', () => {
|
||||
const successCb = (currentUser) => {
|
||||
currentUser.should.be.an.Object();
|
||||
currentUser.uid.should.be.a.String();
|
||||
currentUser.toJSON().should.be.an.Object();
|
||||
should.equal(currentUser.toJSON().email, null);
|
||||
currentUser.isAnonymous.should.equal(true);
|
||||
currentUser.providerId.should.equal('firebase');
|
||||
|
||||
firebase.native.auth().currentUser.uid.should.be.a.String();
|
||||
|
||||
return firebase.native.auth().signOut();
|
||||
};
|
||||
|
||||
return firebase.native.auth().signInAnonymously().then(successCb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Link', () => {
|
||||
it('it should link anonymous account <-> email account', () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
const successCb = (currentUser) => {
|
||||
currentUser.should.be.an.Object();
|
||||
currentUser.uid.should.be.a.String();
|
||||
currentUser.toJSON().should.be.an.Object();
|
||||
should.equal(currentUser.toJSON().email, null);
|
||||
currentUser.isAnonymous.should.equal(true);
|
||||
currentUser.providerId.should.equal('firebase');
|
||||
firebase.native.auth().currentUser.uid.should.be.a.String();
|
||||
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(email, pass);
|
||||
|
||||
return currentUser
|
||||
.link(credential)
|
||||
.then((linkedUser) => {
|
||||
linkedUser.should.be.an.Object();
|
||||
linkedUser.uid.should.be.a.String();
|
||||
linkedUser.toJSON().should.be.an.Object();
|
||||
linkedUser.toJSON().email.should.eql(email);
|
||||
linkedUser.isAnonymous.should.equal(false);
|
||||
linkedUser.providerId.should.equal('firebase');
|
||||
return firebase.native.auth().signOut();
|
||||
}).catch((error) => {
|
||||
return firebase.native.auth().signOut().then(() => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return firebase.native.auth().signInAnonymously().then(successCb);
|
||||
});
|
||||
|
||||
it('it should error on link anon <-> email if email already exists', () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
const successCb = (currentUser) => {
|
||||
currentUser.should.be.an.Object();
|
||||
currentUser.uid.should.be.a.String();
|
||||
currentUser.toJSON().should.be.an.Object();
|
||||
should.equal(currentUser.toJSON().email, null);
|
||||
currentUser.isAnonymous.should.equal(true);
|
||||
currentUser.providerId.should.equal('firebase');
|
||||
firebase.native.auth().currentUser.uid.should.be.a.String();
|
||||
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(email, pass);
|
||||
|
||||
return currentUser
|
||||
.link(credential)
|
||||
.then(() => {
|
||||
return firebase.native.auth().signOut().then(() => {
|
||||
return Promise.reject(new Error('Did not error on link'));
|
||||
});
|
||||
}).catch((error) => {
|
||||
return firebase.native.auth().signOut().then(() => {
|
||||
error.code.should.equal('auth/email-already-in-use');
|
||||
error.message.should.equal('The email address is already in use by another account.');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return firebase.native.auth().signInAnonymously().then(successCb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Email - Login', () => {
|
||||
it('it should login with email and password', () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
const successCb = (currentUser) => {
|
||||
currentUser.should.be.an.Object();
|
||||
currentUser.uid.should.be.a.String();
|
||||
currentUser.toJSON().should.be.an.Object();
|
||||
currentUser.toJSON().email.should.eql('test@test.com');
|
||||
currentUser.isAnonymous.should.equal(false);
|
||||
currentUser.providerId.should.equal('firebase');
|
||||
|
||||
firebase.native.auth().currentUser.uid.should.be.a.String();
|
||||
|
||||
return firebase.native.auth().signOut();
|
||||
};
|
||||
|
||||
return firebase.native.auth().signInWithEmailAndPassword(email, pass).then(successCb);
|
||||
});
|
||||
|
||||
it('it should error on login if user is disabled', () => {
|
||||
const email = 'disabled@account.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
const successCb = () => {
|
||||
return Promise.reject(new Error('Did not error.'));
|
||||
};
|
||||
|
||||
const failureCb = (error) => {
|
||||
error.code.should.equal('auth/user-disabled');
|
||||
error.message.should.equal('The user account has been disabled by an administrator.');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase.native.auth().signInWithEmailAndPassword(email, pass).then(successCb).catch(failureCb);
|
||||
});
|
||||
|
||||
it('it should error on login if password incorrect', () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test1234666';
|
||||
|
||||
const successCb = () => {
|
||||
return Promise.reject(new Error('Did not error.'));
|
||||
};
|
||||
|
||||
const failureCb = (error) => {
|
||||
error.code.should.equal('auth/wrong-password');
|
||||
error.message.should.equal('The password is invalid or the user does not have a password.');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase.native.auth().signInWithEmailAndPassword(email, pass).then(successCb).catch(failureCb);
|
||||
});
|
||||
|
||||
it('it should error on login if user not found', () => {
|
||||
const email = 'randomSomeone@fourOhFour.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
const successCb = () => {
|
||||
return Promise.reject(new Error('Did not error.'));
|
||||
};
|
||||
|
||||
const failureCb = (error) => {
|
||||
error.code.should.equal('auth/user-not-found');
|
||||
error.message.should.equal('There is no user record corresponding to this identifier. The user may have been deleted.');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase.native.auth().signInWithEmailAndPassword(email, pass).then(successCb).catch(failureCb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Email - Create', () => {
|
||||
it('it should create a user with an email and password', () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
const successCb = (newUser) => {
|
||||
newUser.uid.should.be.a.String();
|
||||
newUser.email.should.equal(email.toLowerCase());
|
||||
newUser.emailVerified.should.equal(false);
|
||||
newUser.isAnonymous.should.equal(false);
|
||||
newUser.providerId.should.equal('firebase');
|
||||
};
|
||||
|
||||
return firebase.native.auth().createUserWithEmailAndPassword(email, pass).then(successCb);
|
||||
});
|
||||
|
||||
it('it should error on create with invalid email', () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}${random}.com.boop.shoop`;
|
||||
const pass = random;
|
||||
|
||||
const successCb = () => {
|
||||
return Promise.reject(new Error('Did not error.'));
|
||||
};
|
||||
|
||||
const failureCb = (error) => {
|
||||
error.code.should.equal('auth/invalid-email');
|
||||
error.message.should.equal('The email address is badly formatted.');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase.native.auth().createUserWithEmailAndPassword(email, pass).then(successCb).catch(failureCb);
|
||||
});
|
||||
|
||||
it('it should error on create if email in use', () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test123456789';
|
||||
|
||||
const successCb = () => {
|
||||
return Promise.reject(new Error('Did not error.'));
|
||||
};
|
||||
|
||||
const failureCb = (error) => {
|
||||
error.code.should.equal('auth/email-already-in-use');
|
||||
error.message.should.equal('The email address is already in use by another account.');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase.native.auth().createUserWithEmailAndPassword(email, pass).then(successCb).catch(failureCb);
|
||||
});
|
||||
|
||||
it('it should error on create if password weak', () => {
|
||||
const email = 'testy@testy.com';
|
||||
const pass = '123';
|
||||
|
||||
const successCb = () => {
|
||||
return Promise.reject(new Error('Did not error.'));
|
||||
};
|
||||
|
||||
const failureCb = (error) => {
|
||||
error.code.should.equal('auth/weak-password');
|
||||
// cannot test this message - it's different on the web client than ios/android return
|
||||
// error.message.should.equal('The given password is invalid.');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase.native.auth().createUserWithEmailAndPassword(email, pass).then(successCb).catch(failureCb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Misc', () => {
|
||||
it('it should delete a user', () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
const successCb = (newUser) => {
|
||||
newUser.uid.should.be.a.String();
|
||||
newUser.email.should.equal(email.toLowerCase());
|
||||
newUser.emailVerified.should.equal(false);
|
||||
newUser.isAnonymous.should.equal(false);
|
||||
newUser.providerId.should.equal('firebase');
|
||||
return firebase.native.auth().currentUser.delete();
|
||||
};
|
||||
|
||||
return firebase.native.auth().createUserWithEmailAndPassword(email, pass).then(successCb);
|
||||
});
|
||||
|
||||
it('it should return a token via getToken', () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
const successCb = (newUser) => {
|
||||
newUser.uid.should.be.a.String();
|
||||
newUser.email.should.equal(email.toLowerCase());
|
||||
newUser.emailVerified.should.equal(false);
|
||||
newUser.isAnonymous.should.equal(false);
|
||||
newUser.providerId.should.equal('firebase');
|
||||
|
||||
return newUser.getToken().then((token) => {
|
||||
token.should.be.a.String();
|
||||
token.length.should.be.greaterThan(24);
|
||||
return firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
};
|
||||
|
||||
return firebase.native.auth().createUserWithEmailAndPassword(email, pass).then(successCb);
|
||||
});
|
||||
|
||||
it('it should reject signOut if no currentUser', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (firebase.native.auth().currentUser) {
|
||||
return reject(new Error(`A user is currently signed in. ${firebase.native.auth().currentUser.uid}`));
|
||||
}
|
||||
|
||||
const successCb = tryCatch(() => {
|
||||
reject(new Error('No signOut error returned'));
|
||||
}, reject);
|
||||
|
||||
const failureCb = tryCatch((error) => {
|
||||
error.code.should.equal('auth/no_current_user');
|
||||
error.message.should.equal('No user currently signed in.');
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
return firebase.native.auth().signOut().then(successCb).catch(failureCb);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default authTests;
|
||||
10
tests/src/tests/auth/index.js
Normal file
10
tests/src/tests/auth/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import firebase from '../../firebase';
|
||||
import TestSuite from '../../../lib/TestSuite';
|
||||
|
||||
import authTests from './authTests';
|
||||
|
||||
const suite = new TestSuite('Auth', 'firebase.auth()', firebase);
|
||||
|
||||
suite.addTests(authTests);
|
||||
|
||||
export default suite;
|
||||
10
tests/src/tests/crash/index.js
Normal file
10
tests/src/tests/crash/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import firebase from '../../firebase';
|
||||
import TestSuite from '../../../lib/TestSuite';
|
||||
import logTests from './log';
|
||||
|
||||
const suite = new TestSuite('Crash', 'firebase.crash()', firebase);
|
||||
|
||||
// bootstrap tests
|
||||
suite.addTests(logTests);
|
||||
|
||||
export default suite;
|
||||
17
tests/src/tests/crash/log.js
Normal file
17
tests/src/tests/crash/log.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export default function addTests({ describe, it, firebase }) {
|
||||
describe('Log', () => {
|
||||
it('log: it should log without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.crash().log('Test log');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('logcat: it should log without error', () => {
|
||||
return new Promise((resolve) => {
|
||||
firebase.native.crash().logcat(0, 'LogTest', 'Test log');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
21
tests/src/tests/database/index.js
Normal file
21
tests/src/tests/database/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import firebase from '../../firebase';
|
||||
import TestSuite from '../../../lib/TestSuite';
|
||||
|
||||
/*
|
||||
Test suite files
|
||||
*/
|
||||
|
||||
import snapshotTests from './snapshot';
|
||||
import refTestGroups from './ref/index';
|
||||
|
||||
const suite = new TestSuite('Database', 'firebase.database()', firebase);
|
||||
|
||||
/*
|
||||
Register tests with test suite
|
||||
*/
|
||||
|
||||
suite.addTests(refTestGroups);
|
||||
suite.addTests(snapshotTests);
|
||||
|
||||
export default suite;
|
||||
|
||||
69
tests/src/tests/database/ref/childTests.js
Normal file
69
tests/src/tests/database/ref/childTests.js
Normal file
@@ -0,0 +1,69 @@
|
||||
function childTests({ describe, it, context, firebase }) {
|
||||
describe('ref().child', () => {
|
||||
context('when passed a shallow path', () => {
|
||||
it('returns correct child ref', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests');
|
||||
|
||||
// Test
|
||||
|
||||
const childRef = ref.child('tests');
|
||||
|
||||
// Assertion
|
||||
|
||||
childRef.key.should.eql('tests');
|
||||
});
|
||||
});
|
||||
|
||||
context('when passed a nested path', () => {
|
||||
it('returns correct child ref', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests');
|
||||
|
||||
// Test
|
||||
|
||||
const grandChildRef = ref.child('tests/number');
|
||||
|
||||
// Assertion
|
||||
|
||||
grandChildRef.key.should.eql('number');
|
||||
});
|
||||
});
|
||||
|
||||
context('when passed a path that doesn\'t exist', () => {
|
||||
it('creates a reference, anyway', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests');
|
||||
|
||||
// Test
|
||||
|
||||
const grandChildRef = ref.child('doesnt/exist');
|
||||
|
||||
// Assertion
|
||||
|
||||
grandChildRef.key.should.eql('exist');
|
||||
});
|
||||
});
|
||||
|
||||
context('when passed an invalid path', () => {
|
||||
it('creates a reference, anyway', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests');
|
||||
|
||||
// Test
|
||||
|
||||
const grandChildRef = ref.child('does$&nt/exist');
|
||||
|
||||
// Assertion
|
||||
|
||||
grandChildRef.key.should.eql('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default childTests;
|
||||
38
tests/src/tests/database/ref/factoryTests.js
Normal file
38
tests/src/tests/database/ref/factoryTests.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function factoryTests({ describe, it, firebase }) {
|
||||
describe('ref()', () => {
|
||||
it('returns root reference when provided no path', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref();
|
||||
|
||||
// Test
|
||||
|
||||
|
||||
// Assertion
|
||||
|
||||
(ref.key === null).should.be.true();
|
||||
(ref.parent === null).should.be.true();
|
||||
});
|
||||
|
||||
it('returns reference to data at path', async () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
// Test
|
||||
let valueAtRef;
|
||||
|
||||
await ref.once('value', (snapshot) => {
|
||||
valueAtRef = snapshot.val();
|
||||
});
|
||||
|
||||
// Assertion
|
||||
|
||||
valueAtRef.should.eql(DatabaseContents.DEFAULT.number);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default factoryTests;
|
||||
43
tests/src/tests/database/ref/index.js
Normal file
43
tests/src/tests/database/ref/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import onTests from './onTests';
|
||||
import offTests from './offTests';
|
||||
import onceTests from './onceTests';
|
||||
import setTests from './setTests';
|
||||
import updateTests from './updateTests';
|
||||
import removeTests from './removeTests';
|
||||
import pushTests from './pushTests';
|
||||
import factoryTests from './factoryTests';
|
||||
import keyTests from './keyTests';
|
||||
import parentTests from './parentTests';
|
||||
import childTests from './childTests';
|
||||
import isEqualTests from './isEqualTests';
|
||||
import refTests from './refTests';
|
||||
import rootTests from './rootTests';
|
||||
import transactionTests from './transactionTests';
|
||||
import queryTests from './queryTests';
|
||||
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
const testGroups = [
|
||||
factoryTests, keyTests, parentTests, childTests, rootTests,
|
||||
pushTests, onTests, offTests, onceTests, updateTests, removeTests, setTests,
|
||||
transactionTests, queryTests, refTests, isEqualTests,
|
||||
];
|
||||
|
||||
function registerTestSuite(testSuite) {
|
||||
testSuite.beforeEach(async function () {
|
||||
this._databaseRef = testSuite.firebase.native.database().ref('tests/types');
|
||||
|
||||
await this._databaseRef.set(DatabaseContents.DEFAULT);
|
||||
});
|
||||
|
||||
testSuite.afterEach(async function () {
|
||||
await this._databaseRef.set(DatabaseContents.DEFAULT);
|
||||
});
|
||||
|
||||
testGroups.forEach((testGroup) => {
|
||||
testGroup(testSuite);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = registerTestSuite;
|
||||
41
tests/src/tests/database/ref/isEqualTests.js
Normal file
41
tests/src/tests/database/ref/isEqualTests.js
Normal file
@@ -0,0 +1,41 @@
|
||||
function isEqualTests({ describe, before, it, firebase }) {
|
||||
describe('ref().isEqual()', () => {
|
||||
before(() => {
|
||||
this.ref = firebase.native.database().ref('tests/types');
|
||||
});
|
||||
|
||||
it('returns true when the reference is for the same location', () => {
|
||||
// Setup
|
||||
|
||||
const ref2 = firebase.native.database().ref('tests/types');
|
||||
|
||||
// Assertion
|
||||
|
||||
this.ref.isEqual(ref2).should.eql(true);
|
||||
});
|
||||
|
||||
it('returns false when the reference is for a different location', () => {
|
||||
// Setup
|
||||
|
||||
const ref2 = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
// Assertion
|
||||
|
||||
this.ref.isEqual(ref2).should.eql(false);
|
||||
});
|
||||
|
||||
it('returns false when the reference is null', () => {
|
||||
// Assertion
|
||||
|
||||
this.ref.isEqual(null).should.eql(false);
|
||||
});
|
||||
|
||||
it('returns false when the reference is not a Reference', () => {
|
||||
// Assertion
|
||||
|
||||
this.ref.isEqual(1).should.eql(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default isEqualTests;
|
||||
30
tests/src/tests/database/ref/keyTests.js
Normal file
30
tests/src/tests/database/ref/keyTests.js
Normal file
@@ -0,0 +1,30 @@
|
||||
function keyTests({ describe, it, firebase }) {
|
||||
describe('ref().key', () => {
|
||||
it('returns null for root ref', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref();
|
||||
|
||||
// Test
|
||||
|
||||
|
||||
// Assertion
|
||||
|
||||
(ref.key === null).should.be.true();
|
||||
});
|
||||
|
||||
it('returns correct key for path', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
const arrayItemRef = firebase.native.database().ref('tests/types/array/1');
|
||||
|
||||
// Assertion
|
||||
|
||||
ref.key.should.eql('number');
|
||||
arrayItemRef.key.should.eql('1');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default keyTests;
|
||||
276
tests/src/tests/database/ref/offTests.js
Normal file
276
tests/src/tests/database/ref/offTests.js
Normal file
@@ -0,0 +1,276 @@
|
||||
import should from 'should';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function offTests({ describe, it, xit, xcontext, context, firebase }) {
|
||||
|
||||
describe('ref().off()', () => {
|
||||
xit('doesn\'t unbind children callbacks', async () => {
|
||||
// Setup
|
||||
|
||||
const parentCallback = sinon.spy();
|
||||
const childCallback = sinon.spy();
|
||||
|
||||
const parentRef = firebase.native.database().ref('tests/types');
|
||||
const childRef = firebase.native.database().ref('tests/types/string');
|
||||
|
||||
await new Promise((resolve) => {
|
||||
parentRef.on('value', () => {
|
||||
parentCallback();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
childRef.on('value', () => {
|
||||
childCallback();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
parentCallback.should.be.calledOnce();
|
||||
childCallback.should.be.calledOnce();
|
||||
|
||||
// Returns nothing
|
||||
should(parentRef.off(), undefined);
|
||||
|
||||
// Trigger event parent callback is listening to
|
||||
await parentRef.set(DatabaseContents.DEFAULT);
|
||||
|
||||
// parent and child callbacks should not have been called any more
|
||||
parentCallback.should.be.calledOnce();
|
||||
childCallback.should.be.calledOnce();
|
||||
|
||||
// Trigger event child callback is listening to
|
||||
await childRef.set(DatabaseContents.DEFAULT.string);
|
||||
|
||||
// child callback should still be listening
|
||||
childCallback.should.be.calledOnce();
|
||||
|
||||
// Teardown
|
||||
childRef.off();
|
||||
});
|
||||
|
||||
context('when passed no arguments', () => {
|
||||
context('and there are no callbacks bound', () => {
|
||||
it('does nothing', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
should(ref.off(), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('stops all callbacks listening for all changes', async () => {
|
||||
// Setup
|
||||
|
||||
const valueCallback = sinon.spy();
|
||||
const childAddedCallback = sinon.spy();
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
const arrayLength = DatabaseContents.DEFAULT.array.length;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
valueCallback();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('child_added', () => {
|
||||
childAddedCallback();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
valueCallback.should.be.calledOnce();
|
||||
childAddedCallback.should.have.callCount(arrayLength);
|
||||
|
||||
// Check childAddedCallback is really attached
|
||||
await ref.push(DatabaseContents.DEFAULT.number);
|
||||
childAddedCallback.should.be.callCount(arrayLength + 1);
|
||||
|
||||
// Returns nothing
|
||||
should(ref.off(), undefined);
|
||||
|
||||
// Trigger both callbacks
|
||||
|
||||
await ref.set(DatabaseContents.DEFAULT.array);
|
||||
await ref.push(DatabaseContents.DEFAULT.number);
|
||||
|
||||
// Callbacks should have been unbound and not called again
|
||||
valueCallback.should.be.calledOnce();
|
||||
childAddedCallback.should.be.callCount(arrayLength + 1);
|
||||
});
|
||||
});
|
||||
|
||||
context('when passed an event type', () => {
|
||||
context('and there are no callbacks bound', () => {
|
||||
it('does nothing', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
should(ref.off('value'), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
context('that is invalid', () => {
|
||||
it('does nothing', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
should(ref.off('invalid'), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
xit('detaches all callbacks listening for that event', async () => {
|
||||
// Setup
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
const callbackB = sinon.spy();
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/string');
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackA();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackB();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callbackA.should.be.calledOnce();
|
||||
callbackB.should.be.calledOnce();
|
||||
|
||||
// Returns nothing
|
||||
should(ref.off('value'), undefined);
|
||||
|
||||
// Assertions
|
||||
|
||||
await ref.set(DatabaseContents.DEFAULT.string);
|
||||
|
||||
// Callbacks should have been unbound and not called again
|
||||
callbackA.should.be.calledOnce();
|
||||
callbackB.should.be.calledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
context('when passed a particular callback', () => {
|
||||
context('and there are no callbacks bound', () => {
|
||||
it('does nothing', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
should(ref.off('value', sinon.spy()), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
xit('detaches only that callback', async () => {
|
||||
// Setup
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
const callbackB = sinon.spy();
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/string');
|
||||
|
||||
// Attach the callback the first time
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackA();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Attach the callback the second time
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackB();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callbackA.should.be.calledOnce();
|
||||
callbackB.should.be.calledOnce();
|
||||
|
||||
// Detach callbackA, only
|
||||
should(ref.off('value', callbackA), undefined);
|
||||
|
||||
// Trigger the event the callback is listening to
|
||||
await ref.set(DatabaseContents.DEFAULT.string);
|
||||
|
||||
// CallbackB should still be attached
|
||||
callbackA.should.be.calledOnce();
|
||||
callbackB.should.be.calledTwice();
|
||||
|
||||
// Teardown
|
||||
should(ref.off('value', callbackB), undefined);
|
||||
});
|
||||
|
||||
context('that has been added multiple times', () => {
|
||||
xit('must be called as many times completely remove', async () => {
|
||||
// Setup
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/string');
|
||||
|
||||
// Attach the callback the first time
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackA();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Attach the callback the second time
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', () => {
|
||||
callbackA();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callbackA.should.be.calledTwice();
|
||||
|
||||
// Undo the first time the callback was attached
|
||||
should(ref.off(), undefined);
|
||||
|
||||
// Trigger the event the callback is listening to
|
||||
await ref.set(DatabaseContents.DEFAULT.number);
|
||||
|
||||
// Callback should have been called only once because one of the attachments
|
||||
// has been removed
|
||||
callbackA.should.be.calledThrice();
|
||||
|
||||
// Undo the second attachment
|
||||
should(ref.off(), undefined);
|
||||
|
||||
// Trigger the event the callback is listening to
|
||||
await ref.set(DatabaseContents.DEFAULT.number);
|
||||
|
||||
// Callback should not have been called any more times
|
||||
callbackA.should.be.calledThrice();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
xcontext('when a context', () => {
|
||||
/**
|
||||
* @todo Add tests for when a context is passed. Not sure what the intended
|
||||
* behaviour is as the documentation is unclear, but assumption is that as the
|
||||
* context is not required to unbind a listener, it's used as a filter parameter
|
||||
* so in order for off() to remove a callback, the callback must have been bound
|
||||
* with the same event type, callback function and context.
|
||||
*
|
||||
* Needs to be tested against web implementation, if possible.
|
||||
*/
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default offTests;
|
||||
125
tests/src/tests/database/ref/onTests.js
Normal file
125
tests/src/tests/database/ref/onTests.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import sinon from 'sinon';
|
||||
import 'should-sinon';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function onTests({ describe, it, firebase, tryCatch }) {
|
||||
describe('ref().on()', () => {
|
||||
it('calls callback when value changes', () => {
|
||||
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', (snapshot) => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(currentDataValue);
|
||||
|
||||
const newDataValue = DatabaseContents.NEW[dataRef];
|
||||
await ref.set(newDataValue);
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledWith(newDataValue);
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows binding multiple callbacks to the same ref', () => {
|
||||
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
|
||||
|
||||
const callbackA = sinon.spy();
|
||||
const callbackB = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', (snapshot) => {
|
||||
callbackA(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', (snapshot) => {
|
||||
callbackB(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callbackA.should.be.calledWith(currentDataValue);
|
||||
callbackB.should.be.calledWith(currentDataValue);
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls callback with current values', () => {
|
||||
return Promise.each(Object.keys(DatabaseContents.DEFAULT), (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const dataTypeValue = DatabaseContents.DEFAULT[dataRef];
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
// Test
|
||||
|
||||
return ref.on('value', (snapshot) => {
|
||||
// Assertion
|
||||
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if permission denied', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch(() => {
|
||||
// Assertion
|
||||
|
||||
reject(new Error('No permission denied error'));
|
||||
}, reject);
|
||||
|
||||
const failureCb = tryCatch((error) => {
|
||||
// Assertion
|
||||
|
||||
error.message.includes('permission_denied').should.be.true();
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
// Setup
|
||||
|
||||
const invalidRef = firebase.native.database().ref('nope');
|
||||
|
||||
// Test
|
||||
|
||||
invalidRef.on('value', successCb, failureCb);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default onTests;
|
||||
81
tests/src/tests/database/ref/onceTests.js
Normal file
81
tests/src/tests/database/ref/onceTests.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import sinon from 'sinon';
|
||||
import 'should-sinon';
|
||||
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function onceTests({ describe, firebase, it, tryCatch }) {
|
||||
describe('ref().once()', () => {
|
||||
it('returns a promise', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
// Test
|
||||
|
||||
const returnValue = ref.once('value');
|
||||
|
||||
// Assertion
|
||||
|
||||
returnValue.should.be.Promise();
|
||||
});
|
||||
|
||||
it('resolves with the correct value', async () => {
|
||||
await Promise.map(Object.keys(DatabaseContents.DEFAULT), (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const dataTypeValue = DatabaseContents.DEFAULT[dataRef];
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
// Test
|
||||
|
||||
return ref.once('value').then((snapshot) => {
|
||||
// Assertion
|
||||
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('is NOT called when the value is changed', async () => {
|
||||
// Setup
|
||||
|
||||
const callback = sinon.spy();
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
// Test
|
||||
|
||||
ref.once('value').then(callback);
|
||||
|
||||
await ref.set(DatabaseContents.NEW.number);
|
||||
|
||||
// Assertion
|
||||
callback.should.be.calledOnce();
|
||||
});
|
||||
|
||||
it('errors if permission denied', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch(() => {
|
||||
// Assertion
|
||||
|
||||
reject(new Error('No permission denied error'));
|
||||
}, reject);
|
||||
|
||||
const failureCb = tryCatch((error) => {
|
||||
// Assertion
|
||||
|
||||
error.message.includes('permission_denied').should.be.true();
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
// Setup
|
||||
|
||||
const reference = firebase.native.database().ref('nope');
|
||||
|
||||
// Test
|
||||
reference.once('value', successCb, failureCb);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default onceTests;
|
||||
33
tests/src/tests/database/ref/parentTests.js
Normal file
33
tests/src/tests/database/ref/parentTests.js
Normal file
@@ -0,0 +1,33 @@
|
||||
function parentTests({ describe, context, it, firebase }) {
|
||||
describe('ref().parent', () => {
|
||||
context('on the root ref', () => {
|
||||
it('returns null', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref();
|
||||
|
||||
// Test
|
||||
|
||||
|
||||
// Assertion
|
||||
|
||||
(ref.parent === null).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
context('on a non-root ref', () => {
|
||||
it('returns correct parent', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
const parentRef = firebase.native.database().ref('tests/types');
|
||||
|
||||
// Assertion
|
||||
|
||||
ref.parent.key.should.eql(parentRef.key);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default parentTests;
|
||||
113
tests/src/tests/database/ref/pushTests.js
Normal file
113
tests/src/tests/database/ref/pushTests.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import sinon from 'sinon';
|
||||
import 'should-sinon';
|
||||
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function pushTests({ describe, it, firebase }) {
|
||||
describe('ref().push()', () => {
|
||||
it('returns a ref that can be used to set value later', async () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
let originalListValue;
|
||||
|
||||
await ref.once('value', (snapshot) => {
|
||||
originalListValue = snapshot.val();
|
||||
});
|
||||
|
||||
originalListValue.should.eql(DatabaseContents.DEFAULT.array);
|
||||
|
||||
// Test
|
||||
|
||||
const newItemRef = ref.push();
|
||||
|
||||
const valueToAddToList = DatabaseContents.NEW.number;
|
||||
await newItemRef.set(valueToAddToList);
|
||||
|
||||
let newItemValue,
|
||||
newListValue;
|
||||
|
||||
// Assertion
|
||||
|
||||
await newItemRef.once('value', (snapshot) => {
|
||||
newItemValue = snapshot.val();
|
||||
});
|
||||
|
||||
newItemValue.should.eql(valueToAddToList);
|
||||
|
||||
await ref.once('value', (snapshot) => {
|
||||
newListValue = snapshot.val();
|
||||
});
|
||||
|
||||
const originalListAsObject = originalListValue.reduce((memo, value, index) => {
|
||||
memo[index] = value;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
originalListAsObject[newItemRef.key] = valueToAddToList;
|
||||
|
||||
newListValue.should.eql(originalListAsObject);
|
||||
});
|
||||
|
||||
it('allows setting value immediately', async () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
let originalListValue;
|
||||
|
||||
await ref.once('value', (snapshot) => {
|
||||
originalListValue = snapshot.val();
|
||||
});
|
||||
|
||||
// Test
|
||||
|
||||
const valueToAddToList = DatabaseContents.NEW.number;
|
||||
const newItemRef = await ref.push(valueToAddToList);
|
||||
|
||||
let newItemValue,
|
||||
newListValue;
|
||||
|
||||
// Assertion
|
||||
|
||||
await newItemRef.once('value', (snapshot) => {
|
||||
newItemValue = snapshot.val();
|
||||
});
|
||||
|
||||
newItemValue.should.eql(valueToAddToList);
|
||||
|
||||
await ref.once('value', (snapshot) => {
|
||||
newListValue = snapshot.val();
|
||||
});
|
||||
|
||||
const originalListAsObject = originalListValue.reduce((memo, value, index) => {
|
||||
memo[index] = value;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
originalListAsObject[newItemRef.key] = valueToAddToList;
|
||||
|
||||
newListValue.should.eql(originalListAsObject);
|
||||
});
|
||||
|
||||
it('calls an onComplete callback', async () => {
|
||||
// Setup
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
// Test
|
||||
|
||||
const valueToAddToList = DatabaseContents.NEW.number;
|
||||
await ref.push(valueToAddToList, callback);
|
||||
|
||||
// Assertion
|
||||
|
||||
callback.should.be.calledWith(null);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export default pushTests;
|
||||
25
tests/src/tests/database/ref/queryTests.js
Normal file
25
tests/src/tests/database/ref/queryTests.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'should-sinon';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
function queryTests({ describe, it, firebase, tryCatch }) {
|
||||
describe('ref query', () => {
|
||||
it('orderByChild().equalTo()', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
const webVal = snapshot.val();
|
||||
const ref = firebase.native.database().ref('tests/query');
|
||||
|
||||
ref.orderByChild('search').equalTo('foo').once('value', tryCatch((snapshot) => {
|
||||
const nativeVal = snapshot.val();
|
||||
nativeVal.should.eql(webVal);
|
||||
resolve();
|
||||
}, reject), reject);
|
||||
}, reject);
|
||||
|
||||
firebase.web.database().ref('tests/query').orderByChild('search').equalTo('foo').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default queryTests;
|
||||
15
tests/src/tests/database/ref/refTests.js
Normal file
15
tests/src/tests/database/ref/refTests.js
Normal file
@@ -0,0 +1,15 @@
|
||||
function refTests({ describe, it, firebase }) {
|
||||
describe('ref().ref', () => {
|
||||
it('returns a the reference itself', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref();
|
||||
|
||||
// Assertion
|
||||
|
||||
ref.ref.should.eql(ref);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default refTests;
|
||||
44
tests/src/tests/database/ref/removeTests.js
Normal file
44
tests/src/tests/database/ref/removeTests.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function removeTests({ describe, it, firebase }) {
|
||||
describe('ref().remove()', () => {
|
||||
it('returns a promise', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types');
|
||||
|
||||
// Test
|
||||
|
||||
const returnValue = ref.remove({ number: DatabaseContents.DEFAULT.number });
|
||||
|
||||
// Assertion
|
||||
|
||||
returnValue.should.be.Promise();
|
||||
});
|
||||
|
||||
it('sets value to null', async () => {
|
||||
await Promise.map(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const previousValue = DatabaseContents.DEFAULT[dataRef];
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(previousValue);
|
||||
});
|
||||
|
||||
// Test
|
||||
|
||||
await ref.remove();
|
||||
|
||||
// Assertion
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
(snapshot.val() === null).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default removeTests;
|
||||
36
tests/src/tests/database/ref/rootTests.js
Normal file
36
tests/src/tests/database/ref/rootTests.js
Normal file
@@ -0,0 +1,36 @@
|
||||
function rootTests({ describe, it, context, firebase }) {
|
||||
describe('ref().root', () => {
|
||||
context('when called on a non-root reference', () => {
|
||||
it('returns root ref', () => {
|
||||
// Setup
|
||||
|
||||
const rootRef = firebase.native.database().ref();
|
||||
const nonRootRef = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
// Test
|
||||
|
||||
|
||||
// Assertion
|
||||
|
||||
nonRootRef.root.should.eql(rootRef);
|
||||
});
|
||||
});
|
||||
|
||||
context('when called on the root reference', () => {
|
||||
it('returns root ref', () => {
|
||||
// Setup
|
||||
|
||||
const rootRef = firebase.native.database().ref();
|
||||
|
||||
// Test
|
||||
|
||||
|
||||
// Assertion
|
||||
|
||||
rootRef.root.should.eql(rootRef);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default rootTests;
|
||||
73
tests/src/tests/database/ref/setTests.js
Normal file
73
tests/src/tests/database/ref/setTests.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function setTests({ describe, it, xit, firebase }) {
|
||||
describe('ref.set()', () => {
|
||||
xit('returns a promise', async () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
// Test
|
||||
|
||||
const returnValue = ref.set(DatabaseContents.DEFAULT.number);
|
||||
|
||||
// Assertion
|
||||
|
||||
returnValue.should.be.Promise();
|
||||
|
||||
await returnValue.then((value) => {
|
||||
(value === undefined).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('changes value', async () => {
|
||||
await Promise.map(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const previousValue = DatabaseContents.DEFAULT[dataRef];
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(previousValue);
|
||||
});
|
||||
|
||||
const newValue = DatabaseContents.NEW[dataRef];
|
||||
|
||||
// Test
|
||||
|
||||
await ref.set(newValue);
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
// Assertion
|
||||
|
||||
snapshot.val().should.eql(newValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can unset values', async () => {
|
||||
await Promise.map(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const previousValue = DatabaseContents.DEFAULT[dataRef];
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(previousValue);
|
||||
});
|
||||
|
||||
// Test
|
||||
|
||||
await ref.set(null);
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
// Assertion
|
||||
|
||||
(snapshot.val() === null).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default setTests;
|
||||
53
tests/src/tests/database/ref/transactionTests.js
Normal file
53
tests/src/tests/database/ref/transactionTests.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import Promise from 'bluebird';
|
||||
|
||||
function onTests({ describe, it, firebase, tryCatch }) {
|
||||
describe('ref.transaction()', () => {
|
||||
it('works', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let valueBefore = 1;
|
||||
|
||||
firebase.native.database()
|
||||
.ref('tests/transaction').transaction((currentData) => {
|
||||
if (currentData === null) {
|
||||
return valueBefore + 10;
|
||||
}
|
||||
valueBefore = currentData;
|
||||
return valueBefore + 10;
|
||||
}, tryCatch((error, committed, snapshot) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
if (!committed) {
|
||||
return reject(new Error('Transaction did not commit.'));
|
||||
}
|
||||
|
||||
snapshot.val().should.equal(valueBefore + 10);
|
||||
|
||||
return resolve();
|
||||
}, reject), true);
|
||||
});
|
||||
});
|
||||
|
||||
it('aborts if undefined returned', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
firebase.native.database()
|
||||
.ref('tests/transaction').transaction(() => {
|
||||
return undefined;
|
||||
}, (error, committed) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
if (!committed) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
return reject(new Error('Transaction did not abort commit.'));
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default onTests;
|
||||
112
tests/src/tests/database/ref/updateTests.js
Normal file
112
tests/src/tests/database/ref/updateTests.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import Promise from 'bluebird';
|
||||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
function updateTests({ describe, it, firebase }) {
|
||||
describe('ref().update()', () => {
|
||||
it('returns a promise', () => {
|
||||
// Setup
|
||||
|
||||
const ref = firebase.native.database().ref('tests/types');
|
||||
|
||||
// Test
|
||||
|
||||
const returnValue = ref.update({ number: DatabaseContents.DEFAULT.number });
|
||||
|
||||
// Assertion
|
||||
|
||||
returnValue.should.be.Promise();
|
||||
});
|
||||
|
||||
it('changes value', () => {
|
||||
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const previousValue = DatabaseContents.DEFAULT[dataRef];
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(previousValue);
|
||||
});
|
||||
|
||||
const newValue = DatabaseContents.NEW[dataRef];
|
||||
const parentRef = firebase.native.database().ref('tests/types');
|
||||
|
||||
// Test
|
||||
|
||||
await parentRef.update({ [dataRef]: newValue });
|
||||
|
||||
// Assertion
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(newValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can unset values', () => {
|
||||
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||
// Setup
|
||||
|
||||
const previousValue = DatabaseContents.DEFAULT[dataRef];
|
||||
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(previousValue);
|
||||
});
|
||||
|
||||
const parentRef = firebase.native.database().ref('tests/types');
|
||||
|
||||
// Test
|
||||
|
||||
await parentRef.update({ [dataRef]: null });
|
||||
|
||||
// Assertion
|
||||
|
||||
await ref.once('value').then((snapshot) => {
|
||||
(snapshot.val() === null).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('updates multiple values at once', async () => {
|
||||
// Setup
|
||||
|
||||
const numberPreviousValue = DatabaseContents.DEFAULT.number;
|
||||
const stringPreviousValue = DatabaseContents.DEFAULT.string;
|
||||
|
||||
const numberRef = firebase.native.database().ref('tests/types/number');
|
||||
const stringRef = firebase.native.database().ref('tests/types/string');
|
||||
|
||||
await numberRef.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(numberPreviousValue);
|
||||
});
|
||||
|
||||
await stringRef.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(stringPreviousValue);
|
||||
});
|
||||
|
||||
const numberNewValue = DatabaseContents.NEW.number;
|
||||
const stringNewValue = DatabaseContents.NEW.string;
|
||||
const parentRef = firebase.native.database().ref('tests/types');
|
||||
|
||||
// Test
|
||||
|
||||
await parentRef.update({
|
||||
number: numberNewValue,
|
||||
string: stringNewValue,
|
||||
});
|
||||
|
||||
// Assertion
|
||||
|
||||
await numberRef.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(numberNewValue);
|
||||
});
|
||||
|
||||
await stringRef.once('value').then((snapshot) => {
|
||||
snapshot.val().should.eql(stringNewValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default updateTests;
|
||||
116
tests/src/tests/database/snapshot.js
Normal file
116
tests/src/tests/database/snapshot.js
Normal file
@@ -0,0 +1,116 @@
|
||||
export default function addTests({ tryCatch, describe, it, firebase }) {
|
||||
describe('Snapshot', () => {
|
||||
it('should provide a functioning val() method', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
snapshot.val.should.be.a.Function();
|
||||
snapshot.val().should.eql([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
]);
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.database().ref('tests/types/array').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a functioning child() method', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
snapshot.child('0').val.should.be.a.Function();
|
||||
snapshot.child('0').val().should.equal(0);
|
||||
snapshot.child('0').key.should.be.a.String();
|
||||
snapshot.child('0').key.should.equal('0');
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.database().ref('tests/types/array').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a functioning hasChild() method', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
snapshot.hasChild.should.be.a.Function();
|
||||
snapshot.hasChild('foo').should.equal(true);
|
||||
snapshot.hasChild('baz').should.equal(false);
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.database().ref('tests/types/object').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a functioning hasChildren() method', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
snapshot.hasChildren.should.be.a.Function();
|
||||
snapshot.hasChildren().should.equal(true);
|
||||
snapshot.child('foo').hasChildren().should.equal(false);
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.database().ref('tests/types/object').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a functioning exists() method', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
snapshot.exists.should.be.a.Function();
|
||||
snapshot.exists().should.equal(false);
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.database().ref('tests/types/object/baz/daz').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a functioning getPriority() method', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
snapshot.getPriority.should.be.a.Function();
|
||||
snapshot.getPriority().should.equal(666);
|
||||
snapshot.val().should.eql({ foo: 'bar' });
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
const ref = firebase.native.database().ref('tests/priority');
|
||||
ref.once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a functioning forEach() method', () => {
|
||||
// TODO this doesn't really test that the key order returned is in correct order
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
let total = 0;
|
||||
snapshot.forEach.should.be.a.Function();
|
||||
snapshot.forEach((childSnapshot) => {
|
||||
const val = childSnapshot.val();
|
||||
total = total + val;
|
||||
return val === 3; // stop iteration after key 3
|
||||
});
|
||||
|
||||
total.should.equal(6); // 0 + 1 + 2 + 3 = 6
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.database().ref('tests/types/array').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a key property', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((snapshot) => {
|
||||
snapshot.key.should.be.a.String();
|
||||
snapshot.key.should.equal('array');
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.database().ref('tests/types/array').once('value', successCb, reject);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
216
tests/src/tests/index.js
Normal file
216
tests/src/tests/index.js
Normal file
@@ -0,0 +1,216 @@
|
||||
import { setSuiteStatus, setTestStatus } from '../actions/TestActions';
|
||||
import analytics from './analytics/index';
|
||||
import crash from './crash/index';
|
||||
import database from './database/index';
|
||||
import messaging from './messaging/index';
|
||||
import storage from './storage/index';
|
||||
import auth from './auth/index';
|
||||
|
||||
const testSuiteInstances = [
|
||||
database,
|
||||
auth,
|
||||
analytics,
|
||||
messaging,
|
||||
crash,
|
||||
storage,
|
||||
];
|
||||
|
||||
/*
|
||||
A map of test suite instances to their ids so they may be retrieved
|
||||
at run time and called upon to run individual tests
|
||||
*/
|
||||
const testSuiteRunners = {};
|
||||
|
||||
/*
|
||||
Attributes to hold initial Redux store state
|
||||
*/
|
||||
const testSuites = {};
|
||||
const tests = {};
|
||||
const focusedTestIds = {};
|
||||
const pendingTestIds = {};
|
||||
const testContexts = {};
|
||||
|
||||
/**
|
||||
* @typedef {number} TestId
|
||||
* @typedef {number} TestSuiteId
|
||||
*
|
||||
* @typedef {Object} Test
|
||||
* @property {number} id
|
||||
* @property {number} testSuiteId
|
||||
*
|
||||
* @typedef {Object} TestSuite
|
||||
* @property {number} id
|
||||
* @property {TestId[]} testIds
|
||||
*
|
||||
* @typedef {Object.<TestId,Test>} IndexedTestGroup
|
||||
* @typedef {Object.<TestSuiteId,TestSuite>} IndexedTestSuiteGroup
|
||||
* @typedef {Object.<TestId,bool>} IdLookup
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return initial state for the tests to provide to Redux
|
||||
* @returns {{suites: {}, descriptions: {}}}
|
||||
*/
|
||||
export function initialState() {
|
||||
testSuiteInstances.forEach((testSuite) => {
|
||||
const { id, name, description } = testSuite;
|
||||
|
||||
// Add to test suite runners for later recall
|
||||
testSuiteRunners[testSuite.id] = testSuite;
|
||||
|
||||
const testDefinitions = testSuite.testDefinitions;
|
||||
|
||||
// Add to test suites to place in the redux store
|
||||
testSuites[testSuite.id] = {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
|
||||
testContextIds: Object.keys(testDefinitions.testContexts),
|
||||
testIds: Object.keys(testDefinitions.tests),
|
||||
|
||||
status: null,
|
||||
message: null,
|
||||
time: 0,
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
Object.assign(tests, testDefinitions.tests);
|
||||
Object.assign(testContexts, testDefinitions.testContexts);
|
||||
Object.assign(focusedTestIds, testDefinitions.focusedTestIds);
|
||||
Object.assign(pendingTestIds, testDefinitions.pendingTestIds);
|
||||
});
|
||||
|
||||
return {
|
||||
tests,
|
||||
testSuites,
|
||||
testContexts,
|
||||
focusedTestIds,
|
||||
pendingTestIds,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a redux store to the test suites
|
||||
* @param store
|
||||
*/
|
||||
export function setupSuites(store) {
|
||||
Object.values(testSuiteRunners).forEach((testSuite) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
testSuite.setStore(store, (action) => {
|
||||
store.dispatch(setSuiteStatus(action));
|
||||
}, (action) => {
|
||||
store.dispatch(setTestStatus(action));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single test by id, ignoring whether it's pending or focused.
|
||||
* @param {number} testId - id of test to run
|
||||
*/
|
||||
export function runTest(testId) {
|
||||
const test = tests[testId];
|
||||
|
||||
runTests({ [testId]: test });
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all tests in all test suites. If testIds is provided, only run the tests
|
||||
* that match the ids included.
|
||||
* @params {IndexedTestGroup} testGroup - Group of tests to run
|
||||
* @param {Object=} options - options limiting which tests should be run
|
||||
* @param {IdLookup} options.pendingTestIds - map of ids of pending tests
|
||||
* @param {IdLookup} options.focusedTestIds - map of ids of focused tests
|
||||
*/
|
||||
export function runTests(testGroup, options = { pendingTestIds: {}, focusedTestIds: {} }) {
|
||||
const areFocusedTests = Object.keys(options.focusedTestIds).length > 0;
|
||||
|
||||
if (areFocusedTests) {
|
||||
runOnlyTestsInLookup(testGroup, options.focusedTestIds);
|
||||
} else {
|
||||
const arePendingTests = Object.keys(options.pendingTestIds).length > 0;
|
||||
|
||||
if (arePendingTests) {
|
||||
runAllButTestsInLookup(testGroup, options.pendingTestIds);
|
||||
} else {
|
||||
const testsBySuiteId = getTestsBySuiteId(testGroup);
|
||||
runTestsBySuiteId(testsBySuiteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all tests listed in tests, except those with ids matching values in
|
||||
* testLookup
|
||||
* @param {IndexedTestGroup} testGroup - complete list of tests
|
||||
* @param {IdLookup} testLookup - id lookup of pending tests
|
||||
*/
|
||||
function runAllButTestsInLookup(testGroup, testLookup) {
|
||||
const testsToRunBySuiteId = Object.keys(testGroup).reduce((memo, testId) => {
|
||||
const testIsNotPending = !testLookup[testId];
|
||||
|
||||
if (testIsNotPending) {
|
||||
const test = testGroup[testId];
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
memo[test.testSuiteId] = memo[test.testSuiteId] || [];
|
||||
memo[test.testSuiteId].push(testId);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
Promise.each(Object.keys(testsToRunBySuiteId), (testSuiteId) => {
|
||||
const testIds = testsToRunBySuiteId[testSuiteId];
|
||||
return runSuite(testSuiteId, testIds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs only the tests listed in focused tests
|
||||
* @param {IndexedTestGroup} testGroup - complete list of tests
|
||||
* @param {IdLookup} testLookup - id lookup of focused tests
|
||||
*/
|
||||
function runOnlyTestsInLookup(testGroup, testLookup) {
|
||||
const testsInLookupBySuiteId = getTestsBySuiteId(testGroup, testLookup);
|
||||
runTestsBySuiteId(testsInLookupBySuiteId);
|
||||
}
|
||||
|
||||
function runTestsBySuiteId(suiteIdTests) {
|
||||
Promise.each(Object.keys(suiteIdTests), (testSuiteId) => {
|
||||
const testIds = suiteIdTests[testSuiteId];
|
||||
|
||||
return runSuite(testSuiteId, testIds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tests in a suite. If testIds is provided, only run the tests that match the
|
||||
* ids included.
|
||||
* @param {number} testSuiteId - Id of test suite to run
|
||||
* @param {number[]=} testIds - array of test ids to run from the test suite
|
||||
*/
|
||||
function runSuite(testSuiteId, testIds = null) {
|
||||
const testSuiteRunner = testSuiteRunners[testSuiteId];
|
||||
|
||||
if (testSuiteRunner) {
|
||||
return testSuiteRunner.run(testIds);
|
||||
}
|
||||
|
||||
console.error(`runSuite: Suite with id "${testSuiteId}" not found`);
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
function getTestsBySuiteId(testGroup, testLookup = testGroup) {
|
||||
return Object.keys(testLookup).reduce((memo, testId) => {
|
||||
const test = testGroup[testId];
|
||||
|
||||
if (test) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
memo[test.testSuiteId] = memo[test.testSuiteId] || [];
|
||||
memo[test.testSuiteId].push(testId);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, {});
|
||||
}
|
||||
10
tests/src/tests/messaging/index.js
Normal file
10
tests/src/tests/messaging/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import firebase from '../../firebase';
|
||||
import TestSuite from '../../../lib/TestSuite';
|
||||
|
||||
import messagingTests from './messagingTests';
|
||||
|
||||
const suite = new TestSuite('Messaging', 'firebase.messaging()', firebase);
|
||||
|
||||
suite.addTests(messagingTests);
|
||||
|
||||
export default suite;
|
||||
149
tests/src/tests/messaging/messagingTests.js
Normal file
149
tests/src/tests/messaging/messagingTests.js
Normal file
@@ -0,0 +1,149 @@
|
||||
function messagingTests({ describe, it, firebase }) {
|
||||
describe('FCM', () => {
|
||||
it('it should build a RemoteMessage', () => {
|
||||
const remoteMessage = new firebase.native.messaging.RemoteMessage('305229645282');
|
||||
|
||||
// all optional
|
||||
remoteMessage.setId('foobar');
|
||||
remoteMessage.setTtl(12000);
|
||||
remoteMessage.setType('something');
|
||||
remoteMessage.setData({
|
||||
object: { foo: 'bar ' },
|
||||
array: [1, 2, 3, 4, 5],
|
||||
string: 'hello',
|
||||
boolean: true,
|
||||
number: 123456,
|
||||
});
|
||||
|
||||
// return json object so we can assert values
|
||||
const mOutput = remoteMessage.toJSON();
|
||||
|
||||
mOutput.id.should.equal('foobar');
|
||||
mOutput.ttl.should.equal(12000);
|
||||
mOutput.type.should.equal('something');
|
||||
mOutput.data.should.be.a.Object();
|
||||
|
||||
// all data types should be a string as this is all that native accepts
|
||||
mOutput.data.object.should.equal('[object Object]');
|
||||
mOutput.data.array.should.equal('1,2,3,4,5');
|
||||
mOutput.data.string.should.equal('hello');
|
||||
mOutput.data.number.should.equal('123456');
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('should send a RemoteMessage', () => {
|
||||
const remoteMessage = new firebase.native.messaging.RemoteMessage('305229645282');
|
||||
|
||||
// all optional
|
||||
remoteMessage.setId('foobar');
|
||||
remoteMessage.setTtl(12000);
|
||||
remoteMessage.setType('something');
|
||||
remoteMessage.setData({
|
||||
object: { foo: 'bar ' },
|
||||
array: [1, 2, 3, 4, 5],
|
||||
string: 'hello',
|
||||
number: 123456,
|
||||
});
|
||||
|
||||
firebase.native.messaging().send(remoteMessage);
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('it should return fcm token from getToken', () => {
|
||||
const successCb = (token) => {
|
||||
console.log(token);
|
||||
token.should.be.a.String();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase.native.messaging()
|
||||
.getToken()
|
||||
.then(successCb);
|
||||
});
|
||||
|
||||
it('it should build a RemoteMessage', () => {
|
||||
const remoteMessage = new firebase.native.messaging.RemoteMessage('305229645282');
|
||||
|
||||
// all optional
|
||||
remoteMessage.setId('foobar');
|
||||
remoteMessage.setTtl(12000);
|
||||
remoteMessage.setType('something');
|
||||
remoteMessage.setData({
|
||||
object: { foo: 'bar ' },
|
||||
array: [1, 2, 3, 4, 5],
|
||||
string: 'hello',
|
||||
boolean: true,
|
||||
number: 123456,
|
||||
});
|
||||
|
||||
// return json object so we can assert values
|
||||
const mOutput = remoteMessage.toJSON();
|
||||
|
||||
mOutput.id.should.equal('foobar');
|
||||
mOutput.ttl.should.equal(12000);
|
||||
mOutput.type.should.equal('something');
|
||||
mOutput.data.should.be.a.Object();
|
||||
|
||||
// all data types should be a string as this is all that native accepts
|
||||
mOutput.data.object.should.equal('[object Object]');
|
||||
mOutput.data.array.should.equal('1,2,3,4,5');
|
||||
mOutput.data.string.should.equal('hello');
|
||||
mOutput.data.number.should.equal('123456');
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
|
||||
it('it should send a RemoteMessage', () => {
|
||||
const remoteMessage = new firebase.native.messaging.RemoteMessage('305229645282');
|
||||
|
||||
// all optional
|
||||
remoteMessage.setId('foobar');
|
||||
remoteMessage.setTtl(12000);
|
||||
remoteMessage.setType('something');
|
||||
remoteMessage.setData({
|
||||
object: { foo: 'bar ' },
|
||||
array: [1, 2, 3, 4, 5],
|
||||
string: 'hello',
|
||||
number: 123456,
|
||||
});
|
||||
|
||||
firebase.native.messaging().send(remoteMessage);
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('it should create/remove onTokenRefresh listeners', () => {
|
||||
const cb = () => {
|
||||
};
|
||||
try {
|
||||
const listener = firebase.native.messaging().onTokenRefresh(cb);
|
||||
listener.remove();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('it should subscribe/unsubscribe to topics', () => {
|
||||
firebase.native.messaging().subscribeToTopic('foobar');
|
||||
firebase.native.messaging().unsubscribeFromTopic('foobar');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('it should show a notification', () => {
|
||||
firebase.native.messaging().createLocalNotification({
|
||||
title: 'Hello',
|
||||
body: 'My Notification Message',
|
||||
big_text: "Is it me you're looking for?",
|
||||
sub_text: 'nope',
|
||||
show_in_foreground: true,
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default messagingTests;
|
||||
10
tests/src/tests/storage/index.js
Normal file
10
tests/src/tests/storage/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import firebase from '../../firebase';
|
||||
import TestSuite from '../../../lib/TestSuite';
|
||||
import storageTests from './storageTests';
|
||||
|
||||
const suite = new TestSuite('Storage', 'Upload/Download storage tests', firebase);
|
||||
|
||||
suite.addTests(storageTests);
|
||||
|
||||
export default suite;
|
||||
|
||||
90
tests/src/tests/storage/storageTests.js
Normal file
90
tests/src/tests/storage/storageTests.js
Normal file
@@ -0,0 +1,90 @@
|
||||
function storageTests({ xdescribe, it, firebase, tryCatch }) {
|
||||
xdescribe('downloadFile()', () => {
|
||||
it('it should error on download file if permission denied', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch(() => {
|
||||
reject(new Error('No permission denied error'));
|
||||
}, reject);
|
||||
|
||||
const failureCb = tryCatch((error) => {
|
||||
error.code.should.equal('storage/unauthorized');
|
||||
error.message.includes('not authorized').should.be.true();
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.storage().ref('/not.jpg').downloadFile(`${firebase.native.storage.Native.DOCUMENT_DIRECTORY_PATH}/not.jpg`).then(successCb).catch(failureCb);
|
||||
});
|
||||
});
|
||||
|
||||
it('it should download a file', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((meta) => {
|
||||
meta.state.should.eql(firebase.native.storage.TaskState.SUCCESS);
|
||||
meta.bytesTransferred.should.eql(meta.totalBytes);
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
const failureCb = tryCatch((error) => {
|
||||
reject(error);
|
||||
}, reject);
|
||||
|
||||
firebase.native.storage().ref('/ok.jpeg').downloadFile(`${firebase.native.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`).then(successCb).catch(failureCb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('putFile()', () => {
|
||||
it('it should error on upload if permission denied', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch(() => {
|
||||
reject(new Error('No permission denied error'));
|
||||
}, reject);
|
||||
|
||||
const failureCb = tryCatch((error) => {
|
||||
error.code.should.equal('storage/unauthorized');
|
||||
error.message.includes('not authorized').should.be.true();
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
firebase.native.storage().ref('/uploadNope.jpeg').putFile(`${firebase.native.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`).then(successCb).catch(failureCb);
|
||||
});
|
||||
});
|
||||
|
||||
it('it should upload a file', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const successCb = tryCatch((uploadTaskSnapshot) => {
|
||||
uploadTaskSnapshot.state.should.eql(firebase.native.storage.TaskState.SUCCESS);
|
||||
uploadTaskSnapshot.bytesTransferred.should.eql(uploadTaskSnapshot.totalBytes);
|
||||
uploadTaskSnapshot.metadata.should.be.an.Object();
|
||||
uploadTaskSnapshot.downloadUrl.should.be.a.String();
|
||||
resolve();
|
||||
}, reject);
|
||||
|
||||
const failureCb = tryCatch((error) => {
|
||||
reject(error);
|
||||
}, reject);
|
||||
|
||||
firebase.native.storage().ref('/uploadOk.jpeg').putFile(`${firebase.native.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`).then(successCb).catch(failureCb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('on()', () => {
|
||||
it('should listen to upload state', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const path = `${firebase.native.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`;
|
||||
const ref = firebase.native.storage().ref('/uploadOk.jpeg');
|
||||
const unsubscribe = ref.putFile(path).on(firebase.native.storage.TaskEvent.STATE_CHANGED, tryCatch((snapshot) => {
|
||||
if (snapshot.state === firebase.native.storage.TaskState.SUCCESS) {
|
||||
resolve();
|
||||
}
|
||||
}, reject), tryCatch((error) => {
|
||||
unsubscribe();
|
||||
reject(error);
|
||||
}, reject));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default storageTests;
|
||||
25
tests/src/tests/support/DatabaseContents.js
Normal file
25
tests/src/tests/support/DatabaseContents.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export default {
|
||||
DEFAULT: {
|
||||
array: [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
],
|
||||
boolean: true,
|
||||
string: 'foobar',
|
||||
number: 123567890,
|
||||
object: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
|
||||
NEW: {
|
||||
array: [
|
||||
9, 8, 7, 6, 5, 4,
|
||||
],
|
||||
boolean: false,
|
||||
string: 'baz',
|
||||
number: 84564564,
|
||||
object: {
|
||||
foo: 'baz',
|
||||
},
|
||||
},
|
||||
};
|
||||
10
tests/src/tests/support/databaseTypeMap.js
Normal file
10
tests/src/tests/support/databaseTypeMap.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import DatabaseContents from './DatabaseContents';
|
||||
|
||||
const databaseTypeMap =
|
||||
Object.keys(DatabaseContents.DEFAULT).reduce((dataTypeMap, dataType) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
dataTypeMap[`tests/types/${dataType}`] = DatabaseContents.DEFAULT[dataType];
|
||||
return dataTypeMap;
|
||||
}, {});
|
||||
|
||||
export default databaseTypeMap;
|
||||
Reference in New Issue
Block a user