[tests][bridge] switch to using published npm alpha version of bridge \o/
This commit is contained in:
parent
531617cd53
commit
2ba1b0bf11
|
@ -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)),
|
||||
]
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
|
@ -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();
|
||||
},
|
||||
};
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue