add tests project

This commit is contained in:
Salakar 2018-08-25 13:40:25 +01:00
parent 7b744cb958
commit d5eab4d392
55 changed files with 11165 additions and 0 deletions

32
tests/.babelrc Normal file
View File

@ -0,0 +1,32 @@
{
"presets": [
"react-native"
],
"env": {
"development": {
"plugins": [
[
"module-resolver",
{
"alias": {
"react-native-firebase": ".."
},
"extensions": [".js", ".ios.js", ".android.js"],
"stripExtensions": [".js"]
}
],
[
"istanbul",
{
"useInlineSourceMaps": true,
"instrument": true,
"relativePath": false,
"include": [
"**/dist/**"
]
}
]
]
}
}
}

6
tests/.buckconfig Executable file
View File

@ -0,0 +1,6 @@
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2

10
tests/.editorconfig Normal file
View File

@ -0,0 +1,10 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

39
tests/.eslintrc Normal file
View File

@ -0,0 +1,39 @@
{
"extends": [
"airbnb",
"prettier",
"prettier/flowtype",
"prettier/react"
],
"parser": "babel-eslint",
"plugins": [
"flowtype",
"prettier"
],
"env": {
"es6": true,
"jasmine": true
},
"rules": {
"prettier/prettier": ["error", {
"trailingComma": "es5",
"singleQuote": true
}],
"react/forbid-prop-types": "warn",
"react/jsx-filename-extension": [
"off", { "extensions": [".js", ".jsx"] }
],
"class-methods-use-this": 0,
"no-console": 0,
"no-plusplus": 0,
"no-undef": 0,
"no-underscore-dangle": "off",
"no-use-before-define": 0
},
"globals": {
"__DEV__": true,
"window": true
}
}

5
tests/.firebaserc Normal file
View File

@ -0,0 +1,5 @@
{
"projects": {
"default": "rnfirebase-b9ad4"
}
}

97
tests/.flowconfig Executable file
View File

@ -0,0 +1,97 @@
[ignore]
# We fork some components by platform.
.*/*.web.js
.*/*.android.js
# Some modules have their own node_modules with overlap
.*/node_modules/node-haste/.*
# Ugh
.*/node_modules/babel.*
.*/node_modules/babylon.*
.*/node_modules/invariant.*
# Ignore react and fbjs where there are overlaps, but don't ignore
# anything that react-native relies on
.*/node_modules/fbjs/lib/Map.js
.*/node_modules/fbjs/lib/ErrorUtils.js
# Flow has a built-in definition for the 'react' module which we prefer to use
# over the currently-untyped source
.*/node_modules/react/react.js
.*/node_modules/react/lib/React.js
.*/node_modules/react/lib/ReactDOM.js
.*/__mocks__/.*
.*/__tests__/.*
.*/commoner/test/source/widget/share.js
# Ignore commoner tests
.*/node_modules/commoner/test/.*
# See https://github.com/facebook/flow/issues/442
.*/react-tools/node_modules/commoner/lib/reader.js
# Ignore jest
.*/node_modules/jest-cli/.*
# Ignore Website
.*/website/.*
# Ignore generators
.*/local-cli/generator.*
# Ignore BUCK generated folders
.*\.buckd/
# Ignore RNPM
.*/local-cli/rnpm/.*
.*/node_modules/is-my-json-valid/test/.*\.json
.*/node_modules/iconv-lite/encodings/tables/.*\.json
.*/node_modules/y18n/test/.*\.json
.*/node_modules/spdx-license-ids/spdx-license-ids.json
.*/node_modules/spdx-exceptions/index.json
.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json
.*/node_modules/resolve/lib/core.json
.*/node_modules/jsonparse/samplejson/.*\.json
.*/node_modules/json5/test/.*\.json
.*/node_modules/ua-parser-js/test/.*\.json
.*/node_modules/builtin-modules/builtin-modules.json
.*/node_modules/binary-extensions/binary-extensions.json
.*/node_modules/url-regex/tlds.json
.*/node_modules/joi/.*\.json
.*/node_modules/isemail/.*\.json
.*/node_modules/tr46/.*\.json
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow
flow/
[options]
module.system=haste
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
munge_underscores=true
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
[version]
^0.25.0

41
tests/.gitignore vendored Executable file
View File

@ -0,0 +1,41 @@
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IJ
#
.idea
.gradle
local.properties
# node.js
#
node_modules/
npm-debug.log
# BUCK
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore
.vscode

1
tests/.watchmanconfig Executable file
View File

@ -0,0 +1 @@
{}

31
tests/app.js Executable file
View File

@ -0,0 +1,31 @@
/* eslint-disable import/extensions,import/no-unresolved */
import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';
import bridge from 'jet/platform/react-native';
import firebase from 'react-native-firebase';
require('sinon');
require('should-sinon');
require('should');
class Root extends Component {
constructor(props) {
super(props);
this.state = {
message: '',
};
bridge.setBridgeProperty('module', firebase);
}
render() {
return (
<View>
<Text testID="messageText">{this.state.message}</Text>
</View>
);
}
}
AppRegistry.registerComponent('testing', () => Root);

View File

@ -0,0 +1,193 @@
describe('analytics()', () => {
describe('logEvent()', () => {
it('errors on using a reserved name', () => {
try {
firebase.analytics().logEvent('session_start');
} catch (e) {
e.message.should.containEql('reserved event');
}
});
it('errors if name not alphanumeric', () => {
try {
firebase.analytics().logEvent('!@£$%^&*');
} catch (e) {
e.message.should.containEql('is invalid');
}
});
it('errors if more than 25 params provided', () => {
try {
firebase.analytics().logEvent('fooby', {
1: 1,
2: 2,
3: 3,
4: 4,
5: 5,
6: 6,
7: 7,
8: 8,
9: 9,
10: 10,
11: 11,
12: 12,
13: 13,
14: 14,
15: 15,
16: 16,
17: 17,
18: 18,
19: 19,
20: 20,
21: 21,
22: 22,
23: 23,
24: 24,
25: 25,
26: 26,
});
} catch (e) {
e.message.should.containEql('Maximum number of parameters exceeded');
}
});
it('errors if name is not a string', () => {
(() => {
firebase.analytics().logEvent(123456);
}).should.throw(
`analytics.logEvent(): First argument 'name' is required and must be a string value.`
);
});
it('errors if params is not an object', () => {
(() => {
firebase.analytics().logEvent('test_event', 'this should be an object');
}).should.throw(
`analytics.logEvent(): Second optional argument 'params' must be an object if provided.`
);
});
it('log an event without parameters', () => {
firebase.analytics().logEvent('test_event');
});
it('log an event with parameters', () => {
firebase.analytics().logEvent('test_event', {
boolean: true,
number: 1,
string: 'string',
});
});
});
describe('setAnalyticsCollectionEnabled()', () => {
it('true', () => {
firebase.analytics().setAnalyticsCollectionEnabled(true);
});
it('false', () => {
firebase.analytics().setAnalyticsCollectionEnabled(false);
});
});
describe('setCurrentScreen()', () => {
it('screenName only', () => {
firebase.analytics().setCurrentScreen('test screen');
});
it('screenName with screenClassOverride', () => {
firebase
.analytics()
.setCurrentScreen('test screen', 'test class override');
});
xit('errors if screenName not a string', () => {
// TODO needs validations adding to lib
});
xit('errors if screenClassOverride not a string', () => {
// TODO needs validations adding to lib
});
});
describe('setMinimumSessionDuration()', () => {
it('default duration', () => {
firebase.analytics().setMinimumSessionDuration();
});
it('custom duration', () => {
firebase.analytics().setMinimumSessionDuration(10001);
});
});
describe('setSessionTimeoutDuration()', () => {
it('default duration', () => {
firebase.analytics().setSessionTimeoutDuration();
});
it('custom duration', () => {
firebase.analytics().setSessionTimeoutDuration(1800001);
});
});
describe('setUserId()', () => {
// nulls remove the field on firebase
it('allows a null values to be set', () => {
firebase.analytics().setUserId(null);
});
it('accepts string values', () => {
firebase.analytics().setUserId('test-id');
});
it('rejects none string none null values', () => {
try {
firebase.analytics().setUserId(33.3333);
} catch (e) {
e.message.should.containEql('must be a string');
}
});
});
describe('setUserProperty()', () => {
// nulls remove the field on firebase
it('allows a null values to be set', () => {
firebase.analytics().setUserProperty('fooby', null);
});
it('accepts string values', () => {
firebase.analytics().setUserProperty('fooby2', 'test-id');
});
it('rejects none string none null values', () => {
try {
firebase.analytics().setUserProperty('fooby3', 33.3333);
} catch (e) {
e.message.should.containEql('must be a string');
}
});
xit('errors if property name not a string', () => {
// TODO needs validations adding to lib
});
});
describe('setUserProperties()', () => {
// nulls remove the field on firebase
it('allows a null values to be set', () => {
firebase.analytics().setUserProperties({ fooby: null });
});
it('accepts string values', () => {
firebase.analytics().setUserProperties({ fooby2: 'test-id' });
});
it('rejects none string none null values', () => {
try {
firebase.analytics().setUserProperties({ fooby3: 33.3333 });
} catch (e) {
e.message.should.containEql('must be a string');
}
});
});
});

1129
tests/e2e/auth/auth.e2e.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
describe('auth() -> emailLink Provider', () => {
describe('sendSignInLinkToEmail', () => {
it('should send email', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
// const email = 'MANUAL TEST EMAIL HERE';
const actionCodeSettings = {
url: 'http://localhost:1337/authLinkFoo?bar=1234',
handleCodeInApp: true,
iOS: {
bundleId: 'com.testing',
},
android: {
packageName: 'com.testing',
installApp: true,
minimumVersion: '12',
},
};
await firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings);
});
});
describe('isSignInWithEmailLink', () => {
it('should return true/false', async () => {
const emailLink1 =
'https://www.example.com/action?mode=signIn&oobCode=oobCode';
const emailLink2 =
'https://www.example.com/action?mode=verifyEmail&oobCode=oobCode';
const emailLink3 = 'https://www.example.com/action?mode=signIn';
const emailLink4 =
'https://x59dg.app.goo.gl/?link=https://rnfirebase-b9ad4.firebaseapp.com/__/auth/action?apiKey%3Dfoo%26mode%3DsignIn%26oobCode%3Dbar';
should.equal(true, firebase.auth().isSignInWithEmailLink(emailLink1));
should.equal(false, firebase.auth().isSignInWithEmailLink(emailLink2));
should.equal(false, firebase.auth().isSignInWithEmailLink(emailLink3));
should.equal(true, firebase.auth().isSignInWithEmailLink(emailLink4));
});
});
// FOR MANUAL TESTING ONLY
xdescribe('signInWithEmailLink', () => {
it('should signIn', async () => {
const email = 'MANUAL TEST EMAIL HERE';
const emailLink = 'MANUAL TEST CODE HERE';
const userCredential = await firebase
.auth()
.signInWithEmailLink(email, emailLink);
userCredential.user.email.should.equal(email);
await await firebase.auth().signOut();
});
});
});

View File

@ -0,0 +1,3 @@
xdescribe('auth() => Phone', () => {
// TODO mock native events using bridge's event inspector / mocking tools (still wip)
});

View File

@ -0,0 +1,237 @@
describe('auth() -> Providers', () => {
describe('EmailAuthProvider', () => {
describe('constructor', () => {
it('should throw an unsupported error', () => {
(() => new firebase.auth.EmailAuthProvider()).should.throw(
'`new EmailAuthProvider()` is not supported on the native Firebase SDKs.'
);
});
});
describe('credential', () => {
it('should return a credential object', () => {
const email = 'email@email.com';
const password = 'password';
const credential = firebase.auth.EmailAuthProvider.credential(
email,
password
);
credential.providerId.should.equal('password');
credential.token.should.equal(email);
credential.secret.should.equal(password);
});
});
describe('credentialWithLink', () => {
it('should return a credential object', () => {
const email = 'email@email.com';
const link = 'link';
const credential = firebase.auth.EmailAuthProvider.credentialWithLink(
email,
link
);
credential.providerId.should.equal('emailLink');
credential.token.should.equal(email);
credential.secret.should.equal(link);
});
});
describe('EMAIL_PASSWORD_SIGN_IN_METHOD', () => {
it('should return password', () => {
firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD.should.equal(
'password'
);
});
});
describe('EMAIL_LINK_SIGN_IN_METHOD', () => {
it('should return emailLink', () => {
firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD.should.equal(
'emailLink'
);
});
});
describe('PROVIDER_ID', () => {
it('should return password', () => {
firebase.auth.EmailAuthProvider.PROVIDER_ID.should.equal('password');
});
});
});
describe('FacebookAuthProvider', () => {
describe('constructor', () => {
it('should throw an unsupported error', () => {
(() => new firebase.auth.FacebookAuthProvider()).should.throw(
'`new FacebookAuthProvider()` is not supported on the native Firebase SDKs.'
);
});
});
describe('credential', () => {
it('should return a credential object', () => {
const token = '123456';
const credential = firebase.auth.FacebookAuthProvider.credential(token);
credential.providerId.should.equal('facebook.com');
credential.token.should.equal(token);
credential.secret.should.equal('');
});
});
describe('PROVIDER_ID', () => {
it('should return facebook.com', () => {
firebase.auth.FacebookAuthProvider.PROVIDER_ID.should.equal(
'facebook.com'
);
});
});
});
describe('GithubAuthProvider', () => {
describe('constructor', () => {
it('should throw an unsupported error', () => {
(() => new firebase.auth.GithubAuthProvider()).should.throw(
'`new GithubAuthProvider()` is not supported on the native Firebase SDKs.'
);
});
});
describe('credential', () => {
it('should return a credential object', () => {
const token = '123456';
const credential = firebase.auth.GithubAuthProvider.credential(token);
credential.providerId.should.equal('github.com');
credential.token.should.equal(token);
credential.secret.should.equal('');
});
});
describe('PROVIDER_ID', () => {
it('should return github.com', () => {
firebase.auth.GithubAuthProvider.PROVIDER_ID.should.equal('github.com');
});
});
});
describe('GoogleAuthProvider', () => {
describe('constructor', () => {
it('should throw an unsupported error', () => {
(() => new firebase.auth.GoogleAuthProvider()).should.throw(
'`new GoogleAuthProvider()` is not supported on the native Firebase SDKs.'
);
});
});
describe('credential', () => {
it('should return a credential object', () => {
const token = '123456';
const secret = '654321';
const credential = firebase.auth.GoogleAuthProvider.credential(
token,
secret
);
credential.providerId.should.equal('google.com');
credential.token.should.equal(token);
credential.secret.should.equal(secret);
});
});
describe('PROVIDER_ID', () => {
it('should return google.com', () => {
firebase.auth.GoogleAuthProvider.PROVIDER_ID.should.equal('google.com');
});
});
});
describe('OAuthProvider', () => {
describe('constructor', () => {
it('should throw an unsupported error', () => {
(() => new firebase.auth.OAuthProvider()).should.throw(
'`new OAuthProvider()` is not supported on the native Firebase SDKs.'
);
});
});
describe('credential', () => {
it('should return a credential object', () => {
const idToken = '123456';
const accessToken = '654321';
const credential = firebase.auth.OAuthProvider.credential(
idToken,
accessToken
);
credential.providerId.should.equal('oauth');
credential.token.should.equal(idToken);
credential.secret.should.equal(accessToken);
});
});
describe('PROVIDER_ID', () => {
it('should return oauth', () => {
firebase.auth.OAuthProvider.PROVIDER_ID.should.equal('oauth');
});
});
});
describe('PhoneAuthProvider', () => {
describe('constructor', () => {
it('should throw an unsupported error', () => {
(() => new firebase.auth.PhoneAuthProvider()).should.throw(
'`new PhoneAuthProvider()` is not supported on the native Firebase SDKs.'
);
});
});
describe('credential', () => {
it('should return a credential object', () => {
const verificationId = '123456';
const code = '654321';
const credential = firebase.auth.PhoneAuthProvider.credential(
verificationId,
code
);
credential.providerId.should.equal('phone');
credential.token.should.equal(verificationId);
credential.secret.should.equal(code);
});
});
describe('PROVIDER_ID', () => {
it('should return phone', () => {
firebase.auth.PhoneAuthProvider.PROVIDER_ID.should.equal('phone');
});
});
});
describe('TwitterAuthProvider', () => {
describe('constructor', () => {
it('should throw an unsupported error', () => {
(() => new firebase.auth.TwitterAuthProvider()).should.throw(
'`new TwitterAuthProvider()` is not supported on the native Firebase SDKs.'
);
});
});
describe('credential', () => {
it('should return a credential object', () => {
const token = '123456';
const secret = '654321';
const credential = firebase.auth.TwitterAuthProvider.credential(
token,
secret
);
credential.providerId.should.equal('twitter.com');
credential.token.should.equal(token);
credential.secret.should.equal(secret);
});
});
describe('PROVIDER_ID', () => {
it('should return twitter.com', () => {
firebase.auth.TwitterAuthProvider.PROVIDER_ID.should.equal(
'twitter.com'
);
});
});
});
});

