mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-04-24 04:24:52 +08:00
Merge branch 'master' of https://github.com/invertase/react-native-firebase
# Conflicts: # tests/ios/Podfile.lock
This commit is contained in:
2
tests/.gitignore
vendored
Normal file
2
tests/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
__tests__/build/
|
||||
ios/build/
|
||||
@@ -103,6 +103,84 @@ function pendingTestTests({ it: _it, describe: _describe }) {
|
||||
otherTest.should.be.called();
|
||||
});
|
||||
});
|
||||
|
||||
_describe('when an outer context is focused', () => {
|
||||
_it('a pending test will still not run', async () => {
|
||||
const pendingTest = sinon.spy();
|
||||
const otherTest = sinon.spy();
|
||||
const unfocusedTest = sinon.spy();
|
||||
|
||||
const testSuite = new TestSuite('', '', {});
|
||||
|
||||
testSuite.addTests(({ fdescribe, it, xit }) => {
|
||||
fdescribe('', () => {
|
||||
xit('', pendingTest);
|
||||
|
||||
it('', otherTest);
|
||||
});
|
||||
|
||||
it('', unfocusedTest);
|
||||
});
|
||||
|
||||
testSuite.setStore({
|
||||
getState: () => { return {}; },
|
||||
});
|
||||
|
||||
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
|
||||
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
|
||||
memo.push(testId);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
await testSuite.run(testIdsToRun);
|
||||
|
||||
pendingTest.should.not.be.called();
|
||||
otherTest.should.be.called();
|
||||
unfocusedTest.should.not.be.called();
|
||||
});
|
||||
});
|
||||
|
||||
_describe('when an outer context is focused', () => {
|
||||
_it('a pending context will still not run', async () => {
|
||||
const pendingTest = sinon.spy();
|
||||
const otherTest = sinon.spy();
|
||||
const unfocusedTest = sinon.spy();
|
||||
|
||||
const testSuite = new TestSuite('', '', {});
|
||||
|
||||
testSuite.addTests(({ fdescribe, it, xdescribe }) => {
|
||||
fdescribe('', () => {
|
||||
xdescribe('', () => {
|
||||
it('', pendingTest);
|
||||
});
|
||||
|
||||
it('', otherTest);
|
||||
});
|
||||
|
||||
it('', unfocusedTest);
|
||||
});
|
||||
|
||||
testSuite.setStore({
|
||||
getState: () => { return {}; },
|
||||
});
|
||||
|
||||
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
|
||||
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
|
||||
memo.push(testId);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
await testSuite.run(testIdsToRun);
|
||||
|
||||
pendingTest.should.not.be.called();
|
||||
otherTest.should.be.called();
|
||||
unfocusedTest.should.not.be.called();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default pendingTestTests;
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
PODS:
|
||||
- Firebase/Analytics (3.14.0):
|
||||
- Firebase/Core
|
||||
- Firebase/AppIndexing (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseAppIndexing (= 1.2.0)
|
||||
- Firebase/Auth (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseAuth (= 3.1.1)
|
||||
- Firebase/Core (3.14.0):
|
||||
- FirebaseAnalytics (= 3.7.0)
|
||||
- FirebaseCore (= 3.5.1)
|
||||
- Firebase/Crash (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseCrash (= 1.1.6)
|
||||
- Firebase/Database (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseDatabase (= 3.1.2)
|
||||
- Firebase/DynamicLinks (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseDynamicLinks (= 1.3.3)
|
||||
- Firebase/Messaging (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseMessaging (= 1.2.2)
|
||||
- Firebase/RemoteConfig (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseRemoteConfig (= 1.3.4)
|
||||
- Firebase/Storage (3.14.0):
|
||||
- Firebase/Core
|
||||
- FirebaseStorage (= 1.1.0)
|
||||
- FirebaseAnalytics (3.7.0):
|
||||
- FirebaseCore (~> 3.5)
|
||||
- FirebaseInstanceID (~> 1.0)
|
||||
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
|
||||
- FirebaseAppIndexing (1.2.0)
|
||||
- FirebaseAuth (3.1.1):
|
||||
- FirebaseAnalytics (~> 3.7)
|
||||
- GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- FirebaseCore (3.5.1):
|
||||
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
|
||||
- FirebaseCrash (1.1.6):
|
||||
- FirebaseAnalytics (~> 3.7)
|
||||
- FirebaseInstanceID (~> 1.0)
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
|
||||
- Protobuf (~> 3.1)
|
||||
- FirebaseDatabase (3.1.2):
|
||||
- FirebaseAnalytics (~> 3.7)
|
||||
- FirebaseDynamicLinks (1.3.3):
|
||||
- FirebaseAnalytics (~> 3.7)
|
||||
- FirebaseInstanceID (1.0.9)
|
||||
- FirebaseMessaging (1.2.2):
|
||||
- FirebaseAnalytics (~> 3.7)
|
||||
- FirebaseInstanceID (~> 1.0)
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- Protobuf (~> 3.1)
|
||||
- FirebaseRemoteConfig (1.3.4):
|
||||
- FirebaseAnalytics (~> 3.7)
|
||||
- FirebaseInstanceID (~> 1.0)
|
||||
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
|
||||
- Protobuf (~> 3.1)
|
||||
- FirebaseStorage (1.1.0):
|
||||
- FirebaseAnalytics (~> 3.7)
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- GoogleToolboxForMac/DebugUtils (2.1.1):
|
||||
- GoogleToolboxForMac/Defines (= 2.1.1)
|
||||
- GoogleToolboxForMac/Defines (2.1.1)
|
||||
- GoogleToolboxForMac/Logger (2.1.1):
|
||||
- GoogleToolboxForMac/Defines (= 2.1.1)
|
||||
- GoogleToolboxForMac/NSData+zlib (2.1.1):
|
||||
- GoogleToolboxForMac/Defines (= 2.1.1)
|
||||
- GoogleToolboxForMac/NSDictionary+URLArguments (2.1.1):
|
||||
- GoogleToolboxForMac/DebugUtils (= 2.1.1)
|
||||
- GoogleToolboxForMac/Defines (= 2.1.1)
|
||||
- GoogleToolboxForMac/NSString+URLArguments (= 2.1.1)
|
||||
- GoogleToolboxForMac/NSString+URLArguments (2.1.1)
|
||||
- GTMSessionFetcher/Core (1.1.8)
|
||||
- Protobuf (3.2.0)
|
||||
- React (0.44.0):
|
||||
- React/Core (= 0.44.0)
|
||||
- React/Core (0.44.0):
|
||||
- React/cxxreact
|
||||
- Yoga (= 0.44.0.React)
|
||||
- React/cxxreact (0.44.0):
|
||||
- React/jschelpers
|
||||
- React/jschelpers (0.44.0)
|
||||
- RNFirebase (1.0.2)
|
||||
- Yoga (0.44.0.React)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Firebase/Analytics
|
||||
- Firebase/AppIndexing
|
||||
- Firebase/Auth
|
||||
- Firebase/Core
|
||||
- Firebase/Crash
|
||||
- Firebase/Database
|
||||
- Firebase/DynamicLinks
|
||||
- Firebase/Messaging
|
||||
- Firebase/RemoteConfig
|
||||
- Firebase/Storage
|
||||
- React (from `../node_modules/react-native`)
|
||||
- RNFirebase (from `./../../`)
|
||||
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
React:
|
||||
:path: ../node_modules/react-native
|
||||
RNFirebase:
|
||||
:path: ./../../
|
||||
Yoga:
|
||||
:path: ../node_modules/react-native/ReactCommon/yoga
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Firebase: 85a581fb04e44f63ae9f4fbc8d6dabf4a4c18653
|
||||
FirebaseAnalytics: 0d1b7d81d5021155be37702a94ba1ec16d45365d
|
||||
FirebaseAppIndexing: d0fa52ce0ad13f4b5b2f09e4b47fb0dc2213f4e9
|
||||
FirebaseAuth: cc8a1824170adbd351edb7f994490a3fb5c18be6
|
||||
FirebaseCore: 225d40532489835a034b8f4e2c9c87fbf4f615a2
|
||||
FirebaseCrash: db4c05d9c75baa050744d31b36357c8f1efba481
|
||||
FirebaseDatabase: 05c96d7b43a7368dc91c07791adb49683e1738d1
|
||||
FirebaseDynamicLinks: f0d025dd29a1d70418c003344813b67ab748ffb9
|
||||
FirebaseInstanceID: 2d0518b1378fe9d685ef40cbdd63d2fdc1125339
|
||||
FirebaseMessaging: df8267f378580a24174ce7861233aa11d5c90109
|
||||
FirebaseRemoteConfig: af3003f4e8daa2bd1d5cf90d3cccc1fe224f8ed9
|
||||
FirebaseStorage: a5c55b23741a49a72af8f30f95b3bb5ddbeda12d
|
||||
GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0
|
||||
GTMSessionFetcher: 6f8d8b28b7e345549ac471071608170b31cb4977
|
||||
Protobuf: 745f59e122e5de98d4d7ef291e264a0eef80f58e
|
||||
React: d2077cc20245ccdc8bfe1fdc002f2003318ae8d8
|
||||
RNFirebase: 9b9ab212d14243db38bee3637029f1752c2a349f
|
||||
Yoga: a92a5d8e128905bf9f29c82f870192a6e873dd98
|
||||
|
||||
PODFILE CHECKSUM: 62ff31b87feae064d9ea6ae4b9c4375b48a6153e
|
||||
|
||||
COCOAPODS: 1.2.0
|
||||
@@ -286,8 +286,7 @@ class TestRun {
|
||||
suiteId: this.testSuite.id,
|
||||
status: RunStatus.ERR,
|
||||
time: Date.now() - this.runStartTime,
|
||||
message: `Test suite failed: ${error.message}`,
|
||||
stackTrace: error.stack,
|
||||
message: `Test suite failed: ${error.message}`
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -306,7 +305,7 @@ class TestRun {
|
||||
}
|
||||
|
||||
async _safelyRunFunction(func, timeOutDuration, description) {
|
||||
const syncResultOrPromise = tryCatcher(func);
|
||||
const syncResultOrPromise = captureThrownErrors(func);
|
||||
|
||||
if (syncResultOrPromise.error) {
|
||||
// Synchronous Error
|
||||
@@ -314,49 +313,59 @@ class TestRun {
|
||||
}
|
||||
|
||||
// Asynchronous Error
|
||||
return promiseToCallBack(syncResultOrPromise.value, timeOutDuration, description);
|
||||
return capturePromiseErrors(syncResultOrPromise.result, timeOutDuration, description);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Try catch to object
|
||||
* @returns {{}}
|
||||
* Call a function and capture any errors that are immediately thrown.
|
||||
* @returns {Object} Object containing result of executing the function, or the error
|
||||
* message that was captured
|
||||
* @private
|
||||
*/
|
||||
|
||||
function tryCatcher(func) {
|
||||
function captureThrownErrors(func) {
|
||||
const result = {};
|
||||
|
||||
try {
|
||||
result.value = func();
|
||||
} catch (e) {
|
||||
result.error = e;
|
||||
result.result = func();
|
||||
} catch (error) {
|
||||
result.error = error;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a promise callback-able to trap errors
|
||||
* @param promise
|
||||
* Wraps a promise so that if it's rejected or an error is thrown while it's being
|
||||
* evaluated, it's captured and thrown no further
|
||||
* @param {*} target - Target to wrap. If a thenable object, it's wrapped so if it's
|
||||
* rejected or an error is thrown, it will be captured. If a non-thenable object,
|
||||
* wrapped in resolved promise and returned.
|
||||
* @param {Number} timeoutDuration - Number of milliseconds the promise is allowed
|
||||
* to pend before it's considered timed out
|
||||
* @param {String} description - Description of the context the promises is defined
|
||||
* in, used for reporting where a timeout occurred in the resulting error message.
|
||||
* @private
|
||||
*/
|
||||
|
||||
function promiseToCallBack(promise, timeoutDuration, description) {
|
||||
function capturePromiseErrors(target, timeoutDuration, description) {
|
||||
let returnValue = null;
|
||||
|
||||
try {
|
||||
returnValue = Promise.resolve(promise)
|
||||
returnValue = Promise.resolve(target)
|
||||
.then(() => {
|
||||
return null;
|
||||
}, (error) => {
|
||||
return Promise.resolve(error);
|
||||
})
|
||||
.timeout(timeoutDuration, `${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`)
|
||||
.catch((error) => {
|
||||
return Promise.resolve(error);
|
||||
});
|
||||
})
|
||||
.timeout(timeoutDuration,
|
||||
`${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`
|
||||
);
|
||||
} catch (error) {
|
||||
returnValue = Promise.resolve(error);
|
||||
}
|
||||
|
||||
@@ -111,19 +111,19 @@ class TestSuite {
|
||||
*/
|
||||
async run(testIds = undefined) {
|
||||
const testsToRun = (() => {
|
||||
if (testIds) {
|
||||
return testIds.map((id) => {
|
||||
const test = this.testDefinitions.tests[id];
|
||||
return (testIds || Object.keys(this.testDefinitions.tests)).reduce((memo, id) => {
|
||||
const test = this.testDefinitions.tests[id];
|
||||
|
||||
if (!test) {
|
||||
throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`);
|
||||
}
|
||||
if (!test) {
|
||||
throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`);
|
||||
}
|
||||
|
||||
return test;
|
||||
});
|
||||
}
|
||||
if (!this.testDefinitions.pendingTestIds[id]) {
|
||||
memo.push(test);
|
||||
}
|
||||
|
||||
return Object.values(this.testDefinitions.tests);
|
||||
return memo;
|
||||
}, []);
|
||||
})();
|
||||
|
||||
const testRun = new TestRun(this, testsToRun.reverse(), this.testDefinitions);
|
||||
|
||||
@@ -14,13 +14,14 @@ export function setSuiteStatus({ suiteId, status, time, message, progress }) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setTestStatus({ testId, status, time = 0, message = null }) {
|
||||
export function setTestStatus({ testId, status, stackTrace, time = 0, message = null }) {
|
||||
return {
|
||||
type: TEST_SET_STATUS,
|
||||
testId,
|
||||
|
||||
status,
|
||||
message,
|
||||
stackTrace,
|
||||
time,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ function testsReducers(state = initState.tests, action: Object): State {
|
||||
flattened[`${action.testId}.status`] = action.status;
|
||||
flattened[`${action.testId}.message`] = action.message;
|
||||
flattened[`${action.testId}.time`] = action.time;
|
||||
flattened[`${action.testId}.stackTrace`] = action.stackTrace;
|
||||
|
||||
return unflatten(flattened);
|
||||
}
|
||||
|
||||
@@ -51,35 +51,28 @@ class Test extends React.Component {
|
||||
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;
|
||||
const { test: { stackTrace, description, func, status, time }, testContextName } = 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}>
|
||||
<View >
|
||||
<ScrollView style={styles.sectionContainer}>
|
||||
<Text style={styles.heading}>{testContextName}</Text>
|
||||
<Text style={styles.description}>{description}</Text>
|
||||
</ScrollView>
|
||||
<ScrollView style={styles.sectionContainer}>
|
||||
<Text style={styles.heading}>Test Error</Text>
|
||||
<Text style={styles.description}>
|
||||
<Text>{stackTrace || 'None.'}</Text>
|
||||
</Text>
|
||||
</ScrollView>
|
||||
<Text style={styles.heading}>
|
||||
Test Code Preview
|
||||
</Text>
|
||||
<ScrollView style={styles.sectionContainer}>
|
||||
<Text style={styles.description}>
|
||||
{beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
@@ -93,10 +86,13 @@ Test.propTypes = {
|
||||
test: PropTypes.shape({
|
||||
status: PropTypes.string,
|
||||
time: PropTypes.number,
|
||||
message: PropTypes.string,
|
||||
func: PropTypes.function,
|
||||
stackTrace: PropTypes.function,
|
||||
description: PropTypes.string,
|
||||
}).isRequired,
|
||||
|
||||
testContextName: PropTypes.string,
|
||||
|
||||
navigation: PropTypes.shape({
|
||||
setParams: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
@@ -107,27 +103,32 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
content: {},
|
||||
code: {
|
||||
backgroundColor: '#3F373A',
|
||||
color: '#c3c3c3',
|
||||
padding: 5,
|
||||
fontSize: 12,
|
||||
sectionContainer: {
|
||||
minHeight: 100,
|
||||
},
|
||||
codeHeader: {
|
||||
fontWeight: '600',
|
||||
fontSize: 18,
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
heading: {
|
||||
padding: 5,
|
||||
backgroundColor: '#0288d1',
|
||||
fontWeight: '600',
|
||||
color: '#ffffff',
|
||||
fontSize: 16,
|
||||
},
|
||||
description: {
|
||||
padding: 5,
|
||||
fontSize: 14,
|
||||
},
|
||||
});
|
||||
|
||||
function select({ tests }, { navigation: { state: { params: { testId } } } }) {
|
||||
function select({ tests, testContexts }, { navigation: { state: { params: { testId } } } }) {
|
||||
const test = tests[testId];
|
||||
let testContext = testContexts[test.testContextId];
|
||||
|
||||
while(testContext.parentContextId && testContexts[testContext.parentContextId].parentContextId) {
|
||||
testContext = testContexts[testContext.parentContextId];
|
||||
}
|
||||
return {
|
||||
test,
|
||||
testContextName: testContext.name,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import onTests from './onTests';
|
||||
import onTests from './on/onTests';
|
||||
import onValueTests from './on/onValueTests';
|
||||
import offTests from './offTests';
|
||||
import onceTests from './onceTests';
|
||||
import setTests from './setTests';
|
||||
@@ -19,8 +20,8 @@ import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
const testGroups = [
|
||||
factoryTests, keyTests, parentTests, childTests, rootTests,
|
||||
pushTests, onTests, offTests, onceTests, updateTests, removeTests, setTests,
|
||||
transactionTests, queryTests, refTests, isEqualTests,
|
||||
pushTests, onTests, onValueTests, offTests, onceTests, updateTests,
|
||||
removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests,
|
||||
];
|
||||
|
||||
function registerTestSuite(testSuite) {
|
||||
|
||||
52
tests/src/tests/database/ref/on/onTests.js
Normal file
52
tests/src/tests/database/ref/on/onTests.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'should-sinon';
|
||||
|
||||
function onTests({ describe, it, firebase, context }) {
|
||||
describe('ref().on()', () => {
|
||||
// Observed Web API Behaviour
|
||||
context('when no eventName is provided', () => {
|
||||
it('then raises an error', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
(() => { ref.on(); }).should.throw('Error: Query on failed: Was called with 0 arguments. Expects at least 2');
|
||||
});
|
||||
});
|
||||
|
||||
// Observed Web API Behaviour
|
||||
context('when no callback function is provided', () => {
|
||||
it('then raises an error', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
(() => { ref.on('value'); }).should.throw('Query.on failed: Was called with 1 argument. Expects at least 2.');
|
||||
});
|
||||
});
|
||||
|
||||
// Observed Web API Behaviour
|
||||
context('when an invalid eventName is provided', () => {
|
||||
it('then raises an error', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
(() => { ref.on('invalid', () => {}); }).should.throw('Query.on failed: First argument must be a valid event type: "value", "child_added", "child_removed", "child_changed", or "child_moved".');
|
||||
});
|
||||
});
|
||||
|
||||
// Observed Web API Behaviour
|
||||
context('when an invalid success callback function is provided', () => {
|
||||
it('then raises an error', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
(() => { ref.on('value', 1); }).should.throw('Query.on failed: Second argument must be a valid function.');
|
||||
});
|
||||
});
|
||||
|
||||
// Observed Web API Behaviour
|
||||
context('when an invalid failure callback function is provided', () => {
|
||||
it('then raises an error', () => {
|
||||
const ref = firebase.native.database().ref('tests/types/number');
|
||||
|
||||
(() => { ref.on('value', () => {}, null); }).should.throw('Query.on failed: third argument must either be a cancel callback or a context object.');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default onTests;
|
||||
409
tests/src/tests/database/ref/on/onValueTests.js
Normal file
409
tests/src/tests/database/ref/on/onValueTests.js
Normal file
@@ -0,0 +1,409 @@
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import 'should-sinon';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import DatabaseContents from '../../../support/DatabaseContents';
|
||||
|
||||
/**
|
||||
* On Android, some data types result in callbacks that get called twice every time
|
||||
* they are updated. This appears to be behaviour coming from the Android Firebase
|
||||
* library itself.
|
||||
*
|
||||
* See https://github.com/invertase/react-native-firebase/issues/92 for details
|
||||
*/
|
||||
const DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS = [
|
||||
'array',
|
||||
'number'
|
||||
];
|
||||
|
||||
function onTests({ fdescribe, context, it, firebase, tryCatch }) {
|
||||
fdescribe('ref().on(\'value\')', () => {
|
||||
// Documented Web API Behaviour
|
||||
it('returns the success callback', () => {
|
||||
// Setup
|
||||
|
||||
const successCallback = sinon.spy();
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
|
||||
// Assertions
|
||||
|
||||
ref.on('value', successCallback).should.eql(successCallback);
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
});
|
||||
|
||||
// Documented Web API Behaviour
|
||||
it('calls callback with null if there is no data at ref', async () => {
|
||||
// Setup
|
||||
const ref = firebase.native.database().ref('tests/types/invalid');
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', (snapshot) => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledWith(null);
|
||||
|
||||
await ref.set(1);
|
||||
|
||||
callback.should.be.calledWith(1);
|
||||
|
||||
// Teardown
|
||||
ref.off();
|
||||
await ref.set(null);
|
||||
});
|
||||
|
||||
// Documented Web API Behaviour
|
||||
it('calls callback with the initial data and then 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);
|
||||
|
||||
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
|
||||
callback.should.be.calledThrice();
|
||||
} else {
|
||||
callback.should.be.calledTwice();
|
||||
}
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
await ref.set(currentDataValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls callback when children of the ref change', async () => {
|
||||
const ref = firebase.native.database().ref('tests/types/object');
|
||||
const currentDataValue = DatabaseContents.DEFAULT.object;
|
||||
|
||||
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.string;
|
||||
const childRef = firebase.native.database().ref('tests/types/object/foo2');
|
||||
await childRef.set(newDataValue);
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledWith({
|
||||
...currentDataValue,
|
||||
foo2: newDataValue,
|
||||
});
|
||||
|
||||
callback.should.be.calledTwice();
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
await ref.set(currentDataValue);
|
||||
});
|
||||
|
||||
it('calls callback when child of the ref is added', async () => {
|
||||
const ref = firebase.native.database().ref('tests/types/array');
|
||||
const currentDataValue = DatabaseContents.DEFAULT.array;
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', (snapshot) => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(currentDataValue);
|
||||
|
||||
const newElementRef = await ref.push(37);
|
||||
|
||||
const arrayAsObject = currentDataValue.reduce((memo, element, index) => {
|
||||
memo[index] = element;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
// Assertions
|
||||
callback.should.be.calledWith({
|
||||
...arrayAsObject,
|
||||
[newElementRef.key]: 37,
|
||||
});
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
callback.should.be.calledThrice();
|
||||
} else {
|
||||
callback.should.be.calledTwice();
|
||||
}
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
await ref.set(currentDataValue);
|
||||
});
|
||||
|
||||
it('doesn\'t call callback when the ref is updated with the same value', async () => {
|
||||
const ref = firebase.native.database().ref('tests/types/object');
|
||||
const currentDataValue = DatabaseContents.DEFAULT.object;
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', (snapshot) => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(currentDataValue);
|
||||
|
||||
await ref.set(currentDataValue);
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledOnce(); // Callback is not called again
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
});
|
||||
|
||||
// Documented Web API Behaviour
|
||||
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);
|
||||
callbackA.should.be.calledOnce();
|
||||
|
||||
callbackB.should.be.calledWith(currentDataValue);
|
||||
callbackB.should.be.calledOnce();
|
||||
|
||||
const newDataValue = DatabaseContents.NEW[dataRef];
|
||||
await ref.set(newDataValue);
|
||||
|
||||
callbackA.should.be.calledWith(newDataValue);
|
||||
callbackB.should.be.calledWith(newDataValue);
|
||||
|
||||
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
|
||||
callbackA.should.be.calledThrice();
|
||||
callbackB.should.be.calledThrice();
|
||||
} else {
|
||||
callbackA.should.be.calledTwice();
|
||||
callbackB.should.be.calledTwice();
|
||||
}
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
});
|
||||
});
|
||||
|
||||
context('when no failure callback is provided', () => {
|
||||
it('then does not call the callback for a ref to un-permitted location', () => {
|
||||
const invalidRef = firebase.native.database().ref('nope');
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
invalidRef.on('value', callback);
|
||||
|
||||
/**
|
||||
* As we are testing that a callback is "never" called, we just wait for
|
||||
* a reasonable time before giving up.
|
||||
*/
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
callback.should.not.be.called();
|
||||
invalidRef.off();
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
// Documented Web API Behaviour
|
||||
it('then calls callback bound to the specified context with the initial data and then 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 context = {
|
||||
callCount: 0,
|
||||
};
|
||||
|
||||
// Test
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', function(snapshot) {
|
||||
this.value = snapshot.val();
|
||||
this.callCount += 1;
|
||||
resolve();
|
||||
}, context);
|
||||
});
|
||||
|
||||
context.value.should.eql(currentDataValue);
|
||||
context.callCount.should.eql(1);
|
||||
|
||||
const newDataValue = DatabaseContents.NEW[dataRef];
|
||||
await ref.set(newDataValue);
|
||||
|
||||
// Assertions
|
||||
|
||||
context.value.should.eql(newDataValue);
|
||||
|
||||
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
|
||||
context.callCount.should.eql(3);
|
||||
} else {
|
||||
context.callCount.should.eql(2);
|
||||
}
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
await ref.set(currentDataValue);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Observed Web API Behaviour
|
||||
context('when a failure callback is provided', () => {
|
||||
it('then calls only the failure callback for a ref to un-permitted location', () => {
|
||||
const invalidRef = firebase.native.database().ref('nope');
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
invalidRef.on('value', callback, tryCatch((error) => {
|
||||
error.message.should.eql(
|
||||
'permission_denied at /nope: Client doesn\'t have permission to access the desired data.'
|
||||
);
|
||||
error.name.should.eql('Error');
|
||||
|
||||
callback.should.not.be.called();
|
||||
|
||||
invalidRef.off();
|
||||
resolve();
|
||||
}, reject));
|
||||
});
|
||||
});
|
||||
|
||||
// Documented Web API Behaviour
|
||||
it('then calls callback bound to the specified context with the initial data and then 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 context = {
|
||||
callCount: 0,
|
||||
};
|
||||
|
||||
const failureCallback = sinon.spy();
|
||||
|
||||
// Test
|
||||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', function(snapshot) {
|
||||
this.value = snapshot.val();
|
||||
this.callCount += 1;
|
||||
resolve();
|
||||
}, failureCallback, context);
|
||||
});
|
||||
|
||||
failureCallback.should.not.be.called();
|
||||
context.value.should.eql(currentDataValue);
|
||||
context.callCount.should.eql(1);
|
||||
|
||||
const newDataValue = DatabaseContents.NEW[dataRef];
|
||||
await ref.set(newDataValue);
|
||||
|
||||
// Assertions
|
||||
|
||||
context.value.should.eql(newDataValue);
|
||||
|
||||
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
|
||||
context.callCount.should.eql(3);
|
||||
} else {
|
||||
context.callCount.should.eql(2);
|
||||
}
|
||||
|
||||
// Tear down
|
||||
|
||||
ref.off();
|
||||
await ref.set(currentDataValue);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default onTests;
|
||||
@@ -1,125 +0,0 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user