eas-cli: how to build with releaseChannel? - react-native

I have an environment with API_URL changed depend on releaseChannel
for example in production I want to build with API_URL="https:prod.APPNAME.com".
and for staging : API_URL="https:staging.APPNAME.com".
how can I do that using eas.json
my environment.js:
import Constants from "expo-constants";
// import {API_URL} from '#env';
import { commonProperties } from "./environments.common";
const dev_URL = "http://10.30.157.214:8080/";
const staging_URL = "https:staging.APPNAME.com";
const production_URL = "https:prod.APPNAME.com";
const environment = {
production: false,
dev: {
apiUrl: dev_URL,
...commonProperties,
},
staging: {
apiUrl: staging_URL,
...commonProperties,
},
prod: {
apiUrl: production_URL,
...commonProperties,
},
};
const getEnvVars = (env = Constants.manifest!.releaseChannel) => {
// What is __DEV__ ?
// This variable is set to true when react-native is running in Dev mode.
// __DEV__ is true when run locally, but false when published.
if (__DEV__) {
return environment.dev;
} else if (env === "staging") {
return environment.staging;
} else if (env === "prod") {
return environment.prod;
}
// return environment.prod;
};
export default getEnvVars;
my eas.json:
{
"build": {
"preview": {
"android": {
"buildType": "apk"
}
},
"staging": {
"android": {
"gradleCommand": ":app:assembleRelease"
},
"releaseChannel": "staging"
},
"productionTest": {
"android": {
"gradleCommand": ":app:assembleRelease"
},
"releaseChannel": "prod"
},
"production": {
"autoIncrement": true,
"releaseChannel": "prod"
}
}
}
when i use the command: eas build -p android --profile productionTest the build done with Constants.manifest!.releaseChannel undefined.
is there's any solution to this problem?

Related

Not Receiving Expo Notifications on Android apk when app is killed from menu tray

I did everything right following expo documentation.
Configure Firebase.
Download json file from Firebase.
adding credentials to expo bundle identifier.
Finally I build my app using eas build -p android --profile local
When i download this APK, notifications work fine for me in two states:
When app is open
When app is in background (but not cleared from system tray)
But notifications are no received when I clear out the app (kill app)
My app.json android object structure:
"android": {
"package": "www.blabla.com",
"googleServicesFile": "./google-services.json",
"versionCode": 20,
"useNextNotificationsApi": true,
"config": {
"googleMaps": {
"apiKey": "blablabla"
}
},
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
My eas.json:
{
"cli": {
"version": ">= 2.5.1"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"local": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {}
},
"submit": {
"production": {}
}
}
My useEffect:
useEffect(() => {
registerForPushNotificationsAsync()
.then((token) => {
setExpoPushToken(token), saveTokenOnline(token);
})
.catch((error) => {
toast.show(error.message, {
type: "danger",
placement: "bottom",
duration: 2000,
animationType: "zoom-in",
});
console.log(error);
console.log(error.message);
});
// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current =
Notifications.addNotificationReceivedListener((notification) => {
setNotification(notification);
});
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current =
Notifications.addNotificationResponseReceivedListener((response) => {
console.log(response);
});
// Do unmounting stuff here
return () => {
Notifications.removeNotificationSubscription(
notificationListener.current
);
Notifications.removeNotificationSubscription(responseListener.current);
};
}, []);
other function:
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== "granted") {
alert("Failed to get push token for push notification!");
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
console.log(token);
saveToken(token);
if (!token) {
toast.show("Error getting Token", {
type: "danger",
placement: "bottom",
duration: 2000,
animationType: "zoom-in",
});
}
} else {
alert("Must use physical device for Push Notifications");
}
if (Platform.OS === "android") {
Notifications.setNotificationChannelAsync("default", {
name: "default",
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#FF231F7C",
});
}
return token;
}
I need notifications to work even when app is killed

Express, Ejs and i18next how to switch between languages?