485
tests/e2e/auth/user.e2e.js Normal file
View File

@ -0,0 +1,485 @@
describe('auth().currentUser', () => {
describe('getIdToken()', () => {
it('should return a token', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
const newUser = await firebase
.auth()
.createUserWithEmailAndPassword(email, pass);
// Test
const token = await newUser.getIdToken();
// Assertions
token.should.be.a.String();
token.length.should.be.greaterThan(24);
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('getToken()', () => {
it('should return a token', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
const newUser = await firebase
.auth()
.createUserWithEmailAndPassword(email, pass);
// Test
const token = await newUser.getToken();
// Assertions
token.should.be.a.String();
token.length.should.be.greaterThan(24);
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('linkWithCredential()', () => {
it('should link anonymous account <-> email account', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
await firebase.auth().signInAnonymouslyAndRetrieveData();
const currentUser = firebase.auth().currentUser;
// Test
const credential = firebase.auth.EmailAuthProvider.credential(
email,
pass
);
const linkedUser = await currentUser.linkWithCredential(credential);
// Assertions
linkedUser.should.be.an.Object();
linkedUser.should.equal(firebase.auth().currentUser);
linkedUser.email.toLowerCase().should.equal(email.toLowerCase());
linkedUser.isAnonymous.should.equal(false);
linkedUser.providerId.should.equal('firebase');
linkedUser.providerData.should.be.an.Array();
linkedUser.providerData.length.should.equal(1);
// Clean up
await firebase.auth().currentUser.delete();
});
it('should error on link anon <-> email if email already exists', async () => {
const email = 'test@test.com';
const pass = 'test1234';
await firebase.auth().signInAnonymouslyAndRetrieveData();
const currentUser = firebase.auth().currentUser;
// Test
try {
const credential = firebase.auth.EmailAuthProvider.credential(
email,
pass
);
await currentUser.linkWithCredential(credential);
// Clean up
await firebase.auth().signOut();
// Reject
Promise.reject(new Error('Did not error on link'));
} catch (error) {
// Assertions
error.code.should.equal('auth/email-already-in-use');
error.message.should.equal(
'The email address is already in use by another account.'
);
// Clean up
await firebase.auth().currentUser.delete();
}
});
});
describe('linkAndRetrieveDataWithCredential()', () => {
it('should link anonymous account <-> email account', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
await firebase.auth().signInAnonymouslyAndRetrieveData();
const currentUser = firebase.auth().currentUser;
// Test
const credential = firebase.auth.EmailAuthProvider.credential(
email,
pass
);
const linkedUserCredential = await currentUser.linkAndRetrieveDataWithCredential(
credential
);
// Assertions
const linkedUser = linkedUserCredential.user;
linkedUser.should.be.an.Object();
linkedUser.should.equal(firebase.auth().currentUser);
linkedUser.email.toLowerCase().should.equal(email.toLowerCase());
linkedUser.isAnonymous.should.equal(false);
linkedUser.providerId.should.equal('firebase');
linkedUser.providerData.should.be.an.Array();
linkedUser.providerData.length.should.equal(1);
// Clean up
await firebase.auth().currentUser.delete();
});
it('should error on link anon <-> email if email already exists', async () => {
const email = 'test@test.com';
const pass = 'test1234';
await firebase.auth().signInAnonymouslyAndRetrieveData();
const currentUser = firebase.auth().currentUser;
// Test
try {
const credential = firebase.auth.EmailAuthProvider.credential(
email,
pass
);
await currentUser.linkAndRetrieveDataWithCredential(credential);
// Clean up
await firebase.auth().signOut();
// Reject
Promise.reject(new Error('Did not error on link'));
} catch (error) {
// Assertions
error.code.should.equal('auth/email-already-in-use');
error.message.should.equal(
'The email address is already in use by another account.'
);
// Clean up
await firebase.auth().currentUser.delete();
}
});
});
describe('reauthenticateWithCredential()', () => {
it('should reauthenticate correctly', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
await firebase
.auth()
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
// Test
const credential = firebase.auth.EmailAuthProvider.credential(
email,
pass
);
await firebase
.auth()
.currentUser.reauthenticateWithCredential(credential);
// Assertions
const currentUser = firebase.auth().currentUser;
currentUser.email.should.equal(email.toLowerCase());
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('reauthenticateAndRetrieveDataWithCredential()', () => {
it('should reauthenticate correctly', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
await firebase
.auth()
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
// Test
const credential = firebase.auth.EmailAuthProvider.credential(
email,
pass
);
await firebase
.auth()
.currentUser.reauthenticateAndRetrieveDataWithCredential(credential);
// Assertions
const currentUser = firebase.auth().currentUser;
currentUser.email.should.equal(email.toLowerCase());
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('reload()', () => {
it('should not error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
try {
await firebase.auth().currentUser.reload();
await firebase.auth().signOut();
} catch (error) {
// Reject
await firebase.auth().signOut();
Promise.reject(new Error('reload() caused an error', error));
}
});
});
describe('sendEmailVerification()', () => {
it('should not error', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
await firebase
.auth()
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
try {
await firebase.auth().currentUser.sendEmailVerification();
await firebase.auth().currentUser.delete();
} catch (error) {
// Reject
await firebase.auth().currentUser.delete();
Promise.reject(
new Error('sendEmailVerification() caused an error', error)
);
}
});
});
describe('unlink()', () => {
it('should unlink the email address', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
await firebase.auth().signInAnonymouslyAndRetrieveData();
const currentUser = firebase.auth().currentUser;
const credential = firebase.auth.EmailAuthProvider.credential(
email,
pass
);
await currentUser.linkAndRetrieveDataWithCredential(credential);
// Test
await currentUser.unlink(firebase.auth.EmailAuthProvider.PROVIDER_ID);
// Assertions
const unlinkedUser = firebase.auth().currentUser;
unlinkedUser.providerData.should.be.an.Array();
unlinkedUser.providerData.length.should.equal(0);
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('updateEmail()', () => {
it('should update the email address', async () => {
const random = randomString(12, '#aA');
const random2 = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const email2 = `${random2}@${random2}.com`;
const pass = random;
// Setup
await firebase
.auth()
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
firebase
.auth()
.currentUser.email.toLowerCase()
.should.equal(email.toLowerCase());
// Update user email
await firebase.auth().currentUser.updateEmail(email2);
// Assertions
firebase
.auth()
.currentUser.email.toLowerCase()
.should.equal(email2.toLowerCase());
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('updatePassword()', () => {
it('should update the password', async () => {
const random = randomString(12, '#aA');
const random2 = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
const pass2 = random2;
// Setup
await firebase
.auth()
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
// Update user password
await firebase.auth().currentUser.updatePassword(pass2);
// Sign out
await firebase.auth().signOut();
// Log in with the new password
await firebase
.auth()
.signInAndRetrieveDataWithEmailAndPassword(email, pass2);
// Assertions
firebase.auth().currentUser.should.be.an.Object();
firebase.auth().currentUser.email.should.equal(email.toLowerCase());
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('updateProfile()', () => {
it('should update the profile', async () => {
const random = randomString(12, '#aA');
const email = `${random}@${random}.com`;
const pass = random;
const displayName = random;
const photoURL = `http://${random}.com/${random}.jpg`;
// Setup
await firebase
.auth()
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
// Update user profile
await firebase.auth().currentUser.updateProfile({
displayName,
photoURL,
});
// Assertions
const user = firebase.auth().currentUser;
user.should.be.an.Object();
user.email.should.equal(email.toLowerCase());
user.displayName.should.equal(displayName);
user.photoURL.should.equal(photoURL);
// Clean up
await firebase.auth().currentUser.delete();
});
});
describe('linkWithPhoneNumber()', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => {
firebase.auth().currentUser.linkWithPhoneNumber();
}).should.throw(
'User.linkWithPhoneNumber() is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
describe('linkWithPopup()', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => {
firebase.auth().currentUser.linkWithPopup();
}).should.throw(
'User.linkWithPopup() is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
describe('linkWithRedirect()', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => {
firebase.auth().currentUser.linkWithRedirect();
}).should.throw(
'User.linkWithRedirect() is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
describe('reauthenticateWithPhoneNumber()', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => {
firebase.auth().currentUser.reauthenticateWithPhoneNumber();
}).should.throw(
'User.reauthenticateWithPhoneNumber() is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
describe('reauthenticateWithPopup()', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => {
firebase.auth().currentUser.reauthenticateWithPopup();
}).should.throw(
'User.reauthenticateWithPopup() is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
describe('reauthenticateWithRedirect()', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => {
firebase.auth().currentUser.reauthenticateWithRedirect();
}).should.throw(
'User.reauthenticateWithRedirect() is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
describe('updatePhoneNumber()', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => {
firebase.auth().currentUser.updatePhoneNumber();
}).should.throw(
'User.updatePhoneNumber() is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
describe('refreshToken', () => {
it('should throw an unsupported error', async () => {
await firebase.auth().signInAnonymouslyAndRetrieveData();
(() => firebase.auth().currentUser.refreshToken).should.throw(
'User.refreshToken is unsupported by the native Firebase SDKs.'
);
await firebase.auth().signOut();
});
});
});

124
tests/e2e/bridge.spec.js Executable file
View File

@ -0,0 +1,124 @@
const should = require('should');
describe('bridge', () => {
// beforeEach(async function beforeEach() {
// await device.reloadReactNative();
// bridge.root.setState({ message: this.currentTest.title });
// });
it('should provide -> global.bridge', async () => {
should(bridge).not.be.undefined();
return Promise.resolve();
});
// main react-native module you're testing on
// in our case react-native-firebase
it('should provide -> bridge.module', async () => {
should(bridge.module).not.be.undefined();
return Promise.resolve();
});
// react-native module access
it('should provide -> bridge.rn', () => {
should(bridge.rn).not.be.undefined();
should(bridge.rn.Platform.OS).be.a.String();
should(bridge.rn.Platform.OS).equal(device.getPlatform());
return Promise.resolve();
});
// 'global' context of the app's JS environment
it('should provide -> bridge.context', () => {
should(bridge.context).not.be.undefined();
should(bridge.context.setTimeout).be.a.Function();
should(bridge.context.window).be.a.Object();
// etc ... e.g. __coverage__ is here also if covering
return Promise.resolve();
});
// the apps root component
// allows you to read and set state if required
xit('should provide -> bridge.root', async () => {
should(bridge.root).not.be.undefined();
should(bridge.root.setState).be.a.Function();
should(bridge.root.state).be.a.Object();
// test setting state
await new Promise(resolve =>
bridge.root.setState({ message: 'hello world' }, resolve)
);
should(bridge.root.state.message).equal('hello world');
return Promise.resolve();
});
// we shim our own reloadReactNative functionality as the detox reloadReactNative built-in
// hangs often and seems unpredictable - todo: investigate & PR if solution found
// reloadReactNative is replaced on init with bridge.root automatically
xit('should allow reloadReactNative usage without breaking remote debug', async () => {
should(bridge.reload).be.a.Function();
// and check it works without breaking anything
await device.reloadReactNative();
should(bridge.reload).be.a.Function();
return Promise.resolve();
});
it('should allow launchApp usage without breaking remote debug', async () => {
should(bridge.module).not.be.undefined();
should(bridge.reload).be.a.Function();
should(bridge.rn).not.be.undefined();
should(bridge.rn.Platform.OS).be.a.String();
should(bridge.rn.Platform.OS).equal(device.getPlatform());
await device.launchApp({ newInstance: true });
should(bridge.module).not.be.undefined();
should(bridge.reload).be.a.Function();
should(bridge.rn).not.be.undefined();
should(bridge.rn.Platform.OS).be.a.String();
should(bridge.rn.Platform.OS).equal(device.getPlatform());
return Promise.resolve();
});
// TIMERS
it('timing.setTimeout', cb => {
const start = Date.now();
bridge.context.setTimeout(() => {
const timeTaken = Date.now() - start;
if (timeTaken >= 50) cb();
else cb(new Error('setTimeout fn called too soon.'));
}, 50);
});
it('timing.setInterval', cb => {
let times = 0;
let interval;
const start = Date.now();
interval = bridge.context.setInterval(() => {
const timeTaken = Date.now() - start;
times++;
bridge.context.clearInterval(interval);
if (times >= 2) {
return cb(new Error('Interval did not cancel correctly.'));
}
if (timeTaken < 50) {
return cb(new Error('setInterval fn called too soon.'));
}
return bridge.context.setTimeout(cb, 100);
}, 50);
});
it('timing.setImmediate', cb => {
bridge.context.setImmediate(() => cb());
});
it('timing.requestIdleCallback', cb => {
bridge.context.requestIdleCallback(() => cb());
});
it('timing.requestAnimationFrame', cb => {
bridge.context.requestAnimationFrame(() => cb());
});
});

View File

@ -0,0 +1,97 @@
describe('config()', () => {
before(() => {
firebase.config().enableDeveloperMode();
firebase.config().setDefaults({
foo: 'bar',
bar: 'baz',
});
});
describe('fetch()', () => {
it('with expiration provided', () => firebase.config().fetch(0));
it('without expiration provided', () => firebase.config().fetch());
});
describe('activateFetched()', () => {
it('with expiration provided', async () => {
await firebase.config().fetch(0);
const activated = await firebase.config().activateFetched();
activated.should.be.a.Boolean();
});
it('with expiration provided', async () => {
await firebase.config().fetch();
const activated = await firebase.config().activateFetched();
activated.should.be.a.Boolean();
});
});
describe('getValue()', () => {
it('gets a single value by key', async () => {
const config = await firebase.config().getValue('foo');
config.should.be.a.Object();
config.source.should.be.a.String();
config.val.should.be.a.Function();
config.val().should.be.equalOneOf('bar', true);
});
xit('errors if no key provided or is not a string', async () => {
// TODO needs input validation adding to lib
});
});
describe('getValues()', () => {
it('get multiple values by an array of keys', async () => {
const config = await firebase
.config()
.getValues(['foo', 'bar', 'foobar', 'numvalue']);
config.should.be.a.Object();
config.should.have.keys('foo', 'bar', 'foobar');
const fooValue = config.foo.val();
const barValue = config.bar.val();
const foobarValue = config.foobar.val();
const numvalueValue = config.numvalue.val();
fooValue.should.be.equal(true);
barValue.should.be.equal('baz');
foobarValue.should.be.equal('barbaz');
numvalueValue.should.be.equal(0);
});
xit('errors if any key is not a string', async () => {
// TODO needs input validation adding to lib
});
});
describe('getKeysByPrefix()', () => {
it('get keys beginning with the prefix provided', async () => {
const keys = await firebase.config().getKeysByPrefix('num');
keys.should.be.Array();
should.equal(keys.length, 2);
});
xit('get all keys as an array if no prefix provided', async () => {
// TODO flakey on Android
const keys = await firebase.config().getKeysByPrefix();
keys.should.be.Array();
should.equal(keys.length, 4);
});
xit('errors if prefix is not a string', async () => {
// TODO needs input validation adding to lib
});
});
describe('setDefaultsFromResource()', () => {
it('accepts a resource id/name to read defaults from', async () => {
if (device.getPlatform() === 'android')
firebase.config().setDefaultsFromResource(6666);
else firebase.config().setDefaultsFromResource();
// todo add plist/xml on ios/android to test
});
xit('errors if id not a integer for android or a string for ios', async () => {
// TODO needs input validation adding to lib
});
});
});

View File

@ -0,0 +1,96 @@
describe('Core', () => {
describe('Firebase', () => {
it('it should create js apps for natively initialized apps', () => {
should.equal(firebase.app()._nativeInitialized, true);
return Promise.resolve();
});
it('natively initialized apps should have options available in js', () => {
const platformAppConfig = TestHelpers.core.config();
should.equal(firebase.app().options.apiKey, platformAppConfig.apiKey);
should.equal(firebase.app().options.appId, platformAppConfig.appId);
should.equal(
firebase.app().options.databaseURL,
platformAppConfig.databaseURL
);
should.equal(
firebase.app().options.messagingSenderId,
platformAppConfig.messagingSenderId
);
should.equal(
firebase.app().options.projectId,
platformAppConfig.projectId
);
should.equal(
firebase.app().options.storageBucket,
platformAppConfig.storageBucket
);
return Promise.resolve();
});
it('it should resolve onReady for natively initialized apps', () =>
firebase.app().onReady());
it('it should initialize dynamic apps', () => {
const name = `testscoreapp${global.testRunId}`;
const platformAppConfig = TestHelpers.core.config();
return firebase
.initializeApp(platformAppConfig, name)
.onReady()
.then(newApp => {
newApp.name.should.equal(name.toUpperCase());
newApp.toString().should.equal(name.toUpperCase());
newApp.options.apiKey.should.equal(platformAppConfig.apiKey);
// TODO add back in when android sdk support for deleting apps becomes available
// return newApp.delete();
return Promise.resolve();
});
});
it('SDK_VERSION should return a string version', () => {
firebase.SDK_VERSION.should.be.a.String();
});
});
describe('App', () => {
it('apps should provide an array of apps', () => {
should.equal(!!firebase.apps.length, true);
should.equal(firebase.apps.includes(firebase.app('[DEFAULT]')), true);
return Promise.resolve();
});
it('delete is unsupported', () => {
(() => {
firebase.app().delete();
}).should.throw(
'app.delete() is unsupported by the native Firebase SDKs.'
);
});
it('extendApp should error if an object is not supplied', () => {
(() => {
firebase.app().extendApp('string');
}).should.throw(
"Missing required argument of type 'Object' for method 'extendApp()'."
);
});
it('extendApp should error if a protected property is supplied', () => {
(() => {
firebase.app().extendApp({
database: {},
});
}).should.throw(
"Property 'database' is protected and can not be overridden by extendApp."
);
});
it('extendApp should provide additional functionality', () => {
const extension = {};
firebase.app().extendApp({
extension,
});
firebase.app().extension.should.equal(extension);
});
});
});

View File

@ -0,0 +1,78 @@
describe('crashlytics()', () => {
// todo test is flakey due to a detox error occurring sometimes;
// Error: the string "Error when sending event: websocketFailed with body:
// {\n id = 0;\n message = \"The operation couldn\\U2019t be completed.
// Connection refused\";\n}. Bridge is not set. This is probably because you've
// explicitly synthesized the bridge in RCTWebSocketModule, even though it's
// inherited from RCTEventEmitter.
xdescribe('crash()', () => {
it('should force an app crash', async () => {
await firebase.crashlytics().crash();
if (device.getPlatform() === 'ios') {
// ios responds quicker after a fatal exception if we re-install
await device.uninstallApp();
await device.installApp();
}
await device.launchApp({ newInstance: true });
await firebase.crashlytics().log('app relaunched from a crash');
});
});
describe('log()', () => {
it('should set a string value', async () => {
await firebase.crashlytics().log('123abc');
});
xit('should error on a non a string value', async () => {
// TODO lib needs validations adding
await firebase.crashlytics().log(123456);
});
});
describe('recordError()', () => {
it('should record an error with a code and message', async () => {
await firebase.crashlytics().recordError(1234, 'Test error');
});
xit('should error on invalid args', async () => {
// TODO lib needs validations adding - and this should technically take an instance of Error only
await firebase.crashlytics().recordError({}, []);
});
});
describe('setBoolValue()', () => {
it('should set a boolean value', async () => {
await firebase.crashlytics().setBoolValue('boolKey', true);
});
});
describe('setFloatValue()', () => {
it('should set a float value', async () => {
await firebase.crashlytics().setFloatValue('floatKey', 1.23);
});
});
describe('setIntValue()', () => {
it('should set a integer value', async () => {
await firebase.crashlytics().setIntValue('intKey', 123);
});
});
describe('setStringValue()', () => {
it('should set a string value', async () => {
await firebase.crashlytics().setStringValue('stringKey', 'test');
});
});
describe('setUserIdentifier()', () => {
it('should set a string value', async () => {
await firebase.crashlytics().setUserIdentifier('123abc');
});
xit('should error on a non a string value', async () => {
// TODO lib needs validations adding
await firebase.crashlytics().setUserIdentifier(123456);
});
});
});

View File

@ -0,0 +1,46 @@
const { CONTENTS, setDatabaseContents } = TestHelpers.database;
describe('database()', () => {
before(() => setDatabaseContents());
describe('ref().once()', () => {
it('returns a promise', () => {
const ref = firebase.database().ref('tests/types/number');
const returnValue = ref.once('value');
returnValue.should.be.Promise();
});
it('resolves with the correct value', async () => {
await Promise.all(
Object.keys(CONTENTS.DEFAULT).map(dataRef => {
const dataTypeValue = CONTENTS.DEFAULT[dataRef];
const ref = firebase.database().ref(`tests/types/${dataRef}`);
return ref.once('value').then(snapshot => {
snapshot.val().should.eql(bridge.contextify(dataTypeValue));
});
})
);
});
it('is NOT called when the value is changed', async () => {
const callback = sinon.spy();
const ref = firebase.database().ref('tests/types/number');
ref.once('value').then(callback);
await ref.set(CONTENTS.NEW.number);
callback.should.be.calledOnce();
});
it('errors if permission denied', async () => {
const reference = firebase.database().ref('nope');
try {
await reference.once('value');
} catch (error) {
error.code.includes('database/permission-denied').should.be.true();
return true;
}
throw new Error('No permission denied error');
});
});
});

View File

@ -0,0 +1,52 @@
const { setDatabaseContents, CONTENTS } = TestHelpers.database;
describe('database()', () => {
beforeEach(() => setDatabaseContents());
describe('ref.set()', () => {
it('returns a promise', async () => {
const ref = firebase.database().ref('tests/types/number');
const returnValue = ref.set(CONTENTS.DEFAULT.number);
returnValue.should.be.Promise();
const value = await returnValue;
(value === null).should.be.true();
});
it('changes value', async () => {
await Promise.all(
Object.keys(CONTENTS.DEFAULT).map(async dataRef => {
const previousValue = bridge.contextify(CONTENTS.DEFAULT[dataRef]);
const ref = firebase.database().ref(`tests/types/${dataRef}`);
const snapshot = await ref.once('value');
snapshot.val().should.eql(previousValue);
const newValue = bridge.contextify(CONTENTS.NEW[dataRef]);
await ref.set(newValue);
const snapshot2 = await ref.once('value');
snapshot2.val().should.eql(newValue);
})
);
});
it('can unset values', async () => {
await Promise.all(
Object.keys(CONTENTS.DEFAULT).map(async dataRef => {
const previousValue = bridge.contextify(CONTENTS.DEFAULT[dataRef]);
const ref = firebase.database().ref(`tests/types/${dataRef}`);
const snapshot = await ref.once('value');
snapshot.val().should.eql(previousValue);
await ref.set(null);
const snapshot2 = await ref.once('value');
(snapshot2.val() === null).should.be.true();
})
);
});
});
});

View File

@ -0,0 +1,104 @@
const { setDatabaseContents } = TestHelpers.database;
// TODO use testRunId in refs to prevent multiple test instances interfering with each other
describe('database()', () => {
describe('Snapshot', () => {
before(() => setDatabaseContents());
it('should provide a functioning val() method', async () => {
const snapshot = await firebase
.database()
.ref('tests/types/array')
.once('value');
snapshot.val.should.be.a.Function();
snapshot.val().should.eql(bridge.Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
});
it('should provide a functioning child() method', async () => {
const snapshot = await firebase
.database()
.ref('tests/types/array')
.once('value');
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');
});
// TODO refactor
it('should provide a functioning hasChild() method', async () => {
const snapshot = await firebase
.database()
.ref('tests/types/object')
.once('value');
snapshot.hasChild.should.be.a.Function();
snapshot.hasChild('foo').should.equal(true);
snapshot.hasChild('baz').should.equal(false);
});
it('should provide a functioning hasChildren() method', async () => {
const snapshot = await firebase
.database()
.ref('tests/types/object')
.once('value');
snapshot.hasChildren.should.be.a.Function();
snapshot.hasChildren().should.equal(true);
snapshot
.child('foo')
.hasChildren()
.should.equal(false);
});
it('should provide a functioning exists() method', async () => {
const snapshot = await firebase
.database()
.ref('tests/types/object/baz/daz')
.once('value');
snapshot.exists.should.be.a.Function();
snapshot.exists().should.equal(false);
});
it('should provide a functioning getPriority() method', async () => {
const ref = firebase.database().ref('tests/priority');
const snapshot = await ref.once('value');
snapshot.getPriority.should.be.a.Function();
snapshot.getPriority().should.equal(666);
snapshot.val().should.eql(bridge.Object({ foo: 'bar' }));
});
it('should provide a functioning forEach() method', async () => {
const snapshot = await firebase
.database()
.ref('tests/types/array')
.once('value');
let total = 0;
snapshot.forEach.should.be.a.Function();
snapshot.forEach(childSnapshot => {
const val = childSnapshot.val();
total += val;
return val === 3; // stop iteration after key 3
});
total.should.equal(6); // 0 + 1 + 2 + 3 = 6
});
it('should provide a key property', async () => {
const snapshot = await firebase
.database()
.ref('tests/types/array')
.once('value');
snapshot.key.should.be.a.String();
snapshot.key.should.equal('array');
});
});
});

View File

@ -0,0 +1,34 @@
const { setDatabaseContents } = TestHelpers.database;
// TODO use testRunId in refs to prevent multiple test instances interfering with each other
describe('database()', () => {
before(() => setDatabaseContents());
describe('ref.transaction()', () => {
it('increments a value', async () => {
let valueBefore = 1;
const ref = firebase.database().ref('tests/transaction');
const { committed, snapshot } = await ref.transaction(currentData => {
if (currentData === null) {
return valueBefore + 10;
}
valueBefore = currentData;
return valueBefore + 10;
}, true);
should.equal(committed, true, 'Transaction did not commit.');
snapshot.val().should.equal(valueBefore + 10);
});
it('aborts if undefined returned', async () => {
const ref = firebase.database().ref('tests/transaction');
const { committed } = await ref.transaction(() => undefined, true);
should.equal(
committed,
false,
'Transaction committed and did not abort.'
);
});
});
});

View File

@ -0,0 +1,79 @@
const { testDocRef } = TestHelpers.firestore;
describe('firestore()', () => {
describe('batch()', () => {
it('should create / update / delete', async () => {
const ayRef = testDocRef('AY');
const lRef = testDocRef('LON');
const nycRef = testDocRef('NYC');
const sfRef = testDocRef('SF');
const batch = firebase.firestore().batch();
batch.set(ayRef, { name: 'Aylesbury' });
batch.set(lRef, { name: 'London' });
batch.set(nycRef, { name: 'New York City' });
batch.set(sfRef, { name: 'San Francisco' });
batch.update(nycRef, { population: 1000000 });
batch.update(sfRef, 'name', 'San Fran');
batch.update(
sfRef,
new firebase.firestore.FieldPath('name'),
'San Fran FieldPath'
);
batch.update(
sfRef,
new firebase.firestore.FieldPath('nested', 'name'),
'Nested Nme'
);
batch.update(
sfRef,
new firebase.firestore.FieldPath('nested', 'firstname'),
'First Name',
new firebase.firestore.FieldPath('nested', 'lastname'),
'Last Name'
);
batch.set(lRef, { population: 3000000 }, { merge: true });
batch.delete(ayRef);
await batch.commit();
const ayDoc = await ayRef.get();
should.equal(ayDoc.exists, false);
const lDoc = await lRef.get();
lDoc.data().name.should.equal('London');
lDoc.data().population.should.equal(3000000);
const nycDoc = await nycRef.get();
nycDoc.data().name.should.equal('New York City');
nycDoc.data().population.should.equal(1000000);
const sfDoc = await sfRef.get();
sfDoc.data().name.should.equal('San Fran FieldPath');
sfDoc.data().nested.firstname.should.equal('First Name');
sfDoc.data().nested.lastname.should.equal('Last Name');
});
it('errors when invalid parameters supplied', async () => {
const ref = firebase.firestore().doc('collection/doc');
const batch = firebase.firestore().batch();
(() => {
batch.update(ref, 'error');
}).should.throw(
'WriteBatch.update failed: If using a single update argument, it must be an object.'
);
(() => {
batch.update(ref, 'error1', 'error2', 'error3');
}).should.throw(
'WriteBatch.update failed: The update arguments must be either a single object argument, or equal numbers of key/value pairs.'
);
(() => {
batch.update(ref, 0, 'error');
}).should.throw(
'WriteBatch.update failed: Argument at index 0 must be a string or FieldPath'
);
});
});
});

View File

@ -0,0 +1,156 @@
const testObject = { hello: 'world', testRunId };
const testString = JSON.stringify(testObject);
const testBuffer = Buffer.from(testString);
const testBase64 = testBuffer.toString('base64');
const testObjectLarge = new Array(5000).fill(testObject);
const testStringLarge = JSON.stringify(testObjectLarge);
const testBufferLarge = Buffer.from(testStringLarge);
const testBase64Large = testBufferLarge.toString('base64');
/** ----------------
* CLASS TESTS
* -----------------*/
describe('firestore', () => {
it('should export Blob class on statics', async () => {
const { Blob } = firebase.firestore;
should.exist(Blob);
});
describe('Blob', () => {
it('.constructor() -> returns new instance of Blob', async () => {
const { Blob } = firebase.firestore;
const myBlob = new Blob(testStringLarge);
myBlob.should.be.instanceOf(Blob);
myBlob._binaryString.should.equal(testStringLarge);
myBlob.toBase64().should.equal(testBase64Large);
});
it('.fromBase64String() -> returns new instance of Blob', async () => {
const { Blob } = firebase.firestore;
const myBlob = Blob.fromBase64String(testBase64);
myBlob.should.be.instanceOf(Blob);
myBlob._binaryString.should.equal(testString);
should.deepEqual(
JSON.parse(myBlob._binaryString),
testObject,
'Expected Blob _binaryString internals to serialize to json and match test object'
);
});
it('.fromBase64String() -> throws if arg not typeof string and length > 0', async () => {
const { Blob } = firebase.firestore;
const myBlob = Blob.fromBase64String(testBase64);
myBlob.should.be.instanceOf(Blob);
(() => Blob.fromBase64String(1234)).should.throwError();
(() => Blob.fromBase64String('')).should.throwError();
});
it('.fromUint8Array() -> returns new instance of Blob', async () => {
const testUInt8Array = new Uint8Array(testBuffer);
const { Blob } = firebase.firestore;
const myBlob = Blob.fromUint8Array(testUInt8Array);
myBlob.should.be.instanceOf(Blob);
const json = JSON.parse(myBlob._binaryString);
json.hello.should.equal('world');
});
it('.fromUint8Array() -> throws if arg not instanceof Uint8Array', async () => {
const testUInt8Array = new Uint8Array(testBuffer);
const { Blob } = firebase.firestore;
const myBlob = Blob.fromUint8Array(testUInt8Array);
myBlob.should.be.instanceOf(Blob);
(() => Blob.fromUint8Array('derp')).should.throwError();
});
});
describe('Blob instance', () => {
it('.toString() -> returns string representation of blob instance', async () => {
const { Blob } = firebase.firestore;
const myBlob = Blob.fromBase64String(testBase64);
myBlob.should.be.instanceOf(Blob);
should.equal(
myBlob.toString().includes(testBase64),
true,
'toString() should return a string that includes the base64'
);
});
it('.isEqual() -> returns true or false', async () => {
const { Blob } = firebase.firestore;
const myBlob = Blob.fromBase64String(testBase64);
const myBlob2 = Blob.fromBase64String(testBase64Large);
myBlob.isEqual(myBlob).should.equal(true);
myBlob2.isEqual(myBlob).should.equal(false);
});
it('.isEqual() -> throws if arg not instanceof Blob', async () => {
const { Blob } = firebase.firestore;
const myBlob = Blob.fromBase64String(testBase64);
const myBlob2 = Blob.fromBase64String(testBase64Large);
myBlob.isEqual(myBlob).should.equal(true);
(() => myBlob2.isEqual('derp')).should.throwError();
});
it('.toBase64() -> returns base64 string', async () => {
const { Blob } = firebase.firestore;
const myBlob = Blob.fromBase64String(testBase64);
myBlob.should.be.instanceOf(Blob);
myBlob.toBase64().should.equal(testBase64);
});
it('.toUint8Array() -> returns Uint8Array', async () => {
const { Blob } = firebase.firestore;
const myBlob = Blob.fromBase64String(testBase64);
const testUInt8Array = new Uint8Array(testBuffer);
const testUInt8Array2 = new Uint8Array();
myBlob.should.be.instanceOf(Blob);
should.deepEqual(myBlob.toUint8Array(), testUInt8Array);
should.notDeepEqual(myBlob.toUint8Array(), testUInt8Array2);
});
});
});
/** ----------------
* USAGE TESTS
* -----------------*/
describe('firestore', () => {
describe('Blob', () => {
it('reads and writes small blobs', async () => {
const { Blob } = firebase.firestore;
await firebase
.firestore()
.doc('blob-tests/small')
.set({ blobby: Blob.fromBase64String(testBase64) });
const snapshot = await firebase
.firestore()
.doc('blob-tests/small')
.get();
const blob = snapshot.data().blobby;
blob._binaryString.should.equal(testString);
blob.toBase64().should.equal(testBase64);
});
it('reads and writes large blobs', async () => {
const { Blob } = firebase.firestore;
await firebase
.firestore()
.doc('blob-tests/large')
.set({ blobby: Blob.fromBase64String(testBase64Large) });
const snapshot = await firebase
.firestore()
.doc('blob-tests/large')
.get();
const blob = snapshot.data().blobby;
blob._binaryString.should.equal(testStringLarge);
blob.toBase64().should.equal(testBase64Large);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,658 @@
const {
test2DocRef,
COL2_DOC_1,
COL2_DOC_1_ID,
COL2_DOC_1_PATH,
TEST2_COLLECTION_NAME,
resetTestCollectionDoc,
} = TestHelpers.firestore;
describe('firestore()', () => {
describe('DocumentReference', () => {
before(async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, COL2_DOC_1());
});
describe('class', () => {
it('should return instance methods', () => {
const document = test2DocRef(COL2_DOC_1_ID);
document.should.have.property('firestore');
// TODO: Remaining checks
});
});
describe('id', () => {
it('should return document id', () => {
const document = test2DocRef(COL2_DOC_1_ID);
document.id.should.equal(COL2_DOC_1_ID);
});
});
describe('parent', () => {
it('should return parent collection', () => {
const document = test2DocRef(COL2_DOC_1_ID);
document.parent.id.should.equal(TEST2_COLLECTION_NAME);
});
});
describe('collection()', () => {
it('should return a child collection', () => {
const document = test2DocRef(COL2_DOC_1_ID);
const collection = document.collection('pages');
collection.id.should.equal('pages');
});
it('should error if invalid collection path supplied', () => {
(() => {
test2DocRef(COL2_DOC_1_ID).collection('pages/page1');
}).should.throw(
'Argument "collectionPath" must point to a collection.'
);
});
});
describe('delete()', () => {
it('should delete Document', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, COL2_DOC_1());
await test2DocRef(COL2_DOC_1_ID).delete();
const doc = await test2DocRef(COL2_DOC_1_ID).get();
should.equal(doc.exists, false);
});
});
describe('get()', () => {
it('DocumentSnapshot should have correct properties', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, COL2_DOC_1());
const snapshot = await test2DocRef(COL2_DOC_1_ID).get();
snapshot.id.should.equal(COL2_DOC_1_ID);
snapshot.metadata.should.be.an.Object();
});
it('should support GetOptions source=`default`', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, COL2_DOC_1());
const snapshot = await test2DocRef(COL2_DOC_1_ID).get({
source: 'default',
});
snapshot.id.should.equal(COL2_DOC_1_ID);
snapshot.metadata.should.be.an.Object();
should.equal(snapshot.metadata.fromCache, false);
});
it('should support GetOptions source=`server`', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, COL2_DOC_1());
const snapshot = await test2DocRef(COL2_DOC_1_ID).get({
source: 'server',
});
snapshot.id.should.equal(COL2_DOC_1_ID);
snapshot.metadata.should.be.an.Object();
should.equal(snapshot.metadata.fromCache, false);
});
// TODO: For some reason when using `cache` it's not seeing the data as available, even if
// first requesting it from the server, although interestingly it works fine in the old
// tests app
xit('should support GetOptions source=`cache`', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, COL2_DOC_1());
const ref = test2DocRef(COL2_DOC_1_ID);
// Make sure the reference data is populated in the cache
await ref.get({ source: 'server' });
// Retrieve the cached version
const snapshot = await ref.get({ source: 'cache' });
snapshot.id.should.equal(COL2_DOC_1_ID);
snapshot.metadata.should.be.an.Object();
should.equal(snapshot.metadata.fromCache, true);
});
it('should error with invalid GetOptions source option', async () => {
const docRef = test2DocRef(COL2_DOC_1_ID);
try {
await docRef.get(() => {});
return Promise.reject(
new Error('get() did not reject with invalid argument.')
);
} catch (e) {
// do nothing
}
try {
await docRef.get({ source: 'invalid' });
return Promise.reject(
new Error('get() did not reject with invalid source property.')
);
} catch (e) {
// do nothing
}
return Promise.resolve();
});
});
describe('onSnapshot()', () => {
it('calls callback with the initial data and then when value changes', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
const docRef = test2DocRef(COL2_DOC_1_ID);
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise(resolve2 => {
unsubscribe = docRef.onSnapshot(snapshot => {
callback(snapshot.data());
resolve2();
});
});
callback.should.be.calledWith(currentDataValue);
// Update the document
await docRef.set(newDataValue);
await sleep(50);
// Assertions
callback.should.be.calledWith(newDataValue);
callback.should.be.calledTwice();
// Tear down
unsubscribe();
});
it("doesn't call callback when the ref is updated with the same value", async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
const docRef = test2DocRef(COL2_DOC_1_ID);
const currentDataValue = { name: 'doc1' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise(resolve2 => {
unsubscribe = docRef.onSnapshot(snapshot => {
callback(snapshot.data());
resolve2();
});
});
callback.should.be.calledWith(currentDataValue);
await docRef.set(currentDataValue);
await sleep(50);
// Assertions
callback.should.be.calledOnce(); // Callback is not called again
// Tear down
unsubscribe();
});
it('allows binding multiple callbacks to the same ref', async () => {
// Setup
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
const docRef = test2DocRef(COL2_DOC_1_ID);
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
let unsubscribeA;
let unsubscribeB;
await new Promise(resolve2 => {
unsubscribeA = docRef.onSnapshot(snapshot => {
callbackA(snapshot.data());
resolve2();
});
});
await new Promise(resolve2 => {
unsubscribeB = docRef.onSnapshot(snapshot => {
callbackB(snapshot.data());
resolve2();
});
});
callbackA.should.be.calledWith(currentDataValue);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDataValue);
callbackB.should.be.calledOnce();
await docRef.set(newDataValue);
await sleep(50);
callbackA.should.be.calledWith(newDataValue);
callbackB.should.be.calledWith(newDataValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledTwice();
// Tear down
unsubscribeA();
unsubscribeB();
});
it('listener stops listening when unsubscribed', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
// Setup
const docRef = test2DocRef(COL2_DOC_1_ID);
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
let unsubscribeA;
let unsubscribeB;
await new Promise(resolve2 => {
unsubscribeA = docRef.onSnapshot(snapshot => {
callbackA(snapshot.data());
resolve2();
});
});
await new Promise(resolve2 => {
unsubscribeB = docRef.onSnapshot(snapshot => {
callbackB(snapshot.data());
resolve2();
});
});
callbackA.should.be.calledWith(currentDataValue);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDataValue);
callbackB.should.be.calledOnce();
await docRef.set(newDataValue);
await sleep(50);
callbackA.should.be.calledWith(newDataValue);
callbackB.should.be.calledWith(newDataValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledTwice();
// Unsubscribe A
unsubscribeA();
await docRef.set(currentDataValue);
await sleep(50);
callbackB.should.be.calledWith(currentDataValue);
callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice();
// Unsubscribe B
unsubscribeB();
await docRef.set(newDataValue);
await sleep(50);
callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice();
});
it('supports options and callbacks', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
const docRef = test2DocRef(COL2_DOC_1_ID);
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise(resolve2 => {
unsubscribe = docRef.onSnapshot(
{ includeMetadataChanges: true },
snapshot => {
callback(snapshot.data());
resolve2();
}
);
});
callback.should.be.calledWith(currentDataValue);
// Update the document
await docRef.set(newDataValue);
await sleep(50);
// Assertions
callback.should.be.calledWith(newDataValue);
// Tear down
unsubscribe();
});
it('supports observer', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
const docRef = test2DocRef(COL2_DOC_1_ID);
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise(resolve2 => {
const observer = {
next: snapshot => {
callback(snapshot.data());
resolve2();
},
};
unsubscribe = docRef.onSnapshot(observer);
});
callback.should.be.calledWith(currentDataValue);
// Update the document
await docRef.set(newDataValue);
await sleep(50);
// Assertions
callback.should.be.calledWith(newDataValue);
callback.should.be.calledTwice();
// Tear down
unsubscribe();
});
it('supports options and observer', async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
const docRef = test2DocRef(COL2_DOC_1_ID);
const currentDataValue = { name: 'doc1' };
const newDataValue = { name: 'updated' };
const callback = sinon.spy();
// Test
let unsubscribe;
await new Promise(resolve2 => {
const observer = {
next: snapshot => {
callback(snapshot.data());
resolve2();
},
error: () => {},
};
unsubscribe = docRef.onSnapshot(
{ includeMetadataChanges: true },
observer
);
});
callback.should.be.calledWith(currentDataValue);
// Update the document
await docRef.set(newDataValue);
await sleep(50);
// Assertions
callback.should.be.calledWith(newDataValue);
// Tear down
unsubscribe();
});
it('errors when invalid parameters supplied', async () => {
const docRef = test2DocRef(COL2_DOC_1_ID);
(() => {
docRef.onSnapshot(() => {}, 'error');
}).should.throw(
'DocumentReference.onSnapshot failed: Second argument must be a valid function.'
);
(() => {
docRef.onSnapshot({
next: () => {},
error: 'error',
});
}).should.throw(
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
);
(() => {
docRef.onSnapshot({
next: 'error',
});
}).should.throw(
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
);
(() => {
docRef.onSnapshot(
{
includeMetadataChanges: true,
},
() => {},
'error'
);
}).should.throw(
'DocumentReference.onSnapshot failed: Third argument must be a valid function.'
);
(() => {
docRef.onSnapshot(
{
includeMetadataChanges: true,
},
{
next: () => {},
error: 'error',
}
);
}).should.throw(
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
);
(() => {
docRef.onSnapshot(
{
includeMetadataChanges: true,
},
{
next: 'error',
}
);
}).should.throw(
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
);
(() => {
docRef.onSnapshot(
{
includeMetadataChanges: true,
},
'error'
);
}).should.throw(
'DocumentReference.onSnapshot failed: Second argument must be a function or observer.'
);
(() => {
docRef.onSnapshot({
error: 'error',
});
}).should.throw(
'DocumentReference.onSnapshot failed: First argument must be a function, observer or options.'
);
(() => {
docRef.onSnapshot();
}).should.throw(
'DocumentReference.onSnapshot failed: Called with invalid arguments.'
);
});
});
describe('set()', () => {
before(async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
});
it('should create Document', async () => {
await test2DocRef('doc2').set({ name: 'doc2', testArray: [] });
const doc = await test2DocRef('doc2').get();
doc.data().name.should.equal('doc2');
});
it('should merge Document', async () => {
await test2DocRef(COL2_DOC_1_ID).set(
{ merge: 'merge' },
{ merge: true }
);
const doc = await test2DocRef(COL2_DOC_1_ID).get();
doc.data().name.should.equal('doc1');
doc.data().merge.should.equal('merge');
});
it('should overwrite Document', async () => {
await test2DocRef(COL2_DOC_1_ID).set({ name: 'overwritten' });
const doc = await test2DocRef(COL2_DOC_1_ID).get();
doc.data().name.should.equal('overwritten');
});
});
describe('update()', () => {
beforeEach(async () => {
await resetTestCollectionDoc(COL2_DOC_1_PATH, { name: 'doc1' });
});
it('should update Document using object', async () => {
await test2DocRef(COL2_DOC_1_ID).update({ name: 'updated' });
const doc = await test2DocRef(COL2_DOC_1_ID).get();
doc.data().name.should.equal('updated');
});
it('should update Document using key/value pairs', async () => {
await test2DocRef(COL2_DOC_1_ID).update('name', 'updated');
const doc = await test2DocRef(COL2_DOC_1_ID).get();
doc.data().name.should.equal('updated');
});
it('should update Document using FieldPath/value pair', async () => {
await test2DocRef(COL2_DOC_1_ID).update(
new firebase.firestore.FieldPath('name'),
'Name'
);
const doc = await test2DocRef(COL2_DOC_1_ID).get();
doc.data().name.should.equal('Name');
});
it('should update Document using nested FieldPath and value pair', async () => {
await test2DocRef(COL2_DOC_1_ID).update(
new firebase.firestore.FieldPath('nested', 'name'),
'Nested Name'
);
const doc = await test2DocRef(COL2_DOC_1_ID).get();
doc.data().nested.name.should.equal('Nested Name');
});
it('should update Document using multiple FieldPath/value pairs', async () => {
await test2DocRef(COL2_DOC_1_ID).update(
new firebase.firestore.FieldPath('nested', 'firstname'),
'First Name',
new firebase.firestore.FieldPath('nested', 'lastname'),
'Last Name'
);
const doc = await test2DocRef(COL2_DOC_1_ID).get();
doc.data().nested.firstname.should.equal('First Name');
doc.data().nested.lastname.should.equal('Last Name');
});
it('errors when invalid parameters supplied', async () => {
const docRef = test2DocRef(COL2_DOC_1_ID);
(() => {
docRef.update('error');
}).should.throw(
'DocumentReference.update failed: If using a single update argument, it must be an object.'
);
(() => {
docRef.update('error1', 'error2', 'error3');
}).should.throw(
'DocumentReference.update failed: The update arguments must be either a single object argument, or equal numbers of key/value pairs.'
);
(() => {
docRef.update(0, 'error');
}).should.throw(
'DocumentReference.update failed: Argument at index 0 must be a string or FieldPath'
);
});
});
describe('types', () => {
it('should handle Boolean field', async () => {
const docRef = test2DocRef('reference');
await docRef.set({
field: true,
});
const doc = await docRef.get();
should.equal(doc.data().field, true);
});
it('should handle Date field', async () => {
const date = new bridge.context.window.Date();
const docRef = test2DocRef('reference');
await docRef.set({
field: date,
});
const doc = await docRef.get();
doc.data().field.should.be.instanceof(bridge.context.window.Date);
should.equal(doc.data().field.toISOString(), date.toISOString());
should.equal(doc.data().field.getTime(), date.getTime());
});
it('should handle DocumentReference field', async () => {
const docRef = test2DocRef('reference');
await docRef.set({
field: firebase.firestore().doc('test/field'),
});
const doc = await docRef.get();
should.equal(doc.data().field.path, 'test/field');
});
it('should handle GeoPoint field', async () => {
const docRef = test2DocRef('reference');
await docRef.set({
field: new firebase.firestore.GeoPoint(1.01, 1.02),
});
const doc = await docRef.get();
should.equal(doc.data().field.latitude, 1.01);
should.equal(doc.data().field.longitude, 1.02);
});
});
});
});

View File

@ -0,0 +1,109 @@
const {
COL_DOC_1,
COL_DOC_1_ID,
COL_DOC_1_PATH,
testCollectionDoc,
resetTestCollectionDoc,
} = TestHelpers.firestore;
describe('firestore()', () => {
describe('DocumentSnapshot', () => {
before(async () => {
await resetTestCollectionDoc(COL_DOC_1_PATH, COL_DOC_1());
});
describe('id', () => {
it('returns a string document id', async () => {
const snapshot = await testCollectionDoc(COL_DOC_1_PATH).get();
snapshot.id.should.be.a.String();
snapshot.id.should.equal(COL_DOC_1_ID);
});
});
describe('ref', () => {
it('returns a DocumentReference', async () => {
const snapshot = await testCollectionDoc(COL_DOC_1_PATH).get();
const DocumentReference = bridge.require(
'dist/modules/firestore/DocumentReference'
);
snapshot.ref.should.be.an.instanceOf(DocumentReference);
});
});
describe('metadata', () => {
it('returns an object of meta data', async () => {
const { metadata } = await testCollectionDoc(COL_DOC_1_PATH).get();
metadata.should.be.an.Object();
metadata.should.have.property('hasPendingWrites');
metadata.should.have.property('fromCache');
metadata.hasPendingWrites.should.be.a.Boolean();
metadata.fromCache.should.be.a.Boolean();
});
});
describe('exists', () => {
it('returns a boolean', async () => {
const { exists } = await testCollectionDoc(COL_DOC_1_PATH).get();
exists.should.be.a.Boolean();
exists.should.be.true();
});
});
describe('data()', () => {
it('returns document data', async () => {
// additionally tests context binding not lost during destructuring
const snapshot = await testCollectionDoc(COL_DOC_1_PATH).get();
const { data } = snapshot;
snapshot.data.should.be.a.Function();
data.should.be.a.Function();
snapshot.data().should.be.a.Object();
data().should.be.a.Object();
snapshot.data().baz.should.be.true();
data().baz.should.be.true();
});
});
describe('get()', () => {
it('using a dot notated path string', async () => {
// additionally tests context binding not lost during destructuring
const snapshot = await testCollectionDoc(COL_DOC_1_PATH).get();
const { get } = snapshot;
should.equal(snapshot.get('foo'), 'bar');
should.equal(get('foo'), 'bar');
should.equal(snapshot.get('object.daz'), 123);
should.equal(get('object.daz'), 123);
should.equal(snapshot.get('nonexistent.object'), undefined);
should.equal(get('nonexistent.object'), undefined);
});
it('using a FieldPath instance', async () => {
const snapshot = await testCollectionDoc(COL_DOC_1_PATH).get();
should.equal(snapshot.get('foo'), 'bar');
should.equal(
snapshot.get(new firebase.firestore.FieldPath('foo')),
'bar'
);
should.equal(
snapshot.get(new firebase.firestore.FieldPath('object', 'daz')),
123
);
should.equal(
snapshot.get(
new firebase.firestore.FieldPath('nonexistent', 'object')
),
undefined
);
});
});
});
});

View File

@ -0,0 +1,40 @@
const {
COL_DOC_1,
COL_DOC_1_PATH,
testCollectionDoc,
resetTestCollectionDoc,
} = TestHelpers.firestore;
describe('firestore()', () => {
describe('FieldPath', () => {
before(async () => {
await resetTestCollectionDoc(COL_DOC_1_PATH, COL_DOC_1());
});
it('documentId() should return a FieldPath', () => {
const documentId = firebase.firestore.FieldPath.documentId();
documentId.should.be.instanceof(firebase.firestore.FieldPath);
});
it('should allow getting values via documentSnapshot.get(FieldPath)', async () => {
const snapshot = await testCollectionDoc(COL_DOC_1_PATH).get();
should.equal(snapshot.get('foo'), 'bar');
should.equal(
snapshot.get(new firebase.firestore.FieldPath('foo')),
'bar'
);
should.equal(
snapshot.get(new firebase.firestore.FieldPath('object', 'daz')),
123
);
should.equal(
snapshot.get(new firebase.firestore.FieldPath('nonexistent', 'object')),
undefined
);
});
});
});

View File

@ -0,0 +1,50 @@
const {
DOC_2,
DOC_2_PATH,
testCollectionDoc,
resetTestCollectionDoc,
} = TestHelpers.firestore;
describe('firestore()', () => {
describe('FieldValue', () => {
before(async () => {
await resetTestCollectionDoc(DOC_2_PATH, DOC_2);
});
describe('delete()', () => {
it('should delete a field', async () => {
const { data } = await testCollectionDoc(DOC_2_PATH).get();
should.equal(data().title, DOC_2.title);
await testCollectionDoc(DOC_2_PATH).update({
title: firebase.firestore.FieldValue.delete(),
});
const { data: dataAfterUpdate } = await testCollectionDoc(
DOC_2_PATH
).get();
should.equal(dataAfterUpdate().title, undefined);
});
});
describe('serverTimestamp()', () => {
it('should set timestamp', async () => {
const { data } = await testCollectionDoc(DOC_2_PATH).get();
should.equal(data().creationDate, undefined);
await testCollectionDoc(DOC_2_PATH).update({
creationDate: firebase.firestore.FieldValue.serverTimestamp(),
});
const { data: dataAfterUpdate } = await testCollectionDoc(
DOC_2_PATH
).get();
dataAfterUpdate().creationDate.should.be.instanceof(
bridge.context.window.Date
);
});
});
});
});

View File

@ -0,0 +1,123 @@
describe('firestore()', () => {
describe('collection()', () => {
it('should create CollectionReference with the right id', () => {
firebase
.firestore()
.collection('collection1/doc1/collection2')
.id.should.equal('collection2');
});
it('should error if invalid collection path supplied', () => {
(() => {
firebase.firestore().collection('collection1/doc1');
}).should.throw('Argument "collectionPath" must point to a collection.');
});
});
describe('doc()', () => {
it('should create DocumentReference with correct path', () => {
firebase
.firestore()
.doc('collection1/doc1/collection2/doc2')
.path.should.equal('collection1/doc1/collection2/doc2');
});
it('should error if invalid document path supplied', () => {
(() => {
firebase.firestore().doc('collection1');
}).should.throw('Argument "documentPath" must point to a document.');
});
});
describe('disable/enableNetwork()', () => {
it('calls without error', async () => {
await firebase.firestore().disableNetwork();
await firebase.firestore().enableNetwork();
});
});
describe('enablePersistence()', () => {
it('calls without error', async () => {
await firebase.firestore().enablePersistence();
});
});
describe('setLogLevel()', () => {
it('should set level from string', () => {
firebase.firestore.setLogLevel('debug');
firebase.firestore.setLogLevel('error');
firebase.firestore.setLogLevel('silent');
// test deprecated method
firebase.firestore.enableLogging(true);
firebase.firestore.enableLogging(false);
});
it('should throw an invalid parameter error', () => {
(() => {
firebase.firestore.setLogLevel('warn');
}).should.throw(
'Argument `logLevel` must be one of: `debug`, `error`, `silent`'
);
});
});
describe('settings()', () => {
it('should reject invalid object', async () => {
try {
await firebase.firestore().settings('test');
} catch (error) {
return Promise.resolve();
}
return Promise.reject(new Error('Did not error on invalid object'));
});
it('should reject invalid host setting', async () => {
try {
await firebase.firestore().settings({ host: true });
} catch (error) {
return Promise.resolve();
}
return Promise.reject(
new Error('Did not error on invalid `host` setting')
);
});
it('should reject invalid persistence setting', async () => {
try {
await firebase.firestore().settings({ persistence: 'fail' });
} catch (error) {
return Promise.resolve();
}
return Promise.reject(
new Error('Did not error on invalid `persistence` setting')
);
});
it('should reject invalid ssl setting', async () => {
try {
await firebase.firestore().settings({ ssl: 'fail' });
} catch (error) {
return Promise.resolve();
}
return Promise.reject(
new Error('Did not error on invalid `ssl` setting')
);
});
it('should reject invalid timestampsInSnapshots setting', async () => {
try {
await firebase.firestore().settings({ timestampsInSnapshots: 'fail' });
} catch (error) {
return Promise.resolve();
}
return Promise.reject(
new Error('Did not error on invalid `timestampsInSnapshots` setting')
);
});
});
});

View File

@ -0,0 +1,119 @@
describe('firestore()', () => {
describe('Path', () => {
describe('id', () => {
it('returns the document id', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection/documentId');
path.id.should.be.equal('documentId');
});
it('returns null if no path', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('');
should.equal(path.id, null);
});
});
describe('isDocument', () => {
it('returns true if path is a document', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection/documentId');
path.isDocument.should.be.equal(true);
});
it('returns false if path is a collection', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection');
path.isDocument.should.be.equal(false);
});
});
describe('isCollection', () => {
it('returns true if path is a collection', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection');
path.isCollection.should.be.equal(true);
});
it('returns false if path is a document', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection/documentId');
path.isCollection.should.be.equal(false);
});
});
describe('relativeName', () => {
it('returns original full path', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection');
const path2 = Path.fromName('collection/documentId');
path.relativeName.should.be.equal('collection');
path2.relativeName.should.be.equal('collection/documentId');
});
});
describe('child()', () => {
it('returns original path joined with the provided child path', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection');
const path2 = path.child('documentId');
path.relativeName.should.be.equal('collection');
path2.relativeName.should.be.equal('collection/documentId');
});
});
describe('parent()', () => {
it('returns the parent of the current child path', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection/documentId');
const path2 = path.parent();
path.relativeName.should.be.equal('collection/documentId');
path2.relativeName.should.be.equal('collection');
});
it('returns null if no path', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('');
const path2 = path.parent();
path._parts.length.should.be.equal(0);
should.equal(path2, null);
});
});
describe('static fromName()', () => {
it('returns a new instance from a / delimited path string', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('collection/document');
path.should.be.instanceOf(Path);
path._parts.length.should.be.equal(2);
});
it('returns a new instance from an empty string', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName('');
path.should.be.instanceOf(Path);
path._parts.length.should.be.equal(0);
});
});
it('returns a new instance with no args provided', async () => {
const Path = bridge.require('dist/modules/firestore/Path');
const path = Path.fromName();
path.should.be.instanceOf(Path);
path._parts.length.should.be.equal(0);
});
});
});

View File

@ -0,0 +1,167 @@
const { testDocRef } = TestHelpers.firestore;
describe('firestore()', () => {
describe('runTransaction()', () => {
it('should set() values', async () => {
const firestore = firebase.firestore();
const docRef = testDocRef('tSet');
const updateFunction = async transaction => {
const doc = await transaction.get(docRef);
if (!doc.exists) {
transaction.set(docRef, { value: 1, somethingElse: 'set' });
return 1;
}
return 0;
};
const result = await firestore.runTransaction(updateFunction);
should.equal(result, 1);
const finalDoc = await docRef.get();
finalDoc.data().value.should.equal(1);
finalDoc.data().somethingElse.should.equal('set');
});
it('should update() values', async () => {
const firestore = firebase.firestore();
const docRef = testDocRef('tUpdate');
await docRef.set({ value: 1 });
const updateFunction = async transaction => {
const doc = await transaction.get(docRef);
if (doc.exists) {
transaction.update(docRef, {
value: doc.data().value + 1,
somethingElse: 'update',
});
return 1;
}
return 0;
};
const result = await firestore.runTransaction(updateFunction);
should.equal(result, 1);
const finalDoc = await docRef.get();
finalDoc.data().value.should.equal(2);
finalDoc.data().somethingElse.should.equal('update');
});
it('should delete() values', async () => {
const firestore = firebase.firestore();
const docRef = testDocRef('tDelete');
await docRef.set({ value: 1, somethingElse: 'delete' });
const updateFunction = async transaction => {
const doc = await transaction.get(docRef);
if (doc.exists) {
transaction.delete(docRef);
return 1;
}
return 0;
};
const result = await firestore.runTransaction(updateFunction);
should.equal(result, 1);
const finalDoc = await docRef.get();
finalDoc.exists.should.equal(false);
});
it('error if updateFn does return a promise', async () => {
const firestore = firebase.firestore();
// test async functions - they always return a promise in JS
let didReject = false;
let updateFunction = async () => 1;
try {
await firestore.runTransaction(updateFunction);
} catch (e) {
didReject = true;
}
should.equal(didReject, false);
// should not error as a promise returned
didReject = false;
updateFunction = () => Promise.resolve();
try {
await firestore.runTransaction(updateFunction);
} catch (e) {
didReject = true;
}
should.equal(didReject, false);
// should error as no promise returned
didReject = false;
updateFunction = () => '123456';
try {
await firestore.runTransaction(updateFunction);
} catch (e) {
didReject = true;
e.message.includes('must return a Promise');
}
should.equal(didReject, true);
});
it('updateFn promise rejections / js exceptions handled', async () => {
const firestore = firebase.firestore();
// rejections
let didReject = false;
// eslint-disable-next-line
let updateFunction = () => Promise.reject('shoop');
try {
await firestore.runTransaction(updateFunction);
} catch (e) {
didReject = true;
should.equal(e, 'shoop');
}
should.equal(didReject, true);
// exceptions
didReject = false;
updateFunction = () => {
// eslint-disable-next-line no-throw-literal
throw 'doop';
};
try {
await firestore.runTransaction(updateFunction);
} catch (e) {
didReject = true;
should.equal(e, 'doop');
}
should.equal(didReject, true);
});
it('handle native exceptions', async () => {
const firestore = firebase.firestore();
const docRef = testDocRef('tSet');
const blockedRef = firestore.doc('denied/foo');
const updateFunction = async transaction => {
await transaction.get(docRef);
transaction.set(blockedRef, { value: 1, somethingElse: 'set' });
return 1;
};
// rejections
let didReject = false;
try {
await firestore.runTransaction(updateFunction);
} catch (e) {
// TODO sdks are giving different errors - standardise?
if (device.getPlatform() === 'ios') {
e.message.should.containEql('firestore/failed-precondition');
} else {
e.message.should.containEql('firestore/aborted');
}
didReject = true;
}
should.equal(didReject, true);
});
});
});

View File

@ -0,0 +1,251 @@
const TEST_DATA = TestHelpers.functions.data;
describe('functions()', () => {
it('accepts passing in an FirebaseApp instance as first arg', async () => {
const appName = `functionsApp${global.testRunId}1`;
const platformAppConfig = TestHelpers.core.config();
const app = await firebase
.initializeApp(platformAppConfig, appName)
.onReady();
const functionsForApp = firebase.functions(app);
functionsForApp.app.should.equal(app);
functionsForApp.app.name.should.equal(app.name);
// check from an app
app.functions().app.should.equal(app);
app.functions().app.name.should.equal(app.name);
});
it('accepts passing in a region string as first arg', async () => {
const region = 'europe-west1';
const functionsForRegion = firebase.functions(region);
// check internal region property
functionsForRegion._customUrlOrRegion.should.equal(region);
// app should be default app
functionsForRegion.app.should.equal(firebase.app());
functionsForRegion.app.name.should.equal(firebase.app().name);
firebase
.app()
.functions(region)
.app.should.equal(firebase.app());
firebase
.app()
.functions(region)
._customUrlOrRegion.should.equal(region);
const functionRunner = functionsForRegion.httpsCallable(
'runTestWithRegion'
);
const response = await functionRunner();
// the function just sends back it's region as a string
response.data.should.equal(region);
});
// TODO app and region test both args
// TODO app passed to existing app instance - should error?
describe('httpsCallable(fnName)(args)', () => {
it('accepts primitive args: undefined', async () => {
const functionRunner = firebase.functions().httpsCallable('runTest');
const response = await functionRunner();
response.data.should.equal('null');
});
it('accepts primitive args: string', async () => {
const functionRunner = firebase.functions().httpsCallable('runTest');
const response = await functionRunner('hello');
response.data.should.equal('string');
});
it('accepts primitive args: number', async () => {
const functionRunner = firebase.functions().httpsCallable('runTest');
const response = await functionRunner(123);
response.data.should.equal('number');
});
it('accepts primitive args: boolean', async () => {
const functionRunner = firebase.functions().httpsCallable('runTest');
const response = await functionRunner(true);
response.data.should.equal('boolean');
});
it('accepts primitive args: null', async () => {
const functionRunner = firebase.functions().httpsCallable('runTest');
const response = await functionRunner(null);
response.data.should.equal('null');
});
it('accepts array args', async () => {
const functionRunner = firebase.functions().httpsCallable('runTest');
const response = await functionRunner([1, 2, 3, 4]);
response.data.should.equal('array');
});
it('accepts object args', async () => {
const type = 'simpleObject';
const inputData = TEST_DATA[type];
const functionRunner = firebase.functions().httpsCallable('runTest');
const { data: outputData } = await functionRunner({
type,
inputData,
});
should.deepEqual(outputData, inputData);
});
it('accepts complex nested objects', async () => {
const type = 'advancedObject';
const inputData = TEST_DATA[type];
const functionRunner = firebase.functions().httpsCallable('runTest');
const { data: outputData } = await functionRunner({
type,
inputData,
});
should.deepEqual(outputData, inputData);
});
it('accepts complex nested arrays', async () => {
const type = 'advancedArray';
const inputData = TEST_DATA[type];
const functionRunner = firebase.functions().httpsCallable('runTest');
const { data: outputData } = await functionRunner({
type,
inputData,
});
should.deepEqual(outputData, inputData);
});
});
describe('HttpsError', () => {
it('errors return instance of HttpsError', async () => {
const functionRunner = firebase.functions().httpsCallable('runTest');
try {
await functionRunner({});
return Promise.reject(new Error('Function did not reject with error.'));
} catch (e) {
should.equal(e.details, null);
e.code.should.equal('invalid-argument');
e.message.should.equal('Invalid test requested.');
}
return Promise.resolve();
});
it('HttpsError.details -> allows returning complex data', async () => {
let type = 'advancedObject';
let inputData = TEST_DATA[type];
const functionRunner = firebase.functions().httpsCallable('runTest');
try {
await functionRunner({
type,
inputData,
asError: true,
});
return Promise.reject(new Error('Function did not reject with error.'));
} catch (e) {
should.deepEqual(e.details, inputData);
e.code.should.equal('cancelled');
e.message.should.equal(
'Response data was requested to be sent as part of an Error payload, so here we are!'
);
}
type = 'advancedArray';
inputData = TEST_DATA[type];
try {
await functionRunner({
type,
inputData,
asError: true,
});
return Promise.reject(new Error('Function did not reject with error.'));
} catch (e) {
should.deepEqual(e.details, inputData);
e.code.should.equal('cancelled');
e.message.should.equal(
'Response data was requested to be sent as part of an Error payload, so here we are!'
);
}
return Promise.resolve();
});
it('HttpsError.details -> allows returning primitives', async () => {
let type = 'number';
let inputData = TEST_DATA[type];
const functionRunner = firebase.functions().httpsCallable('runTest');
try {
await functionRunner({
type,
inputData,
asError: true,
});
return Promise.reject(new Error('Function did not reject with error.'));
} catch (e) {
e.code.should.equal('cancelled');
e.message.should.equal(
'Response data was requested to be sent as part of an Error payload, so here we are!'
);
should.deepEqual(e.details, inputData);
}
type = 'string';
inputData = TEST_DATA[type];
try {
await functionRunner({
type,
inputData,
asError: true,
});
return Promise.reject(new Error('Function did not reject with error.'));
} catch (e) {
should.deepEqual(e.details, inputData);
e.code.should.equal('cancelled');
e.message.should.equal(
'Response data was requested to be sent as part of an Error payload, so here we are!'
);
}
type = 'boolean';
inputData = TEST_DATA[type];
try {
await functionRunner({
type,
inputData,
asError: true,
});
return Promise.reject(new Error('Function did not reject with error.'));
} catch (e) {
should.deepEqual(e.details, inputData);
e.code.should.equal('cancelled');
e.message.should.equal(
'Response data was requested to be sent as part of an Error payload, so here we are!'
);
}
type = 'null';
inputData = TEST_DATA[type];
try {
await functionRunner({
type,
inputData,
asError: true,
});
return Promise.reject(new Error('Function did not reject with error.'));
} catch (e) {
should.deepEqual(e.details, inputData);
e.code.should.equal('cancelled');
e.message.should.equal(
'Response data was requested to be sent as part of an Error payload, so here we are!'
);
}
return Promise.resolve();
});
});
});

60
tests/e2e/iid/iid.e2e.js Normal file
View File

@ -0,0 +1,60 @@
describe('iid()', () => {
describe('get()', () => {
it('returns instance id string', async () => {
const iid = await firebase.iid().get();
iid.should.be.a.String();
});
});
describe('delete()', () => {
it('deletes the current instance id', async () => {
const iidBefore = await firebase.iid().get();
iidBefore.should.be.a.String();
await firebase.iid().delete();
const iidAfter = await firebase.iid().get();
iidAfter.should.be.a.String();
iidBefore.should.not.equal(iidAfter);
await sleep(4000);
});
});
describe('getToken()', () => {
it('should return an FCM token from getToken with arguments', async () => {
const authorizedEntity = firebase.iid().app.options.messagingSenderId;
const token = await firebase.iid().getToken(authorizedEntity, '*');
token.should.be.a.String();
});
it('should return an FCM token from getToken without arguments', async () => {
const token = await firebase.iid().getToken();
token.should.be.a.String();
});
it('should return an FCM token from getToken with 1 argument', async () => {
const authorizedEntity = firebase.iid().app.options.messagingSenderId;
const token = await firebase.iid().getToken(authorizedEntity);
token.should.be.a.String();
});
});
describe('deleteToken()', () => {
it('should return nil from deleteToken with arguments', async () => {
const authorizedEntity = firebase.iid().app.options.messagingSenderId;
const token = await firebase.iid().deleteToken(authorizedEntity, '*');
should.not.exist(token);
});
it('should return nil from deleteToken without arguments', async () => {
const token = await firebase.iid().deleteToken();
should.not.exist(token);
});
it('should return nil from deleteToken with 1 argument', async () => {
const authorizedEntity = firebase.iid().app.options.messagingSenderId;
const token = await firebase.iid().deleteToken(authorizedEntity);
should.not.exist(token);
});
});
});

15
tests/e2e/init.js Executable file
View File

@ -0,0 +1,15 @@
const detox = require('detox');
const config = require('../package.json').detox;
before(async () => {
await detox.init(config);
// needs to be called before any usage of firestore
await firebase.firestore().settings({ persistence: true });
await firebase.firestore().settings({ persistence: false });
});
after(async () => {
console.log('Cleaning up...');
await TestHelpers.firestore.cleanup();
await detox.cleanup();
});

8
tests/e2e/mocha.opts Executable file
View File

@ -0,0 +1,8 @@
--recursive
--timeout 120000
--reporter list
--slow 600
--bail
--exit
--require jet/platform/node
--require ./helpers

View File

@ -0,0 +1,93 @@
describe('perf()', () => {
describe('HttpMetric', () => {
it('start() & stop()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.stop();
});
it('removeAttribute()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.putAttribute('foo', 'bar');
const value = await httpMetric.getAttribute('foo');
should.equal(value, 'bar');
await httpMetric.removeAttribute('foo');
const value2 = await httpMetric.getAttribute('foo');
should.equal(value2, null);
await httpMetric.stop();
});
it('getAttribute() should return null', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
const value = await httpMetric.getAttribute('foo');
should.equal(value, null);
await httpMetric.removeAttribute('foo');
await httpMetric.stop();
});
it('getAttribute() should return string value', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.putAttribute('foo', 'bar');
const value = await httpMetric.getAttribute('foo');
should.equal(value, 'bar');
await httpMetric.removeAttribute('foo');
await httpMetric.stop();
});
it('putAttribute()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.putAttribute('foo', 'bar');
const value = await httpMetric.getAttribute('foo');
value.should.equal('bar');
await httpMetric.removeAttribute('foo');
await httpMetric.stop();
});
it('getAttributes()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.putAttribute('foo', 'bar');
await httpMetric.putAttribute('bar', 'baz');
const value = await httpMetric.getAttributes();
value.should.deepEqual({
foo: 'bar',
bar: 'baz',
});
await httpMetric.removeAttribute('foo');
await httpMetric.removeAttribute('bar');
await httpMetric.stop();
});
it('setHttpResponseCode()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.setHttpResponseCode(500);
await httpMetric.stop();
});
it('setRequestPayloadSize()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.setRequestPayloadSize(1234567);
await httpMetric.stop();
});
it('setResponseContentType()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.setResponseContentType('application/foobar');
await httpMetric.stop();
});
it('setResponsePayloadSize()', async () => {
const httpMetric = firebase.perf().newHttpMetric('http://foo.com', 'GET');
await httpMetric.start();
await httpMetric.setResponsePayloadSize(123456789);
await httpMetric.stop();
});
});
});

