[tests][bridge] switch to using published npm alpha version of bridge \o/

This commit is contained in:
Salakar 2018-03-28 02:33:13 +01:00
parent 531617cd53
commit 2ba1b0bf11
9 changed files with 0 additions and 531 deletions

View File

@ -1,32 +0,0 @@
const chalk = require('chalk');
module.exports = function consoleContext() {
return {
...console,
/**
* Override console log so we can ignore certain logs like the application being started
*
* @param args
*/
log(...args) {
if (
args[0] &&
typeof args[0] === 'string' &&
args[0].startsWith('Running application "')
) {
return;
}
console.log(...args);
},
warn(...args) {
console.log(
...[
'⚠️ ',
...args.map(a => (typeof a === 'string' ? chalk.yellowBright(a) : a)),
]
);
},
};
};

View File

@ -1,101 +0,0 @@
/* eslint-disable guard-for-in,no-restricted-syntax */
global.bridge.context = null;
const consoleContext = require('./console');
const { createContext } = require('vm');
const chalk = require('chalk');
let customBridgeProps = [];
let driftCheckStart = null;
module.exports = {
/**
* Cleanup existing context - just some quick iterations over common fb/rn/bridge locations
* garbage collection will do the rest. This is probably not needed...
*/
async cleanup() {
if (global.bridge.context) {
if (global.bridge.beforeContextReset) {
await global.bridge.beforeContextReset();
}
for (const name in global.bridge.context.__fbBatchedBridge) {
global.bridge.context.__fbBatchedBridge[name] = undefined;
delete global.bridge.context.__fbBatchedBridge[name];
}
for (const name in global.bridge.context.__fbGenNativeModule) {
global.bridge.context.__fbGenNativeModule[name] = undefined;
delete global.bridge.context.__fbGenNativeModule[name];
}
for (const name in global.bridge.context.__fbBatchedBridgeConfig) {
global.bridge.context.__fbBatchedBridgeConfig[name] = undefined;
delete global.bridge.context.__fbBatchedBridgeConfig[name];
}
for (const name in global.bridge.context) {
global.bridge.context[name] = undefined;
delete global.bridge.context[name];
}
global.bridge.context = undefined;
// clear custom props and reset props track array
for (let i = 0; i < customBridgeProps.length; i++) {
global.bridge[customBridgeProps[i]] = undefined;
delete global.bridge[customBridgeProps[i]];
}
customBridgeProps = [];
}
},
/**
* Create a new context for a RN app to attach to, we additionally provide __bridgeNode for
* the counterpart RN bridge code to attach to and communicate back
*/
create() {
global.bridge.context = createContext({
console: consoleContext(),
__bridgeNode: {
_ready() {
if (!driftCheckStart) {
driftCheckStart = Date.now();
global.bridge.context.__driftCheck(1);
} else {
setTimeout(() => process.emit('bridge-attached'), 1);
}
},
_callbackDriftCheck() {
const timeTaken = Date.now() - driftCheckStart;
if (timeTaken > 5000) {
console.log(
`${chalk.blue(
'[bridge] ⚠️ '
)} It looks like there's an issue with device timer performance...`
);
console.log(
`${chalk.blue(
'[bridge] ⚠️ '
)} You may experience slow testing times as a result - ensure your device date/time correctly matches your debugger machine.`
);
// todo android only
// fake the RN warning for this
global.bridge.context.console.warn(
`Debugger and device times have drifted. ` +
`Please correct this by running adb shell "date \`date +%m%d%H%M%Y.%S\`" on your debugger machine.`
);
}
setTimeout(() => process.emit('bridge-attached'), 1);
},
setBridgeProperty(key, value) {
customBridgeProps.push(key);
global.bridge[key] = value;
},
},
});
},
};

View File

@ -1,23 +0,0 @@
const { createCoverageMap } = require('istanbul-lib-coverage');
const rootMap = createCoverageMap({});
module.exports = {
collect() {
if (bridge.context && bridge.context.__coverage__) {
try {
rootMap.merge(Object.assign({}, bridge.context.__coverage__));
global.__coverage__ = rootMap.toJSON();
} catch (e) {
// ignore
}
}
},
summary() {
return rootMap.getCoverageSummary();
},
json() {
return rootMap.toJSON();
},
};

View File

