Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth, emulator): implement useEmulator with jest + e2e testing #4552

Merged
merged 8 commits into from
Nov 16, 2020
5 changes: 4 additions & 1 deletion .github/workflows/scripts/firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"indexes": "firestore.indexes.json"
},
"emulators": {
"auth": {
"port": "9099"
},
"firestore": {
"port": "8080"
},
Expand All @@ -12,4 +15,4 @@
"port": 4000
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/bin/bash
if ! [ -x "$(command -v firebase)" ]; then
echo "❌ Firebase tools CLI is missing."
echo "❌ Firebase-tools CLI is missing. Run 'npm i -g firebase-tools' or the equivalent"
exit 1
fi

EMU_START_COMMAND="firebase emulators:start --only firestore"
EMU_START_COMMAND="firebase emulators:start --only firestore,auth --project react-native-firebase-testing"

if [ "$1" == "--no-daemon" ]; then
$EMU_START_COMMAND
Expand All @@ -15,4 +15,4 @@ else
sleep 2
done
echo "Firestore emulator is online!"
fi
fi
24 changes: 19 additions & 5 deletions jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ jest.doMock('react-native', () => {
},
NativeModules: {
...ReactNative.NativeModules,
RNFBAdMobModule: {},
RNFBAdMobInterstitialModule: {},
RNFBAdMobRewardedModule: {},
RNFBAdsConsentModule: {},
RNFBAppModule: {
NATIVE_FIREBASE_APPS: [
{
Expand All @@ -25,13 +29,23 @@ jest.doMock('react-native', () => {
options: {},
},
],
addListener: jest.fn(),
eventsAddListener: jest.fn(),
eventsNotifyReady: jest.fn(),
},
RNFBAuthModule: {
APP_LANGUAGE: {
'[DEFAULT]': 'en-US',
},
APP_USER: {
'[DEFAULT]': 'jestUser',
},
addAuthStateListener: jest.fn(),
addIdTokenListener: jest.fn(),
useEmulator: jest.fn(),
},
RNFBPerfModule: {},
RNFBAdMobModule: {},
RNFBAdMobInterstitialModule: {},
RNFBAdMobRewardedModule: {},
RNFBAdsConsentModule: {},
RNFBCrashlyticsModule: {},
RNFBPerfModule: {},
},
},
ReactNative,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"tests:packager:chrome": "cd tests && node_modules/.bin/react-native start --reset-cache",
"tests:packager:jet": "cd tests && cross-env REACT_DEBUGGER=\"echo nope\" node_modules/.bin/react-native start --no-interactive",
"tests:packager:jet-reset-cache": "cd tests && cross-env REACT_DEBUGGER=\"echo nope\" node_modules/.bin/react-native start --reset-cache --no-interactive",
"tests:emulator:start": "cd ./.github/workflows/scripts && sh ./start-firestore-emulator.sh --no-daemon",
"tests:emulator:start-ci": "cd ./.github/workflows/scripts && sh ./start-firestore-emulator.sh",
"tests:emulator:start": "cd ./.github/workflows/scripts && sh ./start-firebase-emulator.sh --no-daemon",
"tests:emulator:start-ci": "cd ./.github/workflows/scripts && sh ./start-firebase-emulator.sh",
"tests:android:build": "cd tests && ./node_modules/.bin/detox build --configuration android.emu.debug",
"tests:android:build-release": "cd tests && ./node_modules/.bin/detox build --configuration android.emu.release",
"tests:android:test": "cd tests && ./node_modules/.bin/detox test --configuration android.emu.debug",
Expand Down
20 changes: 0 additions & 20 deletions packages/analytics/__tests__/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,26 +103,6 @@ describe('Analytics', () => {
);
});

it('call methods, getters & setters that fire a console.warn() & have no return value', () => {
const analytics = firebase.analytics();
// @ts-ignore test
const logEcommercePurchaseSpy = jest.spyOn(analytics, 'logEcommercePurchase');
// @ts-ignore test
const logPresentOfferSpy = jest.spyOn(analytics, 'logPresentOffer');
// @ts-ignore test
const logPurchaseRefundSpy = jest.spyOn(analytics, 'logPurchaseRefund');
// @ts-ignore test
analytics.logEcommercePurchase();
// @ts-ignore test
analytics.logPresentOffer();
// @ts-ignore test
analytics.logPurchaseRefund();

expect(logEcommercePurchaseSpy).toBeCalled();
expect(logPresentOfferSpy).toBeCalled();
expect(logPurchaseRefundSpy).toBeCalled();
});

describe('logEvent()', () => {
it('errors if name is not a string', () => {
// @ts-ignore test
Expand Down
43 changes: 0 additions & 43 deletions packages/analytics/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,6 @@ class FirebaseAnalyticsModule extends FirebaseModule {
return this.native.setAnalyticsCollectionEnabled(enabled);
}

setCurrentScreen(screenName, screenClassOverride) {
// eslint-disable-next-line no-console
console.warn(
'firebase.analytics().setCurrentScreen(), is now deprecated. Please use firebase.analytics().logScreenView() instead',
);
return this.logScreenView({
screen_name: screenName,
screen_class: screenClassOverride,
});
}

setSessionTimeoutDuration(milliseconds = 1800000) {
if (!isNumber(milliseconds)) {
throw new Error(
Expand Down Expand Up @@ -319,16 +308,6 @@ class FirebaseAnalyticsModule extends FirebaseModule {
),
);
}
/**
* logEcommercePurchase purchase is now deprecated, use logPurchase instead:
* https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event#public-static-final-string-ecommerce_purchase
*/
logEcommercePurchase() {
// eslint-disable-next-line no-console
console.warn(
'firebase.analytics().logEcommercePurchase(), "ECOMMERCE_PURCHASE" event is now deprecated. Please use firebase.analytics().logPurchase() instead',
);
}

logGenerateLead(object = {}) {
if (!isObject(object)) {
Expand Down Expand Up @@ -423,28 +402,6 @@ class FirebaseAnalyticsModule extends FirebaseModule {
);
}

/**
* Deprecated, use logRefundEvent instead:
* https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event#public-static-final-string-present_offer
*/
logPresentOffer() {
// eslint-disable-next-line no-console
console.warn(
'firebase.analytics().logPresentOffer(), "PRESENT_OFFER" event is now deprecated. Please use firebase.analytics().logViewPromotion() instead',
);
}

/**
* Deprecated, use logRefundEvent instead:
* https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event#public-static-final-string-purchase_refund
*/
logPurchaseRefund() {
// eslint-disable-next-line no-console
console.warn(
'firebase.analytics().logPurchaseRefund(), "PURCHASE_REFUND" event is now deprecated. Please use firebase.analytics().logRefund() instead',
);
}

logSelectContent(object) {
if (!isObject(object)) {
throw new Error(
Expand Down
11 changes: 11 additions & 0 deletions packages/app/e2e/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
exports.getE2eTestProject = function getE2eTestProject() {
return 'react-native-firebase-testing';
};

exports.getE2eEmulatorHost = function getE2eEmulatorHost() {
// Note that in most package implementations involving the emulator, we re-write
// localhost and 127.0.0.1 on Android to 10.0.2.2 (the Android emulator host interface)
// But this specific code is executing in the host context even during E2E test.
// So no re-write is necessary here.
return 'localhost';
};
46 changes: 46 additions & 0 deletions packages/auth/__tests__/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import auth, { firebase } from '../lib';

describe('Auth', () => {
describe('namespace', () => {
it('accessible from firebase.app()', () => {
const app = firebase.app();
expect(app.auth).toBeDefined();
expect(app.auth().useEmulator).toBeDefined();
});
});

describe('useEmulator()', () => {
it('useEmulator requires a string url', () => {
// @ts-ignore because we pass an invalid argument...
expect(() => auth().useEmulator()).toThrow(
'firebase.auth().useEmulator() takes a non-empty string',
);
expect(() => auth().useEmulator('')).toThrow(
'firebase.auth().useEmulator() takes a non-empty string',
);
// @ts-ignore because we pass an invalid argument...
expect(() => auth().useEmulator(123)).toThrow(
'firebase.auth().useEmulator() takes a non-empty string',
);
});

it('useEmulator requires a well-formed url', () => {
// No http://
expect(() => auth().useEmulator('localhost:9099')).toThrow(
'firebase.auth().useEmulator() unable to parse host and port from url',
);
// No port
expect(() => auth().useEmulator('http://localhost')).toThrow(
'firebase.auth().useEmulator() unable to parse host and port from url',
);
});

it('useEmulator -> remaps Android loopback to host', () => {
const foo = auth().useEmulator('http://localhost:9099');
expect(foo).toEqual(['10.0.2.2', 9099]);

const bar = auth().useEmulator('http://127.0.0.1:9099');
expect(bar).toEqual(['10.0.2.2', 9099]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -991,10 +991,17 @@ public void confirmationResultConfirm(
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);

PhoneAuthCredential credential = PhoneAuthProvider.getCredential(
mVerificationId,
verificationCode
);
PhoneAuthCredential credential = null;
try {
credential = PhoneAuthProvider.getCredential(
mVerificationId,
verificationCode
);
} catch (Exception e) {
Log.d(TAG, "confirmationResultConfirm::getCredential::failure", e);
promiseRejectAuthException(promise, e);
return;
}

firebaseAuth.signInWithCredential(credential).addOnCompleteListener(getExecutor(), task -> {
if (task.isSuccessful()) {
Expand Down Expand Up @@ -1307,13 +1314,18 @@ private void linkWithCredential(
if (exception instanceof FirebaseAuthUserCollisionException) {
FirebaseAuthUserCollisionException authUserCollisionException = (FirebaseAuthUserCollisionException) exception;
AuthCredential updatedCredential = authUserCollisionException.getUpdatedCredential();
firebaseAuth.signInWithCredential(updatedCredential).addOnCompleteListener(getExecutor(), result -> {
if (result.isSuccessful()) {
promiseWithAuthResult(result.getResult(), promise);
} else {
promiseRejectAuthException(promise, exception);
}
});
try {
firebaseAuth.signInWithCredential(updatedCredential).addOnCompleteListener(getExecutor(), result -> {
if (result.isSuccessful()) {
promiseWithAuthResult(result.getResult(), promise);
} else {
promiseRejectAuthException(promise, exception);
}
});
} catch (Exception e) {
// we the attempt to log in after the collision failed, reject back to JS
promiseRejectAuthException(promise, exception);
}
} else {
promiseRejectAuthException(promise, exception);
}
Expand Down Expand Up @@ -1649,6 +1661,14 @@ public void verifyPasswordResetCode(String appName, String code, final Promise p
});
}

@ReactMethod
public void useEmulator(String appName, String host, int port) {
Log.d(TAG, "useEmulator");
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
firebaseAuth.useEmulator(host, port);
}

/* ------------------
* INTERNAL HELPERS
* ---------------- */
Expand Down
Loading