View File

@ -0,0 +1,49 @@
describe('perf()', () => {
describe('setPerformanceCollectionEnabled()', () => {
it('true', async () => {
await firebase.perf().setPerformanceCollectionEnabled(true);
});
it('false', async () => {
await firebase.perf().setPerformanceCollectionEnabled(false);
await firebase.perf().setPerformanceCollectionEnabled(true);
await device.launchApp({ newInstance: true });
});
it('errors if not boolean', async () => {
(() => firebase.perf().setPerformanceCollectionEnabled()).should.throw(
'firebase.perf().setPerformanceCollectionEnabled() requires a boolean value'
);
});
});
describe('newTrace()', () => {
it('returns an instance of Trace', async () => {
const trace = firebase.perf().newTrace('foo');
trace.constructor.name.should.be.equal('Trace');
});
it('errors if identifier not a string', async () => {
(() => firebase.perf().newTrace([1, 2, 3, 4])).should.throw(
'firebase.perf().newTrace() requires a string value'
);
});
});
describe('newHttpMetric()', () => {
it('returns an instance of HttpMetric', async () => {
const trace = firebase.perf().newHttpMetric('http://foo.com', 'GET');
trace.constructor.name.should.be.equal('HttpMetric');
});
it('errors if url/httpMethod not a string', async () => {
(() => firebase.perf().newHttpMetric(123, [1, 2])).should.throw(
'firebase.perf().newHttpMetric() requires url and httpMethod string values'
);
});
it('errors if httpMethod not a valid type', async () => {
(() => firebase.perf().newHttpMetric('foo', 'FOO')).should.throw(); // TODO error
});
});
});