I have been trying to figure out how to make the switcher or language selector work from the frontend part of the website. Also my first time working with SSR, so I am not sure about the communication in between. So far what I handled and works well, the inclusion for the translation rendering is working, also when i manually change the language for testing purpose.
I have been reading the documentation, but there is not specific tutorial about how to handle this, more than the function "changeLanguage" and some solutions I found are older as 5 years and involves, deprecated version and some kind of extra library to handled some template views, and I don't know if if this actually is still feasible. However inside the repository from i18next-http-middleware, there is an example that basically does kind what I want. So can be found under the "basic-pug" folder. The difference is that it is used "Pug" as a template engine, which I don't think it should affect or make any difference. so I used exactly the same configuration as the example, which i downloaded and tested and it was working, so it supposed enabled a cookie, and then on the url can pass the cookie as a param, with the locale, and it supposed to work, however not working with in my project, and I also wondering if it possible to have "/de/" instead "/?lang=de", example www.myweb.com/de/about and not www.myweb.com/about/?lang=de, because that's not exactly what I was imagining, and also no clear how to handled when u have nested or several routes.
In general, I was wondering how to use the "changelanguage" function in the frontend side, so I have, for example, something like:
<div>
<div onclick="Changelanguage("de")"> Deutsch</div>
<div onclick="Changelanguage("en")"> English</div>
</div>
Because by declaring the function just like in the views, on a script tag, I the changelanguage is not a function, because there is no communication between both.
Please find below my configuration:
Json file:
{
"name": "project",
"version": "0.0.0",
"private": true,
"author": "project",
"main": "dist/index.js",
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon src/index.js --exec \"node -r dotenv/config -r #babel/register\"",
"clean": "rimraf dist",
"build": "npm run clean && mkdir -p dist && babel src -s -D -d dist",
"postinstall": "npm run build"
},
"dependencies": {
"#babel/cli": "^7.18.9",
"#babel/core": "^7.18.9",
"#babel/plugin-proposal-class-properties": "^7.18.6",
"#babel/plugin-proposal-object-rest-spread": "^7.18.9",
"#babel/preset-env": "^7.18.9",
"body-parser": "^1.19.0",
"ejs": "^3.1.8",
"express": "^4.18.1",
"graphql": "^16.5.0",
"graphql-request": "^4.3.0",
"i18next": "^21.9.1",
"i18next-fs-backend": "^1.1.5",
"i18next-http-middleware": "^3.2.1",
"locomotive-scroll": "^4.1.4",
"morgan": "^1.10.0",
"node-sass-middleware": "^1.0.1",
"rimraf": "^3.0.0"
},
"devDependencies": {
"#babel/register": "^7.18.9",
"dotenv": "^16.0.1",
"nodemon": "^2.0.19"
},
"babel": {
"presets": [
[
"#babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
],
"plugins": [
"#babel/plugin-proposal-class-properties",
"#babel/plugin-proposal-object-rest-spread"
]
}
}
My routes file js
import { GraphQLClient
} from "graphql-request";
import { Router
} from "express";
import * as queries from "./queries";
import i18next from "i18next";
const routes = Router();
export function request({ query, variables, preview, includeDrafts
}) {
// ...some headers and client configs
};
return client.request(query, variables);
}
let generalInfo = null;
const getData = async (options) => {
options.page = options.page ? options.page : "";
if (!generalInfo) {
generalInfo = await request({
query: queries.getGeneralInfo,
variables: {
locale: options.lang
}
});
}
let q = queries.getPage(options.page);
let data = await request({
query: q,
variables: {
locale: options.lang
}
});
data = Object.assign(data, generalInfo);
return data;
};
const getCase = async (slug, lang) => {
if (!generalInfo) {
generalInfo = await request({
query: queries.getGeneralInfo,
variables: {
locale: lang
}
});
}
slug = slug ? slug : "";
let q = queries.getCase;
let data = await request({
query: q,
variables: {
locale: lang
}
});
data = Object.assign(data, generalInfo);
const filter = data.allCases.filter((n) => n.slug == slug)
return filter;
};
const getInnerPage = async (slug, lang) => {
if (!generalInfo) {
generalInfo = await request({
query: queries.getGeneralInfo,
variables: {
locale: lang
}
});
}
slug = slug ? slug : "";
let q = queries.getInner;
let data = await request({
query: q,
variables: {
locale: lang
}
});
data = Object.assign(data, generalInfo);
const filter = data.allPages.filter((n) => n.slug == slug);
return filter;
};
routes.get("/cases/:slug", (req, res) => {
let slug = req.params.slug;
getCase(slug, req.locale).then(filter => {
if (filter.length) {
res.render("case",
{ title: filter.project_title, filter, t: i18next.t
});
} else {
res.render("404");
}
}).catch(e => {
console.error(e);
});
});
routes.get("/page/:slug", (req, res) => {
let slug = req.params.slug;
getInnerPage(slug, req.locale).then(filter => {
if (filter.length) {
res.render("page",
{ title: filter[
0
].titlePage, filter, t: i18next.t
});
} else {
res.render("404");
}
}).catch(e => {
console.error(e);
});
});
routes.get("/", (req, res) => {
let options = { 'lang': req.locale, 'page': ""
}
//console.log(req.locale + " locale selected");
getData(options).then(data => {
res.render("home",
{ title: "Home", data, t: i18next.t
});
}).catch(e => {
console.error(e);
});
});
export default routes;
My index js
import express from "express";
import path from "path";
import logger from "morgan";
import bodyParser from "body-parser";
import routes from "./routes";
import sassMiddleware from "node-sass-middleware";
import i18next from "i18next";
import i18nextBackend from "i18next-fs-backend";
import i18nextMiddleware from "i18next-http-middleware";
const { PORT = 5050 } = process.env;
const app = express();
i18next.use(i18nextBackend)
.use(i18nextMiddleware.LanguageDetector)
.init({
//lng: 'de',
debug: true,
fallbackLng: 'en',
preload: ['de', 'en'],
backend: {
loadPath: './locales/{{lng}}/translation.json'
},
detection: {
order: ['querystring', 'cookie'],
caches: ['cookie'],
lookupQuerystring: 'lang',
lookupCookie: 'lang',
ignoreCase: true,
cookieSecure: false
},
})
app.use(i18nextMiddleware.handle(i18next));
//i18next.changeLanguage('de');
app.set("views", path.join(__dirname, "../views"));
app.set("view engine", "ejs");
app.use(logger("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.locals = { config: { whatever: 'this is' } };
app.use(
sassMiddleware({
src: path.join(__dirname, "../public/scss"),
dest: path.join(__dirname, "../public"),
indentedSyntax: false,
sourceMap: false
})
);
app.use(express.static(path.join(__dirname, "../public")));
app.use("/", routes);
app.use(function (req, res) {
res.status(404).render('404.ejs');
});
app.use(function (req, res) {
res.status(500).render('500.ejs');
});
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
export default app;
Thank you in advance for any help, I think I am mainly struggling because I use to work on client side, but still learning about SSR.
You should be able to define the language also in the route, like described here: https://github.com/i18next/i18next-http-middleware#language-detection
Define the 'path' in the detection options: order: ['path', /*'session', */ 'querystring', 'cookie', 'header'],
Regarding the language change. You probably just need to navigate to the route with the appropriate language.

Coinbase Oauth Redirect URL Issue with Expo App

I am trying to integrate Coinbase oauth2 to my react-native expo app. I have followed the below guide: https://docs.expo.dev/guides/authentication/#coinbase
Below is my app.json
{
"expo": {
"name": "My App",
"slug": "my-app",
"privacy": "public",
"platforms": [
"ios",
"android",
"web"
],
"version": "1.1.13",
"facebookScheme": "fbxxxxxxxxxxxx",
"facebookAppId": "xxxxxxxxxxxxxxxxxxxx",
"facebookDisplayName": "Ubuntu",
"orientation": "portrait",
"icon": "./assets/app_logo.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "cover",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.package.app",
"infoPlist": {
"NSCameraUsageDescription": "This app uses the camera to upload documents."
},
"googleServicesFile": "./GoogleService-Info.plist",
"usesIcloudStorage": true
},
"android": {
"package": "com.package.app",
"useNextNotificationsApi": true
},
"web": {
"build": {
"babel": {
"include": [
"static-container"
]
}
}
},
"scheme": "com.package.app"
}
}
App.native.js
import {
exchangeCodeAsync,
makeRedirectUri,
TokenResponse,
useAuthRequest
} from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import * as React from "react";
import { Button, View } from "react-native";
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: "https://www.coinbase.com/oauth/authorize",
tokenEndpoint: "https://api.coinbase.com/oauth/token",
revocationEndpoint: "https://api.coinbase.com/oauth/revoke",
};
const redirectUri = makeRedirectUri({
// scheme: 'com.package.app',
// path: 'redirect',
native: 'com.package.app://redirect',
useProxy: false
});
const CLIENT_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: CLIENT_ID,
scopes: ["wallet:accounts:read"],
redirectUri // -> tried this as well redirectUri: 'urn:ietf:wg:oauth:2.0:oob'
},
discovery
);
const {
// The token will be auto exchanged after auth completes.
token,
exchangeError,
} = useAutoExchange(
response?.type === "success" ? response.params.code : null
);
React.useEffect(() => {
if (token) {
console.log("My Token:", token.accessToken);
}
}, [token]);
return (
<View style={{ marginTop: 300,}}>
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
</View>
);
}
// A hook to automatically exchange the auth token for an access token.
// this should be performed in a server and not here in the application.
// For educational purposes only:
function useAutoExchange(code) {
const [state, setState] = React.useReducer(
(state, action) => ({ ...state, ...action }),
{ token: null, exchangeError: null }
);
const isMounted = useMounted();
React.useEffect(() => {
if (!code) {
setState({ token: null, exchangeError: null });
return;
}
exchangeCodeAsync(
{
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
code,
redirectUri,
},
discovery
)
.then((token) => {
if (isMounted.current) {
setState({ token, exchangeError: null });
}
})
.catch((exchangeError) => {
if (isMounted.current) {
setState({ exchangeError, token: null });
}
});
}, [code]);
return state;
}
function useMounted() {
const isMounted = React.useRef(true);
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
}
I have added com.package.app to the permitted redirect URL as well.
Does anyone help me find why am I still getting 'The redirect URI included is not valid'?
Expo SDK version: 43
Developing platform: Windows
Packages used:
"expo-auth-session": "~3.4.2"
"expo-web-browser": "~10.0.3"
"react": "17.0.0"
"react-native": "0.64.3"
Thank you.

