Move integration tests to OSS (#43094)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/43094

Merge the internal `cxxcdp-tester` project into `jsinspector-modern/tests`.

Note: These tests still use RN default feature flags and therefore test against the legacy CDP registry - that's addressed in the next diff.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D53766994

fbshipit-source-id: eec144124b20a4500e28398e98763febaed52748
This commit is contained in:
Rob Hogan
2024-02-22 04:21:35 -08:00
committed by Facebook GitHub Bot
parent 765e542c8d
commit 8cbf46a5f4
5 changed files with 998 additions and 0 deletions
@@ -0,0 +1,156 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ReactInstanceIntegrationTest.h"
#include "FollyDynamicMatchers.h"
#include "UniquePtrFactory.h"
#include "prelude.js.h"
#include <folly/json.h>
#include <glog/logging.h>
#include <react/runtime/hermes/HermesInstance.h>
namespace facebook::react::jsinspector_modern {
using namespace ::testing;
ReactInstanceIntegrationTest::ReactInstanceIntegrationTest()
: runtime(nullptr),
instance(nullptr),
messageQueueThread(std::make_shared<MockMessageQueueThread>()),
errorHandler(std::make_shared<ErrorUtils>()) {}
void ReactInstanceIntegrationTest::SetUp() {
auto mockRegistry = std::make_unique<MockTimerRegistry>();
auto timerManager =
std::make_shared<react::TimerManager>(std::move(mockRegistry));
auto jsErrorHandlingFunc = [](react::MapBuffer error) noexcept {
LOG(INFO) << "Error: \nFile: " << error.getString(react::kFrameFileName)
<< "\nLine: " << error.getInt(react::kFrameLineNumber)
<< "\nColumn: " << error.getInt(react::kFrameColumnNumber)
<< "\nMethod: " << error.getString(react::kFrameMethodName);
};
auto jsRuntimeFactory = std::make_unique<react::HermesInstance>();
std::unique_ptr<react::JSRuntime> runtime_ =
jsRuntimeFactory->createJSRuntime(nullptr, nullptr, messageQueueThread);
jsi::Runtime* jsiRuntime = &runtime_->getRuntime();
// Error handler:
jsiRuntime->global().setProperty(
*jsiRuntime,
"ErrorUtils",
jsi::Object::createFromHostObject(*jsiRuntime, errorHandler));
instance = std::make_unique<react::ReactInstance>(
std::move(runtime_),
messageQueueThread,
timerManager,
std::move(jsErrorHandlingFunc));
timerManager->setRuntimeExecutor(instance->getBufferedRuntimeExecutor());
// JS Environment:
initializeRuntime(preludeJsCode);
// Inspector:
auto& inspector = getInspectorInstance();
auto pages = inspector.getPages();
// We should now have at least a single page once the above runtime has been
// initialized.
assert(pages.size() > 0);
size_t pageId = pages.back().id;
clientToVM_ = inspector.connect(pageId, mockRemoteConnections_.make_unique());
}
void ReactInstanceIntegrationTest::TearDown() {
clientToVM_->disconnect();
}
void ReactInstanceIntegrationTest::initializeRuntime(std::string_view script) {
react::ReactInstance::JSRuntimeFlags flags{
.isProfiling = false,
};
instance->initializeRuntime(flags, [](jsi::Runtime&) {});
messageQueueThread->tick();
std::string init(script);
// JS calls no longer buffered after calling loadScript
instance->loadScript(std::make_unique<react::JSBigStdString>(init), "");
messageQueueThread->flush();
}
void ReactInstanceIntegrationTest::send(
const std::string& method,
const folly::dynamic& params) {
folly::dynamic request = folly::dynamic::object();
request["method"] = method;
request["id"] = id_++;
request["params"] = params;
sendJSONString(folly::toJson(request));
}
void ReactInstanceIntegrationTest::sendJSONString(const std::string& message) {
// The runtime must be initialized and connected to before messaging
clientToVM_->sendMessage(message);
}
jsi::Value ReactInstanceIntegrationTest::run(const std::string& script) {
auto runtimeExecutor = instance->getUnbufferedRuntimeExecutor();
auto ret = jsi::Value::undefined();
runtimeExecutor([script, &ret](jsi::Runtime& rt) {
ret = rt.evaluateJavaScript(
std::make_unique<jsi::StringBuffer>(script), "<test>");
});
messageQueueThread->flush();
while (verbose_ && errorHandler->size() > 0) {
LOG(INFO) << "Error: " << errorHandler->getLastError().getMessage();
}
return ret;
}
bool ReactInstanceIntegrationTest::verbose(bool isVerbose) {
const bool previous = verbose_;
verbose_ = isVerbose;
return previous;
}
TEST_F(ReactInstanceIntegrationTest, RuntimeEvalTest) {
auto val = run("1 + 2");
EXPECT_EQ(val.asNumber(), 3);
}
TEST_F(ReactInstanceIntegrationTest, ConsoleLogTest) {
InSequence s;
EXPECT_CALL(getRemoteConnection(), onMessage(_))
.Times(2)
.RetiresOnSaturation();
EXPECT_CALL(
getRemoteConnection(),
onMessage(JsonParsed(AllOf(
AtJsonPtr("/params/args/0/value", Eq("Hello, World!")),
AtJsonPtr("/method", Eq("Runtime.consoleAPICalled"))))));
EXPECT_CALL(getRemoteConnection(), onDisconnect());
send("Runtime.enable");
run("console.log('Hello, World!');");
}
} // namespace facebook::react::jsinspector_modern
@@ -0,0 +1,53 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/json.h>
#include <gtest/gtest.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <memory>
#include "InspectorMocks.h"
#include "ReactNativeMocks.h"
#include "UniquePtrFactory.h"
namespace facebook::react::jsinspector_modern {
class ReactInstanceIntegrationTest : public ::testing::Test {
protected:
ReactInstanceIntegrationTest();
void SetUp() override;
void TearDown() override;
jsi::Value run(const std::string& script);
bool verbose(bool isVerbose);
void send(
const std::string& method,
const folly::dynamic& params = folly::dynamic::object());
void sendJSONString(const std::string& message);
jsi::Runtime* runtime;
std::unique_ptr<react::ReactInstance> instance;
std::shared_ptr<MockMessageQueueThread> messageQueueThread;
std::shared_ptr<ErrorUtils> errorHandler;
MockRemoteConnection& getRemoteConnection() {
return *mockRemoteConnections_[0];
}
private:
void initializeRuntime(std::string_view script);
size_t id_ = 1;
bool verbose_ = false;
UniquePtrFactory<MockRemoteConnection> mockRemoteConnections_;
std::unique_ptr<ILocalConnection> clientToVM_;
};
} // namespace facebook::react::jsinspector_modern
@@ -0,0 +1,93 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ReactNativeMocks.h"
namespace facebook::react::jsinspector_modern {
//
// MockMessageQueueThread
//
void MockMessageQueueThread::runOnQueue(std::function<void()>&& func) {
callbackQueue_.push(func);
}
void MockMessageQueueThread::tick() {
if (!callbackQueue_.empty()) {
auto callback = callbackQueue_.front();
callback();
callbackQueue_.pop();
}
}
void MockMessageQueueThread::guardedTick() {
try {
tick();
} catch (const std::exception& e) {
// For easier debugging
FAIL() << e.what();
}
}
void MockMessageQueueThread::flush() {
while (!callbackQueue_.empty()) {
tick();
}
}
size_t MockMessageQueueThread::size() {
return callbackQueue_.size();
}
void MockMessageQueueThread::quitSynchronous() {
assert(false && "Not implemented");
}
void MockMessageQueueThread::runOnQueueSync(std::function<void()>&& callback) {
callback();
}
//
// ErrorUtils
//
jsi::Value ErrorUtils::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
auto methodName = name.utf8(rt);
if (methodName == "reportFatalError") {
return jsi::Function::createFromHostFunction(
rt,
name,
1,
[this](
jsi::Runtime& runtime,
/* thisValue */ const jsi::Value&,
const jsi::Value* arguments,
size_t count) {
if (count >= 1) {
auto value = jsi::Value(runtime, arguments[0]);
auto error = jsi::JSError(runtime, std::move(value));
LOG(INFO) << "JSI Fatal: " << error.getMessage();
reportFatalError(std::move(error));
}
return jsi::Value::undefined();
});
} else {
throw std::runtime_error("Unknown method: " + methodName);
}
}
size_t ErrorUtils::size() {
return errors_.size();
}
jsi::JSError ErrorUtils::getLastError() {
auto error = errors_.back();
errors_.pop_back();
return error;
}
} // namespace facebook::react::jsinspector_modern
@@ -0,0 +1,65 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <queue>
#include <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
#include <react/runtime/ReactInstance.h>
namespace facebook::react::jsinspector_modern {
class MockTimerRegistry : public react::PlatformTimerRegistry {
public:
MOCK_METHOD2(createTimer, void(uint32_t, double));
MOCK_METHOD2(createRecurringTimer, void(uint32_t, double));
MOCK_METHOD1(deleteTimer, void(uint32_t));
};
class MockMessageQueueThread : public react::MessageQueueThread {
public:
void runOnQueue(std::function<void()>&& func) override;
// Unused
void runOnQueueSync(std::function<void()>&&) override;
// Unused
void quitSynchronous() override;
void tick();
void flush();
void guardedTick();
size_t size();
private:
std::queue<std::function<void()>> callbackQueue_;
};
class ErrorUtils : public jsi::HostObject {
public:
jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override;
void reportFatalError(jsi::JSError&& error) {
errors_.push_back(std::move(error));
}
size_t size();
jsi::JSError getLastError();
private:
std::vector<jsi::JSError> errors_;
};
} // namespace facebook::react::jsinspector_modern
@@ -0,0 +1,631 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @polyfill
* @nolint
* @format
*/
/* eslint-disable no-shadow, eqeqeq, curly, no-unused-vars, no-void, no-control-regex */
#pragma once
constexpr std::string_view preludeJsCode = R"___(
(function (global, __DEV__) {const inspect = (function () {
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// https://github.com/joyent/node/blob/master/lib/util.js
function inspect(obj, opts) {
var ctx = {
seen: [],
formatValueCalls: 0,
stylize: stylizeNoColor,
};
return formatValue(ctx, obj, opts.depth);
}
function stylizeNoColor(str, styleType) {
return str;
}
function arrayToHash(array) {
var hash = {};
array.forEach(function (val, idx) {
hash[val] = true;
});
return hash;
}
function formatValue(ctx, value, recurseTimes) {
ctx.formatValueCalls++;
if (ctx.formatValueCalls > 200) {
return `[TOO BIG formatValueCalls ${ctx.formatValueCalls} exceeded limit of 200]`;
}
// Primitive types cannot have properties
var primitive = formatPrimitive(ctx, value);
if (primitive) {
return primitive;
}
// Look up the keys of the object.
var keys = Object.keys(value);
var visibleKeys = arrayToHash(keys);
// IE doesn't make error fields non-enumerable
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
if (
isError(value) &&
(keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)
) {
return formatError(value);
}
// Some type of object without properties can be shortcutted.
if (keys.length === 0) {
if (isFunction(value)) {
var name = value.name ? ': ' + value.name : '';
return ctx.stylize('[Function' + name + ']', 'special');
}
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
}
if (isDate(value)) {
return ctx.stylize(Date.prototype.toString.call(value), 'date');
}
if (isError(value)) {
return formatError(value);
}
}
var base = '',
array = false,
braces = ['{', '}'];
// Make Array say that they are Array
if (isArray(value)) {
array = true;
braces = ['[', ']'];
}
// Make functions say that they are functions
if (isFunction(value)) {
var n = value.name ? ': ' + value.name : '';
base = ' [Function' + n + ']';
}
// Make RegExps say that they are RegExps
if (isRegExp(value)) {
base = ' ' + RegExp.prototype.toString.call(value);
}
// Make dates with properties first say the date
if (isDate(value)) {
base = ' ' + Date.prototype.toUTCString.call(value);
}
// Make error with message first say the error
if (isError(value)) {
base = ' ' + formatError(value);
}
if (keys.length === 0 && (!array || value.length == 0)) {
return braces[0] + base + braces[1];
}
if (recurseTimes < 0) {
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
} else {
return ctx.stylize('[Object]', 'special');
}
}
ctx.seen.push(value);
var output;
if (array) {
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
} else {
output = keys.map(function (key) {
return formatProperty(
ctx,
value,
recurseTimes,
visibleKeys,
key,
array,
);
});
}
ctx.seen.pop();
return reduceToSingleString(output, base, braces);
}
function formatPrimitive(ctx, value) {
if (isUndefined(value)) return ctx.stylize('undefined', 'undefined');
if (isString(value)) {
var simple =
"'" +
JSON.stringify(value)
.replace(/^"|"$/g, '')
.replace(/'/g, "\\'")
.replace(/\\"/g, '"') +
"'";
return ctx.stylize(simple, 'string');
}
if (isNumber(value)) return ctx.stylize('' + value, 'number');
if (isBoolean(value)) return ctx.stylize('' + value, 'boolean');
// For some reason typeof null is "object", so special case here.
if (isNull(value)) return ctx.stylize('null', 'null');
}
function formatError(value) {
return '[' + Error.prototype.toString.call(value) + ']';
}
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
for (var i = 0, l = value.length; i < l; ++i) {
if (hasOwnProperty(value, String(i))) {
output.push(
formatProperty(
ctx,
value,
recurseTimes,
visibleKeys,
String(i),
true,
),
);
} else {
output.push('');
}
}
keys.forEach(function (key) {
if (!key.match(/^\d+$/)) {
output.push(
formatProperty(ctx, value, recurseTimes, visibleKeys, key, true),
);
}
});
return output;
}
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
var name, str, desc;
desc = Object.getOwnPropertyDescriptor(value, key) || {value: value[key]};
if (desc.get) {
if (desc.set) {
str = ctx.stylize('[Getter/Setter]', 'special');
} else {
str = ctx.stylize('[Getter]', 'special');
}
} else {
if (desc.set) {
str = ctx.stylize('[Setter]', 'special');
}
}
if (!hasOwnProperty(visibleKeys, key)) {
name = '[' + key + ']';
}
if (!str) {
if (ctx.seen.indexOf(desc.value) < 0) {
if (isNull(recurseTimes)) {
str = formatValue(ctx, desc.value, null);
} else {
str = formatValue(ctx, desc.value, recurseTimes - 1);
}
if (str.indexOf('\n') > -1) {
if (array) {
str = str
.split('\n')
.map(function (line) {
return ' ' + line;
})
.join('\n')
.slice(2);
} else {
str =
'\n' +
str
.split('\n')
.map(function (line) {
return ' ' + line;
})
.join('\n');
}
}
} else {
str = ctx.stylize('[Circular]', 'special');
}
}
if (isUndefined(name)) {
if (array && key.match(/^\d+$/)) {
return str;
}
name = JSON.stringify('' + key);
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
name = name.slice(1, name.length - 1);
name = ctx.stylize(name, 'name');
} else {
name = name
.replace(/'/g, "\\'")
.replace(/\\"/g, '"')
.replace(/(^"|"$)/g, "'");
name = ctx.stylize(name, 'string');
}
}
return name + ': ' + str;
}
function reduceToSingleString(output, base, braces) {
var numLinesEst = 0;
var length = output.reduce(function (prev, cur) {
numLinesEst++;
if (cur.indexOf('\n') >= 0) numLinesEst++;
return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
}, 0);
if (length > 60) {
return (
braces[0] +
(base === '' ? '' : base + '\n ') +
' ' +
output.join(',\n ') +
' ' +
braces[1]
);
}
return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
}
// NOTE: These type checking functions intentionally don't use `instanceof`
// because it is fragile and can be easily faked with `Object.create()`.
function isArray(ar) {
return Array.isArray(ar);
}
function isBoolean(arg) {
return typeof arg === 'boolean';
}
function isNull(arg) {
return arg === null;
}
function isNullOrUndefined(arg) {
return arg == null;
}
function isNumber(arg) {
return typeof arg === 'number';
}
function isString(arg) {
return typeof arg === 'string';
}
function isSymbol(arg) {
return typeof arg === 'symbol';
}
function isUndefined(arg) {
return arg === void 0;
}
function isRegExp(re) {
return isObject(re) && objectToString(re) === '[object RegExp]';
}
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
function isDate(d) {
return isObject(d) && objectToString(d) === '[object Date]';
}
function isError(e) {
return (
isObject(e) &&
(objectToString(e) === '[object Error]' || e instanceof Error)
);
}
function isFunction(arg) {
return typeof arg === 'function';
}
function objectToString(o) {
return Object.prototype.toString.call(o);
}
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
return inspect;
})();
const OBJECT_COLUMN_NAME = '(index)';
const LOG_LEVELS = {
trace: 0,
info: 1,
warn: 2,
error: 3,
};
const INSPECTOR_LEVELS = [];
INSPECTOR_LEVELS[LOG_LEVELS.trace] = 'debug';
INSPECTOR_LEVELS[LOG_LEVELS.info] = 'log';
INSPECTOR_LEVELS[LOG_LEVELS.warn] = 'warning';
INSPECTOR_LEVELS[LOG_LEVELS.error] = 'error';
// Strip the inner function in getNativeLogFunction(), if in dev also
// strip method printing to originalConsole.
const INSPECTOR_FRAMES_TO_SKIP = __DEV__ ? 2 : 1;
function getNativeLogFunction(level) {
return function () {
let str;
if (arguments.length === 1 && typeof arguments[0] === 'string') {
str = arguments[0];
} else {
str = Array.prototype.map
.call(arguments, function (arg) {
return inspect(arg, {depth: 10});
})
.join(', ');
}
// TRICKY
// If more than one argument is provided, the code above collapses them all
// into a single formatted string. This transform wraps string arguments in
// single quotes (e.g. "foo" -> "'foo'") which then breaks the "Warning:"
// check below. So it's important that we look at the first argument, rather
// than the formatted argument string.
const firstArg = arguments[0];
let logLevel = level;
if (
typeof firstArg === 'string' &&
firstArg.slice(0, 9) === 'Warning: ' &&
logLevel >= LOG_LEVELS.error
) {
// React warnings use console.error so that a stack trace is shown,
// but we don't (currently) want these to show a redbox
// (Note: Logic duplicated in ExceptionsManager.js.)
logLevel = LOG_LEVELS.warn;
}
if (global.__inspectorLog) {
global.__inspectorLog(
INSPECTOR_LEVELS[logLevel],
str,
[].slice.call(arguments),
INSPECTOR_FRAMES_TO_SKIP,
);
}
if (groupStack.length) {
str = groupFormat('', str);
}
global.nativeLoggingHook(str, logLevel);
};
}
function repeat(element, n) {
return Array.apply(null, Array(n)).map(function () {
return element;
});
}
function consoleTablePolyfill(rows) {
// convert object -> array
if (!Array.isArray(rows)) {
var data = rows;
rows = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
var row = data[key];
row[OBJECT_COLUMN_NAME] = key;
rows.push(row);
}
}
}
if (rows.length === 0) {
global.nativeLoggingHook('', LOG_LEVELS.info);
return;
}
var columns = Object.keys(rows[0]).sort();
var stringRows = [];
var columnWidths = [];
// Convert each cell to a string. Also
// figure out max cell width for each column
columns.forEach(function (k, i) {
columnWidths[i] = k.length;
for (var j = 0; j < rows.length; j++) {
var cellStr = (rows[j][k] || '?').toString();
stringRows[j] = stringRows[j] || [];
stringRows[j][i] = cellStr;
columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
}
});
// Join all elements in the row into a single string with | separators
// (appends extra spaces to each cell to make separators | aligned)
function joinRow(row, space) {
var cells = row.map(function (cell, i) {
var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join('');
return cell + extraSpaces;
});
space = space || ' ';
return cells.join(space + '|' + space);
}
var separators = columnWidths.map(function (columnWidth) {
return repeat('-', columnWidth).join('');
});
var separatorRow = joinRow(separators, '-');
var header = joinRow(columns);
var table = [header, separatorRow];
for (var i = 0; i < rows.length; i++) {
table.push(joinRow(stringRows[i]));
}
// Notice extra empty line at the beginning.
// Native logging hook adds "RCTLog >" at the front of every
// logged string, which would shift the header and screw up
// the table
global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info);
}
const GROUP_PAD = '\u2502'; // Box light vertical
const GROUP_OPEN = '\u2510'; // Box light down+left
const GROUP_CLOSE = '\u2518'; // Box light up+left
const groupStack = [];
function groupFormat(prefix, msg) {
// Insert group formatting before the console message
return groupStack.join('') + prefix + ' ' + (msg || '');
}
function consoleGroupPolyfill(label) {
global.nativeLoggingHook(groupFormat(GROUP_OPEN, label), LOG_LEVELS.info);
groupStack.push(GROUP_PAD);
}
function consoleGroupCollapsedPolyfill(label) {
global.nativeLoggingHook(groupFormat(GROUP_CLOSE, label), LOG_LEVELS.info);
groupStack.push(GROUP_PAD);
}
function consoleGroupEndPolyfill() {
groupStack.pop();
global.nativeLoggingHook(groupFormat(GROUP_CLOSE), LOG_LEVELS.info);
}
function consoleAssertPolyfill(expression, label) {
if (!expression) {
global.nativeLoggingHook('Assertion failed: ' + label, LOG_LEVELS.error);
}
}
if (global.nativeLoggingHook) {
const originalConsole = global.console;
// Preserve the original `console` as `originalConsole`
if (__DEV__ && originalConsole) {
const descriptor = Object.getOwnPropertyDescriptor(global, 'console');
if (descriptor) {
Object.defineProperty(global, 'originalConsole', descriptor);
}
}
global.console = {
error: getNativeLogFunction(LOG_LEVELS.error),
info: getNativeLogFunction(LOG_LEVELS.info),
log: getNativeLogFunction(LOG_LEVELS.info),
warn: getNativeLogFunction(LOG_LEVELS.warn),
trace: getNativeLogFunction(LOG_LEVELS.trace),
debug: getNativeLogFunction(LOG_LEVELS.trace),
table: consoleTablePolyfill,
group: consoleGroupPolyfill,
groupEnd: consoleGroupEndPolyfill,
groupCollapsed: consoleGroupCollapsedPolyfill,
assert: consoleAssertPolyfill,
};
Object.defineProperty(console, '_isPolyfilled', {
value: true,
enumerable: false,
});
// If available, also call the original `console` method since that is
// sometimes useful. Ex: on OS X, this will let you see rich output in
// the Safari Web Inspector console.
if (__DEV__ && originalConsole) {
Object.keys(console).forEach(methodName => {
const reactNativeMethod = console[methodName];
if (originalConsole[methodName]) {
console[methodName] = function () {
originalConsole[methodName](...arguments);
reactNativeMethod.apply(console, arguments);
};
}
});
// The following methods are not supported by this polyfill but
// we still should pass them to original console if they are
// supported by it.
['clear', 'dir', 'dirxml', 'profile', 'profileEnd'].forEach(methodName => {
if (typeof originalConsole[methodName] === 'function') {
console[methodName] = function () {
originalConsole[methodName](...arguments);
};
}
});
}
} else if (!global.console) {
function stub() {}
const log = global.print || stub;
global.console = {
debug: log,
error: log,
info: log,
log: log,
trace: log,
warn: log,
assert(expression, label) {
if (!expression) {
log('Assertion failed: ' + label);
}
},
clear: stub,
dir: stub,
dirxml: stub,
group: stub,
groupCollapsed: stub,
groupEnd: stub,
profile: stub,
profileEnd: stub,
table: stub,
};
Object.defineProperty(console, '_isPolyfilled', {
value: true,
enumerable: false,
});
}})(globalThis, true)
)___";