View File

@ -0,0 +1,88 @@
describe('perf()', () => {
describe('Trace', () => {
it('start() & stop()', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
await trace.stop();
});
it('getAttribute() should return null', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
const value = await trace.getAttribute('foo');
should.equal(value, null);
await trace.stop();
});
it('getAttribute() should return string value', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
await trace.putAttribute('foo', 'bar');
const value = await trace.getAttribute('foo');
should.equal(value, 'bar');
await trace.stop();
});
it('putAttribute()', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
await trace.putAttribute('foo', 'bar');
const value = await trace.getAttribute('foo');
value.should.equal('bar');
await trace.stop();
});
it('getAttributes()', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
await trace.putAttribute('foo', 'bar');
await trace.putAttribute('bar', 'baz');
const value = await trace.getAttributes();
value.should.deepEqual({
foo: 'bar',
bar: 'baz',
});
await trace.stop();
});
it('removeAttribute()', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
await trace.putAttribute('foobar', 'bar');
const value = await trace.getAttribute('foobar');
value.should.equal('bar');
await trace.removeAttribute('foobar');
const removed = await trace.getAttribute('foobar');
should.equal(removed, null);
await trace.stop();
});
it('getMetric()', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
const metric = await trace.getMetric('foo');
metric.should.equal(0);
await trace.stop();
});
it('putMetric()', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
await trace.putMetric('baz', 1);
const metric = await trace.getMetric('baz');
metric.should.equal(1);
await trace.stop();
});
it('incrementMetric()', async () => {
const trace = firebase.perf().newTrace('bar');
await trace.start();
await trace.putMetric('baz', 1);
await trace.incrementMetric('baz', 2);
const metric = await trace.getMetric('baz');
metric.should.equal(3);
await trace.stop();
});
});
});