@ -1,81 +0,0 @@
/* eslint-disable no-param-reassign,global-require */
global.bridge = {};
require('./source-map');
const ws = require('./ws');
const ready = require('./ready');
const coverage = require('./coverage');
let detox;
try {
detox = require('detox');
} catch (e) {
// ignore
}
if (detox) {
/* ---------------------
* DEVICE OVERRIDES
* --------------------- */
let device;
Object.defineProperty(global, 'device', {
get() {
return device;
},
set(originalDevice) {
// device.reloadReactNative({ ... })
// todo detoxOriginalReloadReactNative currently broken it seems
// const detoxOriginalReloadReactNative = originalDevice.reloadReactNative.bind(originalDevice);
originalDevice.reloadReactNative = async () => {
ready.reset();
global.bridge.reload();
return ready.wait();
};
// device.launchApp({ ... })
const detoxOriginalLaunchApp = originalDevice.launchApp.bind(
originalDevice
);
originalDevice.launchApp = async (...args) => {
ready.reset();
await detoxOriginalLaunchApp(...args);
return ready.wait();
};
device = originalDevice;
return originalDevice;
},
});
/* -------------------
* DETOX OVERRIDES
* ------------------- */
// detox.init()
const detoxOriginalInit = detox.init.bind(detox);
detox.init = async (...args) => {
ready.reset();
await detoxOriginalInit(...args);
return ready.wait();
};
// detox.cleanup()
const detoxOriginalCleanup = detox.cleanup.bind(detox);
detox.cleanup = async (...args) => {
try {
ws.close();
} catch (e) {
// do nothing
}
await detoxOriginalCleanup(...args);
};
}
// setup after hook to ensure final context coverage is captured
process.nextTick(() => {
if (global.after) {
after(() => {
coverage.collect();
});
}
});

View File

@ -1,15 +0,0 @@
/* eslint-disable no-return-assign */
let ready = false;
process.on('bridge-attached', () => (ready = true));
module.exports = {
wait() {
if (ready) return Promise.resolve();
return new Promise(resolve => {
process.once('bridge-attached', resolve);
});
},
reset() {
ready = false;
},
};

View File

@ -1,73 +0,0 @@
/* eslint-disable no-param-reassign,global-require */
let Mocha;
try {
Mocha = require('mocha');
} catch (e) {
// ignore
}
let bundleFileName = null;
let sourceMapConsumer = null;
const ErrorStack = require('error-stack-parser');
const { SourceMapConsumer } = require('source-map');
/**
* Convert an error frame into a source mapped string
* @param parsed
* @returns {string}
*/
function frameToStr(parsed) {
const { name, line, column, source } = sourceMapConsumer.originalPositionFor({
line: parsed.lineNumber,
column: parsed.column,
});
return ` at ${name || parsed.functionName || '<anonymous>'} (${source ||
parsed.fileName}:${line || parsed.lineNumber}:${column ||
parsed.columnNumber})`;
}
/**
* Convert an errors stack frames to their original source mapped positions
*
* @param error
* @return {*}
*/
function sourceMappedError(error) {
const original = error.stack.split('\n');
const parsed = ErrorStack.parse(error);
const newStack = [original[0]];
for (let i = 0; i < parsed.length; i++) {
const { fileName } = parsed[i];
if (fileName === bundleFileName) newStack.push(frameToStr(parsed[i]));
else newStack.push(original[i + 1]);
}
error.stack = newStack.join('\n');
return error;
}
if (Mocha) {
// override mocha fail so we can replace stack traces
const Runner = Mocha.Runner;
const originalFail = Runner.prototype.fail;
Runner.prototype.fail = function fail(test, error) {
return originalFail.call(this, test, sourceMappedError(error));
};
}
module.exports = {
sourceMappedError,
/**
* Build a source map consumer from source map bundle contents
* @param str
* @param fileName
* @returns {Promise<void>}
*/
async buildSourceMap(str, fileName) {
bundleFileName = fileName;
sourceMapConsumer = await new SourceMapConsumer(str);
},
};

View File

@ -1,128 +0,0 @@
/* eslint-disable guard-for-in,no-restricted-syntax,no-return-assign */
const url = require('url');
const http = require('http');
const chalk = require('chalk');
const invariant = require('assert');
const { Script } = require('vm');
const context = require('./context');
const coverage = require('./coverage');
const { buildSourceMap } = require('./source-map');
let send;
let bundle;
const PREPARE = 'prepareJSRuntime';
const BUNDLE_FILE_NAME = 'app.bundle.js';
const EXECUTE = 'executeApplicationScript';
function reply(id, result) {
send({
replyID: id,
result,
});
}
function handleError(message) {
throw new Error(message);
}
async function downloadUrl(fileUrl) {
const res = await new Promise((resolve, reject) =>
http.get(fileUrl, resolve).on('error', reject)
);
let buffer = '';
res.setEncoding('utf8');
res.on('data', chunk => (buffer += chunk));
await new Promise(resolve => res.on('end', resolve));
return buffer;
}
async function downloadBundle(bundleUrl) {
const bundleStr = await downloadUrl(bundleUrl);
bundle = new Script(bundleStr, {
timeout: 120000,
displayErrors: true,
filename: BUNDLE_FILE_NAME,
});
const sourceMapUrl = bundleStr
.slice(bundleStr.lastIndexOf('\n'))
.replace('//# sourceMappingURL=', '');
const sourceMapSource = await downloadUrl(sourceMapUrl);
await buildSourceMap(sourceMapSource, BUNDLE_FILE_NAME);
return bundle;
}
async function getBundle(request) {
if (bundle) return bundle;
console.log(`${chalk.blue('[bridge]')} debugger connected`);
const parsedUrl = url.parse(request.url, true);
invariant(parsedUrl.query);
parsedUrl.query.inlineSourceMap = true;
delete parsedUrl.search;
return downloadBundle(url.format(parsedUrl));
}
module.exports = {
set send(fn) {
send = fn;
},
async message(request) {
const { method } = request;
switch (method) {
case PREPARE:
coverage.collect();
await context.cleanup();
context.create();
reply(request.id);
break;
case EXECUTE: {
const script = await getBundle(request);
if (global.bridge.context === null) {
throw new Error('VM context was not prepared.');
}
if (request.inject) {
for (const name in request.inject) {
global.bridge.context[name] = JSON.parse(request.inject[name]);
}
}
script.runInContext(global.bridge.context, BUNDLE_FILE_NAME);
reply(request.id);
break;
}
default: {
let returnValue = [[], [], [], 0];
try {
if (
global.bridge.context !== null &&
typeof global.bridge.context.__fbBatchedBridge === 'object'
) {
returnValue = global.bridge.context.__fbBatchedBridge[method].apply(
null,
request.arguments
);
}
} catch (e) {
if (method !== '$disconnected') {
handleError(
`Failed while making a call bridge call ${method}::${e.message}`
);
}
} finally {
reply(request.id, JSON.stringify(returnValue));
}
}
}
},
};

View File

@ -1,15 +0,0 @@
const vm = require('./vm');
const WebSocket = require('ws');
const ws = new WebSocket(
'ws://localhost:8081/debugger-proxy?role=debugger&name=Chrome'
);
vm.send = obj => ws.send(JSON.stringify(obj));
ws.onmessage = message => vm.message(JSON.parse(message.data));
ws.onclose = event =>
!event.wasClean ? console.error('Bridge WS Error', event.message) : '';
module.exports = ws;

View File

@ -1,63 +0,0 @@
import ReactNative from 'react-native';
import RNRestart from 'react-native-restart'; // Import package from node modules
const { Platform, NativeModules } = ReactNative;
const bridgeNode = global.__bridgeNode;
const INTERNAL_KEYS = ['context', 'rn', 'reload'];
// https://github.com/facebook/react-native/blob/master/React/Modules/RCTDevSettings.mm
if (Platform.OS === 'ios' && !bridgeNode) {
NativeModules.RCTDevSettings.setIsDebuggingRemotely(true);
} else {
if (Platform.OS === 'android' && !bridgeNode) {
// TODO warn to add:
// getReactNativeHost().getReactInstanceManager().getDevSupportManager().getDevSettings().setRemoteJSDebugEnabled(true);
// to MainApplication onCreate
}
if (bridgeNode) {
if (Platform.OS === 'ios') {
bridgeNode.setBridgeProperty(
'reload',
NativeModules.RCTDevSettings.reload
);
} else {
bridgeNode.setBridgeProperty('reload', RNRestart.Restart);
}
bridgeNode.setBridgeProperty('rn', ReactNative);
// keep alive
setInterval(() => {
// I don't do anything...
// BUT i am needed - otherwise RN's batched bridge starts to hang in detox... ???
}, 60);
}
}
global.__driftCheck = delay => {
setTimeout(bridgeNode._callbackDriftCheck, delay);
};
let hasInitialized = false;
export default {
/**
* Expose a property in node on the global.bridge object
* @param key
* @param value
*/
setBridgeProperty(key, value) {
if (INTERNAL_KEYS.includes(key)) return;
if (bridgeNode) {
bridgeNode.setBridgeProperty(key, value);
// notify ready on first setBridgeProp
if (!hasInitialized) {
bridgeNode._ready();
hasInitialized = true;
}
}
},
};