I need help converting a Serverless Nestjs app to a regular Express app

I have a server that was built from the ground up using Serverless and Nestjs but we want to move it to ecs/ec2 because it needs to run 24/7 now. Is it possible to extract out serverless or would I be better off re-writing the bootstraping part of the app?
index.ts with the handler function is used in sls deployment while the main.ts is used for development.
package.json:
"scripts": {
"format": "prettier --write \"**/*.ts\"",
"start": "ts-node -r tsconfig-paths/register src/main.ts",
"start:dev": "nodemon",
"prestart:prod": "rm -rf dist && tsc",
"start:prod": "rm -rf dist && tsc && node dist/main.js",
"start:hmr": "node dist/server",
"webpack": "webpack --config webpack.config.js",
"local-server": "rm -rf dist && tsc && export AWS_REGION=us-east-1 && export IS_ON_LOCAL_SERVER=true && export TZ=UTC && sls offline start --stage dev",
"local-prod-server": "rm -rf dist && tsc && export AWS_REGION=us-east-1 && export IS_ON_LOCAL_SERVER=true && export TZ=UTC && sls offline start --stage prod",
"deploy:prod": "rm -rf dist && tsc && sls deploy --stage prod",
"deploy:dev": "rm -rf dist && tsc && sls deploy --stage dev"
},
Here is my index.ts:
import * as serverless from 'aws-serverless-express';
import * as bunyan from 'bunyan';
import * as audit from 'express-requests-logger';
import { Server } from 'http';
import { NestFactory } from '#nestjs/core';
import * as bodyParser from 'body-parser';
import { AppModule } from './app.module';
import { FasLoggerService } from './fas-logger.service';
// tslint:disable-next-line:no-var-requires
const express = require('express')();
let cachedServer: Server;
export const logger: any = bunyan.createLogger({
name: 'app',
stream: process.stdout,
level: 'debug',
serializers: {}
});
function bootstrapServer(): Promise<Server> {
return NestFactory.create(AppModule, express, {
logger: new FasLoggerService()
})
.then(app => {
app.useLogger(app.get(FasLoggerService));
app.enableCors({
allowedHeaders: ['Content-Type', 'Authorization'],
origin: '*',
methods: ['POST', 'GET', 'PUT', 'DELETE', 'HEAD', 'PATCH'],
preflightContinue: false,
optionsSuccessStatus: 204
});
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(
audit({
logger,
excludeURLs: [],
doubleAudit: false,
request: {
excludeHeaders: ['authorization'],
excludeBody: ['*']
},
response: {
excludeBody: ['*']
}
})
);
return app;
})
.then(app => app.init())
.then(() => serverless.createServer(express));
}
export const handler: any = (event: any, context: any) => {
if (!cachedServer) {
bootstrapServer().then(server => {
cachedServer = server;
return serverless.proxy(server, event, context);
});
} else {
return serverless.proxy(cachedServer, event, context);
}
};
Here is my main.ts:
import * as bunyan from 'bunyan';
import * as audit from 'express-requests-logger';
import { NestFactory } from '#nestjs/core';
import * as bodyParser from 'body-parser';
import { AppModule } from './app.module';
import { FasLoggerService } from './fas-logger.service';
export const logger: any = bunyan.createLogger({
name: 'app',
stream: process.stdout,
level: 'debug',
serializers: {}
});
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: new FasLoggerService()
});
app.useLogger(app.get(FasLoggerService));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.enableCors({
allowedHeaders: ['Content-Type', 'Authorization'],
origin: '*',
methods: ['POST', 'GET', 'PUT', 'DELETE', 'HEAD', 'PATCH'],
preflightContinue: false,
optionsSuccessStatus: 204
});
app.use(
audit({
logger,
excludeURLs: [],
doubleAudit: false,
request: {
excludeHeaders: ['authorization'],
excludeBody: ['*']
},
response: {
excludeBody: ['*']
}
})
);
await app.listen(3000);
}
bootstrap();