View File

@ -0,0 +1,147 @@
describe('storage()', () => {
describe('ref()', () => {
describe('toString()', () => {
it('returns the correct bucket path to the file', () => {
const app = firebase.app();
firebase
.storage()
.ref('/uploadNope.jpeg')
.toString()
.should.equal(`gs://${app.options.storageBucket}/uploadNope.jpeg`);
});
});
describe('downloadFile()', () => {
it('errors if permission denied', async () => {
try {
await firebase
.storage()
.ref('/not.jpg')
.downloadFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/not.jpg`
);
return Promise.reject(new Error('No permission denied error'));
} catch (error) {
error.code.should.equal('storage/unauthorized');
error.message.includes('not authorized').should.be.true();
return Promise.resolve();
}
});
it('downloads a file', async () => {
const meta = await firebase
.storage()
.ref('/ok.jpeg')
.downloadFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`
);
meta.state.should.eql(firebase.storage.TaskState.SUCCESS);
meta.bytesTransferred.should.eql(meta.totalBytes);
});
});
describe('putFile()', () => {
before(async () => {
await firebase
.storage()
.ref('/ok.jpeg')
.downloadFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`
);
await firebase
.storage()
.ref('/cat.gif')
.downloadFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/cat.gif`
);
await firebase
.storage()
.ref('/hei.heic')
.downloadFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/hei.heic`
);
});
it('errors if permission denied', async () => {
try {
await firebase
.storage()
.ref('/uploadNope.jpeg')
.putFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`
);
return Promise.reject(new Error('No permission denied error'));
} catch (error) {
error.code.should.equal('storage/unauthorized');
error.message.includes('not authorized').should.be.true();
return Promise.resolve();
}
});
it('uploads a file', async () => {
const uploadTaskSnapshot = await firebase
.storage()
.ref('/uploadOk.jpeg')
.putFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`
);
await firebase
.storage()
.ref('/uploadCat.gif')
.putFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/cat.gif`
);
await firebase
.storage()
.ref('/uploadHei.heic')
.putFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/hei.heic`
);
uploadTaskSnapshot.state.should.eql(firebase.storage.TaskState.SUCCESS);
uploadTaskSnapshot.bytesTransferred.should.eql(
uploadTaskSnapshot.totalBytes
);
uploadTaskSnapshot.metadata.should.be.an.Object();
uploadTaskSnapshot.downloadURL.should.be.a.String();
});
});
describe('on()', () => {
before(async () => {
await firebase
.storage()
.ref('/ok.jpeg')
.downloadFile(
`${firebase.storage.Native.DOCUMENT_DIRECTORY_PATH}/ok.jpeg`
);
});
it('listens to upload state', () => {
const { resolve, reject, promise } = Promise.defer();
const path = `${
firebase.storage.Native.DOCUMENT_DIRECTORY_PATH
}/ok.jpeg`;
const ref = firebase.storage().ref('/uploadOk.jpeg');
const unsubscribe = ref.putFile(path).on(
firebase.storage.TaskEvent.STATE_CHANGED,
snapshot => {
if (snapshot.state === firebase.storage.TaskState.SUCCESS) {
resolve();
}
},
error => {
unsubscribe();
reject(error);
}
);
return promise;
});
});
});
});

1
tests/firebase.json Normal file
View File

@ -0,0 +1 @@
{}

68
tests/functions/index.js Normal file
View File

@ -0,0 +1,68 @@
const assert = require('assert');
const functions = require('firebase-functions');
const TEST_DATA = require('./test-data');
exports.runTestWithRegion = functions
.region('europe-west1')
.https.onCall(() => 'europe-west1');
exports.runTest = functions.https.onCall(data => {
console.log(Date.now(), data);
if (typeof data === 'undefined') {
return 'undefined';
}
if (typeof data === 'string') {
return 'string';
}
if (typeof data === 'number') {
return 'number';
}
if (typeof data === 'boolean') {
return 'boolean';
}
if (data === null) {
return 'null';
}
if (Array.isArray(data)) {
return 'array';
}
const { type, asError, inputData } = data;
if (!Object.hasOwnProperty.call(TEST_DATA, type)) {
throw new functions.https.HttpsError(
'invalid-argument',
'Invalid test requested.'
);
}
const outputData = TEST_DATA[type];
try {
assert.deepEqual(outputData, inputData);
} catch (e) {
console.error(e);
throw new functions.https.HttpsError(
'invalid-argument',
'Input and Output types did not match.',
e.message
);
}
// all good
if (asError) {
throw new functions.https.HttpsError(
'cancelled',
'Response data was requested to be sent as part of an Error payload, so here we are!',
outputData
);
}
return outputData;
});

3886
tests/functions/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"firebase-admin": "^6.0.0",
"firebase-functions": "^2.0.5"
},
"private": true
}

View File

@ -0,0 +1,41 @@
module.exports = {
number: 1234,
string: 'acde',
boolean: true,
null: null,
simpleObject: {
number: 1234,
string: 'acde',
boolean: true,
null: null,
},
simpleArray: [1234, 'acde', true, null],
advancedObject: {
array: [1234, 'acde', false, null],
object: {
number: 1234,
string: 'acde',
boolean: true,
null: null,
array: [1234, 'acde', true, null],
},
number: 1234,
string: 'acde',
boolean: true,
null: null,
},
advancedArray: [
1234,
'acde',
true,
null,
[1234, 'acde', true, null],
{
number: 1234,
string: 'acde',
boolean: true,
null: null,
array: [1234, 'acde', true, null],
},
],
};

View File

@ -0,0 +1,118 @@
module.exports = {
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',
},
},
QUERY: [
{
search: 'foo',
},
{
search: 'bar',
},
{
search: 'blah',
},
],
ISSUES: {
// https://github.com/invertase/react-native-firebase/issues/100
100: {
1: {
someKey: 'someValue',
someOtherKey: 'someOtherValue',
},
2: {
someKey: 'someValue',
someOtherKey: 'someOtherValue',
},
3: {
someKey: 'someValue',
someOtherKey: 'someOtherValue',
},
},
// https://github.com/invertase/react-native-firebase/issues/108
108: {
foobar: {
name: 'Foobar Pizzas',
latitude: 34.1013717,
},
notTheFoobar: {
name: "Not the pizza you're looking for",
latitude: 34.456787,
},
notAFloat: {
name: 'Not a float',
latitude: 37,
},
},
// https://github.com/invertase/react-native-firebase/issues/171
171: {
10053768200609241: {
email: 'emaila@hotmail.com',
name: 'Sek Ranger',
profile_picture: 'https://url.to/picture',
uid: 'n6V8vACidyW4OKxnELkBbW83JaS2',
},
10053925505239749: {
email: 'emailb@hotmail.com',
name: 'Gu Hungry',
profile_picture: 'https://url.to/picture',
uid: 'Qq4Pwm7H2kO6sJIMLAJxuhAGGh22',
},
10106631429240929: {
email: 'emailc@gmail.com',
name: 'Chwang',
profile_picture: 'https://url.to/picture',
uid: 'T7VVrveS0dPs3idmgetLUfQsLZs1',
},
10106631429240930: {
email: 'emaild@gmail.com',
name: 'Introv Bigs',
profile_picture: 'https://url.to/picture',
uid: 'aNYxLexOb2WsXGOPiEAu47q5bxH3',
},
},
489: {
long1: 1508777379000,
},
// https://github.com/invertase/react-native-firebase/issues/521
521: {
key1: {
name: 'Item 1',
number: 1,
string: 'item1',
},
key3: {
name: 'Item 3',
number: 3,
string: 'item3',
},
key2: {
name: 'Item 2',
number: 2,
string: 'item2',
},
},
},
};

View File

@ -0,0 +1,18 @@
const CONTENTS = require('./content');
module.exports = {
CONTENTS,
setDatabaseContents() {
const database = firebaseAdmin.database();
return Promise.all([
database.ref('tests/types').set(CONTENTS.DEFAULT),
database.ref('tests/priority').setWithPriority(
{
foo: 'bar',
},
666
),
database.ref('tests/query').set(CONTENTS.QUERY),
]);
},
};

168
tests/helpers/firestore.js Normal file
View File

@ -0,0 +1,168 @@
const TEST_COLLECTION_NAME = 'tests';
const TEST2_COLLECTION_NAME = 'tests2';
// const TEST3_COLLECTION_NAME = 'tests3';
let shouldCleanup = false;
const ONE_HOUR = 60 * 60 * 1000;
module.exports = {
async cleanup() {
if (!shouldCleanup) return Promise.resolve();
await Promise.all([
module.exports.cleanCollection(TEST_COLLECTION_NAME),
module.exports.cleanCollection(TEST2_COLLECTION_NAME),
]);
// await module.exports.cleanCollection(`${TEST_COLLECTION_NAME}3`);
// await module.exports.cleanCollection(`${TEST_COLLECTION_NAME}4`);
return Promise.resolve();
},
TEST_COLLECTION_NAME,
TEST2_COLLECTION_NAME,
// TEST3_COLLECTION_NAME,
DOC_1: { name: 'doc1' },
DOC_1_PATH: `tests/doc1${testRunId}`,
DOC_2: { name: 'doc2', title: 'Document 2' },
DOC_2_PATH: `tests/doc2${testRunId}`,
// needs to be a fn as firebase may not yet be available
COL_DOC_1() {
shouldCleanup = true;
return {
baz: true,
daz: 123,
foo: 'bar',
gaz: 12.1234567,
geopoint: new firebase.firestore.GeoPoint(0, 0),
naz: null,
object: {
daz: 123,
},
timestamp: new bridge.context.window.Date(2017, 2, 10, 10, 0, 0),
};
},
// needs to be a fn as firebase may not yet be available
COL2_DOC_1() {
shouldCleanup = true;
return {
baz: true,
daz: 123,
foo: 'bar',
gaz: 12.1234567,
geopoint: new firebase.firestore.GeoPoint(0, 0),
naz: null,
object: {
daz: 123,
},
timestamp: new bridge.context.window.Date(2017, 2, 10, 10, 0, 0),
};
},
COL_DOC_1_ID: `col1${testRunId}`,
COL_DOC_1_PATH: `${TEST_COLLECTION_NAME}/col1${testRunId}`,
COL2_DOC_1_ID: `doc1${testRunId}`,
COL2_DOC_1_PATH: `${TEST2_COLLECTION_NAME}/doc1${testRunId}`,
/**
* Removes all documents on the collection for the current testId or
* documents older than 24 hours
*
* @param collectionName
* @return {Promise<*>}
*/
async cleanCollection(collectionName) {
const firestore = firebaseAdmin.firestore();
const collection = firestore.collection(
collectionName || TEST_COLLECTION_NAME
);
const docsToDelete = (await collection.get()).docs;
const yesterday = new Date(new Date() - 24 * ONE_HOUR);
if (docsToDelete.length) {
const batch = firestore.batch();
for (let i = 0, len = docsToDelete.length; i < len; i++) {
const { ref } = docsToDelete[i];
if (
ref.path.includes(testRunId) ||
new Date(docsToDelete[i].createTime) <= yesterday
) {
batch.delete(ref);
}
}
if (!batch._writes.length) return Promise.resolve();
return batch.commit();
}
return Promise.resolve();
},
testDocRef(docId) {
shouldCleanup = true;
return firebase
.firestore()
.collection(TEST_COLLECTION_NAME)
.doc(
docId.startsWith(testRunId) || docId.endsWith(testRunId)
? docId
: `${testRunId}${docId}`
);
},
test2DocRef(docId) {
shouldCleanup = true;
return firebase
.firestore()
.collection(TEST2_COLLECTION_NAME)
.doc(
docId.startsWith(testRunId) || docId.endsWith(testRunId)
? docId
: `${testRunId}${docId}`
);
},
testCollection(collection) {
shouldCleanup = true;
return firebase.firestore().collection(collection);
},
testCollectionDoc(path) {
shouldCleanup = true;
return firebase.firestore().doc(path);
},
testCollectionDocAdmin(path) {
shouldCleanup = true;
return firebaseAdmin.firestore().doc(path);
},
async resetTestCollectionDoc(path, doc) {
shouldCleanup = true;
const _doc = doc || module.exports.COL_DOC_1();
await firebase
.firestore()
.doc(path || module.exports.COL_DOC_1_PATH)
.set(_doc);
return _doc;
},
};
firebaseAdmin.firestore().settings({ timestampsInSnapshots: true });
// call a get request without waiting to force firestore to connect
// so the first test isn't delayed whilst connecting
module.exports
.testCollectionDocAdmin(module.exports.DOC_1_PATH)
.get()
.then(() => {})
.catch(() => {});

153
tests/helpers/index.js Normal file
View File

@ -0,0 +1,153 @@
/* eslint-disable global-require */
global.sinon = require('sinon');
require('should-sinon');
global.should = require('should');
Object.defineProperty(global, 'firebase', {
get() {
return bridge.module;
},
});
// TODO move as part of bridge
const { Uint8Array } = global;
Object.defineProperty(global, 'Uint8Array', {
get() {
const { stack } = new Error();
if (
(stack.includes('Context.it') || stack.includes('Context.beforeEach')) &&
global.bridge &&
global.bridge.context
) {
return bridge.context.window.Uint8Array;
}
return Uint8Array;
},
});
// TODO move as part of bridge
const { Array } = global;
Object.defineProperty(global, 'Array', {
get() {
const { stack } = new Error();
if (
(stack.includes('Context.it') || stack.includes('Context.beforeEach')) &&
global.bridge &&
global.bridge.context
) {
return bridge.context.window.Array;
}
return Array;
},
});
global.isObject = function isObject(item) {
return item
? typeof item === 'object' && !Array.isArray(item) && item !== null
: false;
};
global.sleep = duration =>
new Promise(resolve => setTimeout(resolve, duration));
global.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;
};
global.testRunId = randomString(4, 'aA#');
/** ------------------
* Init WEB SDK
---------------------*/
/** ------------------
* Init ADMIN SDK
---------------------*/
global.firebaseAdmin = require('firebase-admin');
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(require('./service-account')),
databaseURL: 'https://rnfirebase-b9ad4.firebaseio.com',
});
const originalLog = console.log;
console.log = (...args) => {
if (
args &&
args[0] &&
typeof args[0] === 'string' &&
(args[0].toLowerCase().includes('deprecated') ||
args[0].toLowerCase().includes('restrictions in the native sdk'))
) {
return undefined;
}
return originalLog(...args);
};
/**
* Old style deferred promise shim - for niceness
*
* @returns {{resolve: null, reject: null}}
*/
Promise.defer = function defer() {
const deferred = {
resolve: null,
reject: null,
};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
};
const androidTestConfig = {
// firebase android sdk completely ignores client id
clientId:
'305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com',
appId: '1:305229645282:android:af36d4d29a83e04c',
apiKey: 'AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM',
databaseURL: 'https://rnfirebase-b9ad4.firebaseio.com',
storageBucket: 'rnfirebase-b9ad4.appspot.com',
messagingSenderId: '305229645282',
projectId: 'rnfirebase-b9ad4',
};
const iosTestConfig = {
clientId:
'305229645282-22imndi01abc2p6esgtu1i1m9mqrd0ib.apps.googleusercontent.com',
androidClientId: androidTestConfig.clientId,
appId: '1:305229645282:ios:af36d4d29a83e04c',
apiKey: 'AIzaSyAcdVLG5dRzA1ck_fa_xd4Z0cY7cga7S5A',
databaseURL: 'https://rnfirebase-b9ad4.firebaseio.com',
storageBucket: 'rnfirebase-b9ad4.appspot.com',
messagingSenderId: '305229645282',
projectId: 'rnfirebase-b9ad4',
};
global.TestHelpers = {
functions: {
data: require('./../functions/test-data'),
},
firestore: require('./firestore'),
database: require('./database'),
core: {
config() {
const config =
device.getPlatform() === 'ios' ? iosTestConfig : androidTestConfig;
return { ...config };
},
},
};

View File

@ -0,0 +1,12 @@
{
"type": "service_account",
"project_id": "rnfirebase-b9ad4",
"private_key_id": "749dd48dde76108c94ee0d97282868c452eb80d9",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDj1+lFlzUikGV8\nRFCrW320Cn2NgRkP09keSFY9NWWvWTE3vsYAjmLFmb1M0LKF5HfmrMUZ0tbafll5\nm4gw9T5Px/Ry5lrwrnmNNc6yR9Oj1kXoUtESo8f8mxxmufEv+Z1Fk0mq4T0nSRn8\nPsrHbNlwGWMdpi63NEvbDnq/aH/E0//g+7T3xcGg6dxjJM44qxfU5z5AYovsc+yI\nyAuMS54PvicQa2MSIywqtapCX3QIOlcz2KXqf7t3KhPnA+M6XhO3vetcoCbeGwwC\na5DRaKepcnrQslOltDUJ158+d5BKcZ0Wr0ctU9HI9VoJjUjj//krXHG/KeePbPW+\niUhM4mBnAgMBAAECggEAGxypqvjDv2GsWd2tNb7U1fQ7nXeDunDskKBt9qmgeH2m\nECqY2B8ZXMcfEgFxhOI7bE0ZxSlkHKrVTdW2npDIQekaywNjRemVGDsTZf6LDh9k\nwTD9pPitRv9UIVs1+o3663kL1pQn2UFnIK9+JL3sn9y9gR0xyOYlsqLl81nlfk+8\nTFV42cIXmGZov/GuHcgijdFpVwa2SF2jiV1ZmnWFabsObn8N3Rhbu5UsjYfv0hk2\nCdZSMhJvPwbNW0sJUtRJGQCfoTHGTApfVgfNac7kY4CRCZW79b5DcbD9aXpbZuKH\no9MNXQ7v9CskMU/02GmGuKUhuUx0WNq7em2II4LbMQKBgQD5lP+ha5av6C/W7CuJ\n6wB/yhQz9xus+RQ+bTIkWWtycvJTFxDpZ03+y/TxByRVeEEO90mT/0zFvVVwjQq0\njJD3FjyhHZvd9V7wCD5HjBGoLDJC8oHZUx9EN2VWx3P/DmjksQLJaK6k2URMk2Nl\nzOiz61vHtzfLZW2fInMtei3w0QKBgQDps86a6vkjujQmDNjMkZwSzLvLKYxKHH3u\n6GVWPeKN9BE8KialdBH+nDlfxYOntjMV8bwXReUy9zLjQxztStMjUW7i4WXUenr3\n78Qo/5sKoy8INv5RJF7mz7UhqiY4UKr/t92o4e7rrE+5wTNrB8gJ4IfVO8HM8Vok\nj1llyB5LtwKBgBZNGgpycqSFOsEZmUpZlVHV3LhH/FEYcJajazeUReWUH9MM3JX+\nhRmfX+Opn7WDaUzNC+YNie6hXGESOUKozMtHUWOUIblTk4gfNHFwMqO0T0lsIe4p\nX0HZMVTvvY2s2/KTXoxgrjpMr62n/dm61ZA+F5Vg6bti9Mija2dE45YBAoGALt4P\nZgQPeLnD8YmMBOKCsUZ7ts+bQdWa78mNYRFCGQPEXRN68p1nam88FlfPAlhIwHLd\nLLGouGLYwrjX2UKzOxb+rONU/5qchJKB/VLdbfGx4ezdbbpkiddH1PuBdLPAx03B\nVzgQKRVCW9dCD4nc9RYhR4MXZ5lTYZeZ7w6F/NsCgYBJFhawdZjjBhzFpS5wPMCc\n4Z8kf7cum1hRJLVjJDE8lhMfn+3aqKq8YwynLur0bYn/M8z/NWhiak5pDvO98JQ2\nsFkiTv1t0oNOOo/h7ZdEWIU3U9+SpjxsH8U5bLD/xCpR2ZPtrKgLGAwfd/miyDhx\nYMkyfnONKey1VWH9JVqRHg==\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-g5d00@rnfirebase-b9ad4.iam.gserviceaccount.com",
"client_id": "115889980718069163179",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-g5d00%40rnfirebase-b9ad4.iam.gserviceaccount.com"
}

1
tests/index.android.js Executable file
View File

@ -0,0 +1 @@
require('./app');

1
tests/index.js Executable file
View File

@ -0,0 +1 @@
require('./app');

108
tests/package.json Executable file
View File

@ -0,0 +1,108 @@
{
"name": "react-native-firebase-tests",
"version": "0.0.1",
"private": true,
"scripts": {
"packager-chrome": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android",
"packager-jet": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android",
"packager-jet-reset-cache": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --reset-cache",
"build-android": "detox build --configuration android.emu.debug",
"build-ios": "detox build --configuration ios.sim.debug",
"test": "npm run test-android && test-ios",
"test-android": "detox test --configuration android.emu.debug",
"test-android-reuse": "detox test --configuration android.emu.debug --reuse",
"test-android-cover": "nyc detox test --configuration android.emu.debug",
"test-android-cover-reuse": "nyc detox test --configuration android.emu.debug --reuse",
"test-ios": "detox test --configuration ios.sim.debug --loglevel warn",
"test-ios-reuse": "detox test --configuration ios.sim.debug --reuse --loglevel warn",
"test-ios-cover": "nyc detox test --configuration ios.sim.debug",
"test-ios-cover-reuse": "nyc detox test --configuration ios.sim.debug --reuse --loglevel warn",
"ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .."
},
"dependencies": {
"jet": "0.0.2",
"detox": "^8.1.1",
"fbjs": "^0.8.16",
"firebase-admin": "^5.12.0",
"jsonwebtoken": "^8.2.1",
"mocha": "^5.2.0",
"prop-types": "^15.6.1",
"react": "^16.4.1",
"react-native": "^0.56.0",
"should": "^13.2.1",
"should-sinon": "0.0.6",
"sinon": "^6.1.4"
},
"devDependencies": {
"@babel/cli": "7.0.0-beta.47",
"@babel/core": "7.0.0-beta.47",
"babel-eslint": "^8.2.6",
"babel-plugin-istanbul": "^5.0.1",
"babel-plugin-module-resolver": "^3.1.1",
"babel-preset-react-native": "^5.0.2",
"eslint": "^5.3.0",
"eslint-config-airbnb": "^17.0.0",
"eslint-plugin-flowtype": "^2.46.3",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.7.0",
"flow-bin": "^0.78.0",
"nyc": "^12.0.2",
"rimraf": "^2.6.2"
},
"nyc": {
"check-coverage": false,
"lines": 95,
"statements": 95,
"functions": 95,
"branches": 95,
"include": [
"**/dist/**"
],
"exclude": [
"node_modules",
"**/dist/utils/emitter**",
"**/dist/modules/admob**",
"**/dist/modules/auth/phone**"
],
"sourceMap": true,
"instrument": true,
"cwd": "..",
"reporter": [
"lcov",
"text-summary"
]
},
"detox": {
"test-runner": "mocha",
"specs": "e2e",
"runner-config": "e2e/mocha.opts",
"configurations": {
"ios.sim.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/testing.app",
"build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace ios/testing.xcworkspace -scheme testing -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -quiet",
"type": "ios.simulator",
"name": "iPhone 7 Plus"
},
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/testing.app",
"build": "xcodebuild -workspace ios/testing.xcworkspace -scheme testing -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 7 Plus",
"logLevel": "error"
},
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "pushd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && popd",
"type": "android.emulator",
"name": "TestingAVD"
},
"android.emu.release": {
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"build": "pushd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && popd",
"type": "android.emulator",
"name": "TestingAVD"
}
}
}
}

28
tests/rn-cli.config.js Executable file
View File

@ -0,0 +1,28 @@
const { resolve } = require('path');
let metroBundler;
try {
metroBundler = require('metro');
} catch (ex) {
metroBundler = require('metro-bundler');
}
const blacklist = metroBundler.createBlacklist;
module.exports = {
getProjectRoots() {
return [__dirname, resolve(__dirname, '..')];
},
getProvidesModuleNodeModules() {
return ['react-native', 'react', 'prop-types', 'fbjs'];
},
getBlacklistRE() {
return blacklist([
new RegExp(`^${escape(resolve(__dirname, '..', 'node_modules'))}\\/.*$`),
new RegExp(`^${escape(resolve(__dirname, '..', 'tests'))}\\/.*$`),
new RegExp(
`^${escape(resolve(__dirname, '..', 'tests', 'node_modules'))}\\/.*$`
),
]);
},
};