Mocking RNCamera with Detox does not work, non-mock impl is called

I'm trying to mock out RNCamera in a Detox test. However, the mock camera does not override the real implementation.
I followed these instructions and incorporated advice from these posts.
The result is:
The mock camera class file itself is executed (top-level logging is printed)
Mock camera's takePictureAsync and render functions are not called
Emulator's default camera is used
I have also tried:
RN_SRC_EXT=e2e react-native run-android, then detox test
adding "test-runner": "jest" and callingjest.mock` in the best body
replacing RN_SRC_EXT=e2e with RN_SRC_EXT=e2e.js
cleanup: rm -rf node_modules; yarn install etc.
My app layout is like (non-test-relevant content excluded):
package.json:
{
...
"dependencies": {
...
"#types/jest": "^24.0.21",
"react": "16.9.0",
"react-dom": "latest",
"react-native": "0.61.1",
"react-native-camera": "^3.6.0",
},
"devDependencies": {
"#babel/core": "latest",
"#babel/preset-env": "latest",
"#babel/register": "latest",
"#babel/preset-react": "latest",
"#react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^24.9.0",
"detox": "^14.5.1",
"jest": "^24.9.0",
"jest-fetch-mock": "^2.1.2",
"metro-react-native-babel-preset": "^0.56.0",
"mocha": "^6.2.2",
"react-test-renderer": "16.9.0"
},
"detox": {
"configurations": {
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
},
"android.emu.release": {
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
}
}
}
}
e2e/config.js:
require('#babel/register')({
//cache: true,
presets: [require('metro-react-native-babel-preset')],
plugins: [require('#babel/plugin-transform-runtime').default],
only: ['./e2e', './js'],
ignore: ['node_modules']
});
e2e/config.json:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
e2e/mocha.opts:
--recursive
--timeout 300000
--bail
--file e2e/init.js
--require e2e/config.js
--require e2e/config.json
--file e2e/react-native-camera.e2e.js
metro.config.js:
const defaultSourceExts = require('metro-config/src/defaults/defaults').sourceExts
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
sourceExts: process.env.RN_SRC_EXT
? process.env.RN_SRC_EXT.split(',').concat(defaultSourceExts)
: defaultSourceExts
}
};
// printed as: module.exports.resolver from e2e { sourceExts: [ 'e2e', 'js', 'json', 'ts', 'tsx' ] }
console.log("module.exports from e2e", module.exports);
e2e/config.json:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
// tried with and without this line
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
test.spec.js
describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should blah blah balh', async () => {
// test implementation
});
});
init.js:
const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/mocha/adapter');
before(async () => {
await detox.init(config);
});
beforeEach(async function() {
await adapter.beforeEach(this);
});
afterEach(async function() {
await adapter.afterEach(this);
});
after(async () => {
await detox.cleanup();
});
e2e/react-native-camera.e2e.js: (from here)
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
// This IS printed on detox test -c android.emu.debug.
console.log("executing react-native-camera-e2e.js");
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
};
takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // Never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
// This is never printed.
console.log("mock RNCamera render()");
return null;
}
}
export const takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
export default RNCamera;
Mocking with detox and mocking with Jest are 2 different things.
In order to be able to mock third party module with detox, you can create a proxy component provider that will decide what component to use. So that you will be able to load the mocked version for detox testing.
In your case you can create a CameraProvider.js that will just export the real RNCamera:
export { RNCamera } from 'react-native-camera';
Then you will need to create the mocked version CameraProvider.e2e.js alongside the previous file for end-to-end testing with detox:
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
AutoFocus: { on: {} },
WhiteBalance: { auto: {} },
};
takePictureAsync = async () => {
console.log('mock RNCamera takePictureAsync');
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
console.log('mock RNCamera render()');
return null;
}
}
Finally, if you have correctly configured your metro bundler in the root folder of your react-native project, you should be able to either build your app or start the bundler (for debug targets) with the RN_SRC_EXT=e2e.js env variable to let the metro bundler know which file extensions it should replace.
Gomino's answer is correct but its missing that you will need to import from the proxy component in the code under test.
E.G. Do this in your component:
import { RNCamera } from '../proxies/RNCamera';
And not
import { RNCamera } from 'react-native-camera';
Then you need to make sure the bundler is started with the RN_SRC_EXT=e2e.js env variable. So my detox test scripts (in package.json) look something like this:
"start-detox-metro": "RN_SRC_EXT=e2e.js react-native start",
"detox-build": "detox build --configuration android.emu.debug",
"detox test": "detox test --configuration android.emu.debug --reuse",
Run the scripts in that order.
The metro bundler will handle swapping the .e2e.js files in place of the original proxy file (but only when the env var "RN_SRC_EXT=e2e.js" is set).