Files
react-native/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp
T
Ruslan Lesiutin f03268b896 Replace DOMHighResTimeStamp alias in ReactCommon with new abstractions (#51512)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51512

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

# Changelog: [Internal]

Replaces `DOMHighResTimeStamp` alias completely in `ReactCommon` with `HighResTimeStamp`.

`DOMHighResTimeStamp` as a type is now expected to be used only in JavaScript.

I didn't update places where we explcitly use `std::chrono::high_resolution_clock`, since it is platform-specific and there is no guarantee that `std::chrono::high_resolution_clock` == `std::chrono::steady_clock`.

Also, places that are isolated and not part of the Web Performance APIs, such as Telemetry for Fabric, are not updates as part of this diff. Although these subsystems are also using `std::chrono::steady_clock` as a low-level representation, they are not sharing it with other parts of the React Native core.

Reviewed By: rubennorte

Differential Revision: D75185613

fbshipit-source-id: 889719368de163e6f529689df6cc16d816fde66c
2025-05-22 10:16:45 -07:00

799 lines
29 KiB
C++

/*
* 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 "BridgingTest.h"
namespace facebook::react {
using namespace std::literals;
TEST_F(BridgingTest, jsiTest) {
jsi::Value value = true;
jsi::Value string = jsi::String::createFromAscii(rt, "hello");
jsi::Value object = jsi::Object(rt);
jsi::Value array = jsi::Array::createWithElements(rt, value, object);
jsi::Value func = function("() => {}");
// The bridging mechanism needs to know how to copy and downcast values.
EXPECT_NO_THROW(bridging::fromJs<jsi::Value>(rt, value, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::String>(rt, string, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::Object>(rt, object, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::Array>(rt, array, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::Function>(rt, func, invoker));
// Should throw when attempting an invalid cast.
EXPECT_JSI_THROW(bridging::fromJs<jsi::Object>(rt, value, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::String>(rt, array, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::Array>(rt, object, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::Array>(rt, string, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::Array>(rt, func, invoker));
// Should be able to generically no-op convert JSI.
EXPECT_NO_THROW(bridging::toJs(rt, value, invoker));
EXPECT_NO_THROW(bridging::toJs(rt, string.asString(rt), invoker));
EXPECT_NO_THROW(bridging::toJs(rt, object.asObject(rt), invoker));
EXPECT_NO_THROW(bridging::toJs(rt, array.asObject(rt).asArray(rt), invoker));
EXPECT_NO_THROW(
bridging::toJs(rt, func.asObject(rt).asFunction(rt), invoker));
}
TEST_F(BridgingTest, boolTest) {
EXPECT_TRUE(bridging::fromJs<bool>(rt, jsi::Value(true), invoker));
EXPECT_FALSE(bridging::fromJs<bool>(rt, jsi::Value(false), invoker));
EXPECT_JSI_THROW(bridging::fromJs<bool>(rt, jsi::Value(1), invoker));
EXPECT_TRUE(bridging::toJs(rt, true));
EXPECT_FALSE(bridging::toJs(rt, false));
}
TEST_F(BridgingTest, numberTest) {
EXPECT_EQ(1, bridging::fromJs<int>(rt, jsi::Value(1), invoker));
EXPECT_FLOAT_EQ(1.2f, bridging::fromJs<float>(rt, jsi::Value(1.2), invoker));
EXPECT_DOUBLE_EQ(1.2, bridging::fromJs<double>(rt, jsi::Value(1.2), invoker));
EXPECT_JSI_THROW(bridging::fromJs<double>(rt, jsi::Value(true), invoker));
EXPECT_EQ(1, static_cast<int>(bridging::toJs(rt, 1)));
EXPECT_FLOAT_EQ(1.2f, static_cast<float>(bridging::toJs(rt, 1.2f)));
EXPECT_DOUBLE_EQ(1.2, bridging::toJs(rt, 1.2));
EXPECT_EQ(
42,
static_cast<uint32_t>(
bridging::toJs(rt, static_cast<uint32_t>(42)).asNumber()));
EXPECT_EQ(
-42,
static_cast<uint32_t>(
bridging::toJs(rt, static_cast<uint32_t>(-42)).asNumber()));
EXPECT_FALSE(
-42 ==
static_cast<int32_t>(
bridging::toJs(rt, static_cast<uint32_t>(-42)).asNumber()));
}
TEST_F(BridgingTest, stringTest) {
auto string = jsi::String::createFromAscii(rt, "hello");
EXPECT_EQ("hello"s, bridging::fromJs<std::string>(rt, string, invoker));
EXPECT_JSI_THROW(bridging::fromJs<std::string>(rt, jsi::Value(1), invoker));
EXPECT_TRUE(
jsi::String::strictEquals(rt, string, bridging::toJs(rt, "hello")));
EXPECT_TRUE(
jsi::String::strictEquals(rt, string, bridging::toJs(rt, "hello"s)));
EXPECT_TRUE(
jsi::String::strictEquals(rt, string, bridging::toJs(rt, "hello"sv)));
}
TEST_F(BridgingTest, objectTest) {
auto object = jsi::Object(rt);
object.setProperty(rt, "foo", "bar");
auto omap =
bridging::fromJs<std::map<std::string, std::string>>(rt, object, invoker);
auto umap = bridging::fromJs<std::unordered_map<std::string, std::string>>(
rt, object, invoker);
EXPECT_EQ(1, omap.size());
EXPECT_EQ(1, umap.size());
EXPECT_EQ("bar"s, omap["foo"]);
EXPECT_EQ("bar"s, umap["foo"]);
EXPECT_EQ(
"bar"s,
bridging::toJs(rt, omap, invoker)
.getProperty(rt, "foo")
.asString(rt)
.utf8(rt));
EXPECT_EQ(
"bar"s,
bridging::toJs(rt, umap, invoker)
.getProperty(rt, "foo")
.asString(rt)
.utf8(rt));
}
TEST_F(BridgingTest, hostObjectTest) {
struct TestHostObject : public jsi::HostObject {
jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override {
if (name.utf8(rt) == "test") {
return {1};
}
return jsi::Value::undefined();
}
};
auto hostobject = std::make_shared<TestHostObject>();
auto object = bridging::toJs(rt, hostobject);
EXPECT_EQ(1, object.getProperty(rt, "test").asNumber());
EXPECT_EQ(
hostobject, bridging::fromJs<decltype(hostobject)>(rt, object, invoker));
}
TEST_F(BridgingTest, weakbjectTest) {
auto object = jsi::Object(rt);
auto weakobject = jsi::WeakObject(rt, object);
EXPECT_TRUE(jsi::Object::strictEquals(
rt,
object,
bridging::fromJs<jsi::WeakObject>(rt, object, invoker)
.lock(rt)
.asObject(rt)));
EXPECT_TRUE(jsi::Object::strictEquals(
rt, object, bridging::toJs(rt, weakobject).asObject(rt)));
}
TEST_F(BridgingTest, arrayTest) {
auto vec = std::vector({"foo"s, "bar"s});
auto array = jsi::Array::createWithElements(rt, "foo", "bar");
EXPECT_EQ(
vec, bridging::fromJs<std::vector<std::string>>(rt, array, invoker));
auto arr = bridging::fromJs<std::array<std::string, 2>>(rt, array, invoker);
EXPECT_EQ(vec[0], arr[0]);
EXPECT_EQ(vec[1], arr[1]);
auto pair =
bridging::fromJs<std::pair<std::string, std::string>>(rt, array, invoker);
EXPECT_EQ(vec[0], pair.first);
EXPECT_EQ(vec[1], pair.second);
EXPECT_EQ(vec.size(), bridging::toJs(rt, vec, invoker).size(rt));
for (size_t i = 0; i < vec.size(); i++) {
EXPECT_EQ(
vec[i],
bridging::toJs(rt, vec, invoker)
.getValueAtIndex(rt, i)
.asString(rt)
.utf8(rt));
}
EXPECT_EQ(2, bridging::toJs(rt, std::make_pair(1, "2"), invoker).size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::make_tuple(1, "2"), invoker).size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::array<int, 2>{1, 2}, invoker).size(rt));
EXPECT_EQ(
2,
bridging::toJs(rt, std::array<std::string, 2>{"1", "2"}, invoker)
.size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::deque<int>{1, 2}, invoker).size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::list<int>{1, 2}, invoker).size(rt));
EXPECT_EQ(
2,
bridging::toJs(rt, std::initializer_list<int>{1, 2}, invoker).size(rt));
std::vector<std::array<std::string, 2>> headers{
{"foo", "bar"}, {"baz", "qux"}};
auto jsiHeaders = bridging::toJs(rt, headers, invoker);
EXPECT_EQ(headers.size(), jsiHeaders.size(rt));
}
TEST_F(BridgingTest, functionTest) {
auto object = jsi::Object(rt);
object.setProperty(rt, "foo", "bar");
auto lambda = [](std::map<std::string, std::string> map, std::string key) {
return map[key];
};
auto func = bridging::toJs(rt, lambda, invoker);
EXPECT_EQ(
"bar"s,
func.call(rt, object, jsi::String::createFromAscii(rt, "foo"))
.asString(rt)
.utf8(rt));
// Should throw if not enough arguments are passed or are the wrong types.
EXPECT_JSI_THROW(func.call(rt, object));
EXPECT_JSI_THROW(func.call(rt, object, jsi::Value(1)));
// Test with non-capturing lambda converted to function pointer.
func = bridging::toJs(rt, +lambda, invoker);
EXPECT_EQ(
"bar"s,
func.call(rt, object, jsi::String::createFromAscii(rt, "foo"))
.asString(rt)
.utf8(rt));
}
TEST_F(BridgingTest, syncCallbackTest) {
auto fn = function("(a, b) => a + b");
auto cb = bridging::fromJs<SyncCallback<std::string(std::string, int)>>(
rt, fn, invoker);
auto foo = "foo"s;
EXPECT_EQ("foo1"s, cb(foo, 1)); // Tests lvalue string
EXPECT_EQ("bar2", cb("bar", 2)); // Tests rvalue C string
EXPECT_TRUE(fn.isFunction(rt)); // Ensure the function wasn't invalidated.
}
TEST_F(BridgingTest, syncCallbackImplicitBridgingTest) {
{ // Value
auto fn = function("(a, b) => a + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::Value, int)>>(
rt, fn, invoker);
jsi::Value foo(jsi::String::createFromAscii(rt, "foo"));
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_EQ(cb(jsi::String::createFromAscii(rt, "bar"), 2), "bar2");
EXPECT_TRUE(fn.isFunction(rt));
}
{ // Object
auto fn = function("(a, b) => a.obj + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::Object, int)>>(
rt, fn, invoker);
jsi::Object foo(rt);
foo.setProperty(rt, "obj", "foo");
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_TRUE(fn.isFunction(rt));
}
{ // String
auto fn = function("(a, b) => a + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::String, int)>>(
rt, fn, invoker);
jsi::String foo(jsi::String::createFromAscii(rt, "foo"));
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_EQ(cb(jsi::String::createFromAscii(rt, "bar"), 2), "bar2");
EXPECT_TRUE(fn.isFunction(rt));
}
{ // Array
auto fn = function("(a, b) => a[0] + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::Array, int)>>(
rt, fn, invoker);
jsi::Array foo(rt, 1);
foo.setValueAtIndex(rt, 0, jsi::String::createFromAscii(rt, "foo"));
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_TRUE(fn.isFunction(rt));
}
}
TEST_F(BridgingTest, asyncCallbackTest) {
std::string output;
auto func = std::function<void(std::string)>([&](auto str) { output = str; });
auto cb = bridging::fromJs<AsyncCallback<decltype(func), std::string>>(
rt, function("(func, str) => func(str)"), invoker);
cb(func, "hello");
flushQueue(); // Run scheduled async work
EXPECT_EQ("hello"s, output);
// Test with lambda invocation
cb.call([func, jsInvoker = invoker](jsi::Runtime& rt, jsi::Function& f) {
f.call(
rt,
bridging::toJs(rt, func, jsInvoker),
bridging::toJs(rt, "hello again", jsInvoker));
});
flushQueue();
EXPECT_EQ("hello again"s, output);
}
TEST_F(BridgingTest, asyncCallbackInvalidation) {
std::string output;
std::function<void(std::string)> func = [&](auto str) { output = str; };
auto jsCallback = bridging::fromJs<AsyncCallback<>>(
rt, bridging::toJs(rt, func, invoker), invoker);
jsCallback.call(
[](jsi::Runtime& rt, jsi::Function& f) { f.call(rt, "hello"); });
// LongLivedObjectCollection goes away before callback is executed
LongLivedObjectCollection::get(rt).clear();
flushQueue();
// Assert native callback is never invoked
ASSERT_EQ(""s, output);
}
TEST_F(BridgingTest, asyncCallbackImplicitBridgingTest) {
std::string output;
auto func = std::function<void(std::string)>([&](auto str) { output = str; });
{ // Value
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::Value, int>>(
rt, function("(func, a, b) => func(a + b)"), invoker);
jsi::Value foo(jsi::String::createFromAscii(rt, "foo"));
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
cb(func, jsi::String::createFromAscii(rt, "bar"), 2);
flushQueue();
EXPECT_EQ(output, "bar2");
output.clear();
}
{ // Object
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::Object, int>>(
rt, function("(func, a, b) => func(a.obj + b)"), invoker);
jsi::Object foo(rt);
foo.setProperty(rt, "obj", "foo");
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
output.clear();
}
{ // String
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::String, int>>(
rt, function("(func, a, b) => func(a + b)"), invoker);
jsi::String foo(jsi::String::createFromAscii(rt, "foo"));
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
cb(func, jsi::String::createFromAscii(rt, "bar"), 2);
flushQueue();
EXPECT_EQ(output, "bar2");
output.clear();
}
{ // Array
auto fn = function("(func, a, b) => func(a[0] + b)");
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::Array, int>>(
rt, fn, invoker);
jsi::Array foo(rt, 1);
foo.setValueAtIndex(rt, 0, jsi::String::createFromAscii(rt, "foo"));
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
output.clear();
}
}
TEST_F(BridgingTest, promiseTest) {
auto func = function(
"(promise, obj) => {"
" promise.then("
" (res) => { obj.res = res; },"
" (err) => { obj.err = err; }"
" )"
"}");
auto promise = AsyncPromise<std::vector<std::string>>(rt, invoker);
auto output = jsi::Object(rt);
func.call(rt, bridging::toJs(rt, promise, invoker), output);
promise.resolve({"foo"s, "bar"s});
flushQueue();
EXPECT_EQ(1, output.getPropertyNames(rt).size(rt));
EXPECT_EQ(2, output.getProperty(rt, "res").asObject(rt).asArray(rt).size(rt));
EXPECT_NO_THROW(promise.resolve({"ignored"}));
EXPECT_NO_THROW(promise.reject("ignored"));
promise = AsyncPromise<std::vector<std::string>>(rt, invoker);
output = jsi::Object(rt);
func.call(rt, bridging::toJs(rt, promise, invoker), output);
promise.reject("fail");
flushQueue();
EXPECT_EQ(1, output.getPropertyNames(rt).size(rt));
EXPECT_EQ(
"fail"s,
output.getProperty(rt, "err")
.asObject(rt)
.getProperty(rt, "message")
.asString(rt)
.utf8(rt));
EXPECT_NO_THROW(promise.resolve({"ignored"}));
EXPECT_NO_THROW(promise.reject("ignored"));
}
using EventType = std::vector<std::string>;
using EventSubscriptionsWithLastEvent =
std::vector<std::pair<jsi::Object, std::shared_ptr<EventType>>>;
namespace {
template <typename EventType>
void addEventSubscription(
jsi::Runtime& rt,
const AsyncEventEmitter<EventType>& eventEmitter,
EventSubscriptionsWithLastEvent& eventSubscriptionsWithListener,
const std::shared_ptr<TestCallInvoker>& invoker) {
auto eventEmitterJs = bridging::toJs(rt, eventEmitter, invoker);
auto lastEvent = std::make_shared<EventType>();
auto listenJs = bridging::toJs(
rt,
[lastEvent = lastEvent](const EventType& event) { *lastEvent = event; },
invoker);
eventSubscriptionsWithListener.emplace_back(std::make_pair(
jsi::Object(eventEmitterJs.asFunction(rt)
.callWithThis(rt, eventEmitterJs, listenJs)
.asObject(rt)),
std::move(lastEvent)));
}
} // namespace
TEST_F(BridgingTest, eventEmitterTest) {
EventSubscriptionsWithLastEvent eventSubscriptionsWithListener;
AsyncEventEmitter<EventType> eventEmitter;
EXPECT_NO_THROW(eventEmitter.emit({"one", "two", "three"}));
EXPECT_EQ(0, eventSubscriptionsWithListener.size());
// register 3 JavaScript listeners to the event emitter
for (int i = 0; i < 3; ++i) {
addEventSubscription<EventType>(
rt, eventEmitter, eventSubscriptionsWithListener, invoker);
}
EXPECT_TRUE(eventEmitter.state_->listeners.contains(0));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(1));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(2));
// emit with args
EXPECT_NO_THROW(eventEmitter.emit({"four", "five", "six"}));
flushQueue();
// verify all listeners received the event
for (const auto& [_, lastEvent] : eventSubscriptionsWithListener) {
EXPECT_EQ(3, lastEvent->size());
EXPECT_EQ("four", lastEvent->at(0));
EXPECT_EQ("five", lastEvent->at(1));
EXPECT_EQ("six", lastEvent->at(2));
}
// Remove 2nd eventSubscriptions
eventSubscriptionsWithListener[1]
.first.getPropertyAsFunction(rt, "remove")
.callWithThis(rt, eventSubscriptionsWithListener[1].first);
eventSubscriptionsWithListener.erase(
eventSubscriptionsWithListener.begin() + 1);
// Add 4th and 5th eventSubscriptions
addEventSubscription<EventType>(
rt, eventEmitter, eventSubscriptionsWithListener, invoker);
addEventSubscription<EventType>(
rt, eventEmitter, eventSubscriptionsWithListener, invoker);
EXPECT_TRUE(eventEmitter.state_->listeners.contains(0));
EXPECT_FALSE(eventEmitter.state_->listeners.contains(1));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(2));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(3));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(4));
// Emit more events
EXPECT_NO_THROW(eventEmitter.emit({"seven", "eight", "nine"}));
flushQueue();
for (const auto& [_, lastEvent] : eventSubscriptionsWithListener) {
EXPECT_EQ(3, lastEvent->size());
EXPECT_EQ("seven", lastEvent->at(0));
EXPECT_EQ("eight", lastEvent->at(1));
EXPECT_EQ("nine", lastEvent->at(2));
}
// clean-up the event subscriptions
for (const auto& [eventSubscription, _] : eventSubscriptionsWithListener) {
eventSubscription.getPropertyAsFunction(rt, "remove")
.callWithThis(rt, eventSubscription);
}
flushQueue();
// Emit with function
EXPECT_NO_THROW(eventEmitter.emit(
[jsInvoker = invoker,
value = {"ten", "eleven", "twelve"}](jsi::Runtime& rt) -> jsi::Value {
return bridging::toJs(rt, value, jsInvoker);
}));
flushQueue();
// no new data as listeners had been removed
for (const auto& [_, lastEvent] : eventSubscriptionsWithListener) {
EXPECT_EQ(3, lastEvent->size());
EXPECT_EQ("seven", lastEvent->at(0));
EXPECT_EQ("eight", lastEvent->at(1));
EXPECT_EQ("nine", lastEvent->at(2));
}
}
TEST_F(BridgingTest, optionalTest) {
EXPECT_EQ(
1, bridging::fromJs<std::optional<int>>(rt, jsi::Value(1), invoker));
EXPECT_EQ(
1,
bridging::fromJs<std::optional<int>>(
rt, std::make_optional(jsi::Value(1)), invoker));
EXPECT_EQ(
"hi"s,
bridging::fromJs<std::optional<std::string>>(
rt,
std::make_optional(jsi::String::createFromAscii(rt, "hi")),
invoker));
EXPECT_FALSE(
bridging::fromJs<std::optional<int>>(rt, jsi::Value::undefined(), invoker)
.has_value());
EXPECT_FALSE(
bridging::fromJs<std::optional<int>>(rt, jsi::Value::null(), invoker)
.has_value());
EXPECT_TRUE(bridging::toJs(rt, std::optional<int>(), invoker).isNull());
EXPECT_EQ(1, bridging::toJs(rt, std::optional<int>(1), invoker).asNumber());
}
TEST_F(BridgingTest, pointerTest) {
auto str = "hi"s;
auto unique = std::make_unique<std::string>(str);
auto shared = std::make_shared<std::string>(str);
auto weak = std::weak_ptr<std::string>(shared);
EXPECT_EQ(str, bridging::toJs(rt, unique, invoker).asString(rt).utf8(rt));
EXPECT_EQ(str, bridging::toJs(rt, shared, invoker).asString(rt).utf8(rt));
EXPECT_EQ(str, bridging::toJs(rt, weak, invoker).asString(rt).utf8(rt));
shared.reset();
EXPECT_TRUE(bridging::toJs(rt, weak, invoker).isNull());
}
TEST_F(BridgingTest, supportTest) {
// Ensure sure can convert some basic types, including primitives that can be
// trivially converted to JSI values.
EXPECT_TRUE((bridging::supportsFromJs<bool>));
EXPECT_TRUE((bridging::supportsFromJs<bool, bool>));
EXPECT_TRUE((bridging::supportsFromJs<bool, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<int>));
EXPECT_TRUE((bridging::supportsFromJs<int, int>));
EXPECT_TRUE((bridging::supportsFromJs<int, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<double>));
EXPECT_TRUE((bridging::supportsFromJs<double, double>));
EXPECT_TRUE((bridging::supportsFromJs<double, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<std::string>));
EXPECT_TRUE((bridging::supportsFromJs<std::string, jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<std::string, jsi::String&>));
EXPECT_TRUE((bridging::supportsFromJs<std::set<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<std::set<int>, jsi::Array&>));
EXPECT_TRUE((bridging::supportsFromJs<std::vector<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<std::vector<int>, jsi::Array&>));
EXPECT_TRUE((
bridging::
supportsFromJs<std::vector<std::array<std::string, 2>>, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<
std::vector<std::array<std::string, 2>>,
jsi::Array&>));
EXPECT_TRUE(
(bridging::supportsFromJs<std::map<std::string, int>, jsi::Object>));
EXPECT_TRUE(
(bridging::supportsFromJs<std::map<std::string, int>, jsi::Object&>));
// Ensure incompatible conversions will fail.
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<std::string, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<std::string, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<std::set<int>, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<std::set<int>, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<std::vector<int>, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<std::vector<int>, jsi::String&>));
// Ensure copying and down casting JSI values is also supported.
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String, jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String, jsi::String&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Object&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Array&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Function&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Array&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Object&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Function&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Object&>));
// Ensure incorrect casts will fail.
EXPECT_FALSE((bridging::supportsFromJs<jsi::Array, jsi::Function>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Array, jsi::Function&>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Function, jsi::Array>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Function, jsi::Array&>));
// Ensure we can create HighResTimeStamp and HighResDuration from JSI
// values.
EXPECT_TRUE((bridging::supportsFromJs<HighResTimeStamp, jsi::Value>));
EXPECT_TRUE((bridging::supportsFromJs<HighResTimeStamp, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<HighResDuration, jsi::Value>));
EXPECT_TRUE((bridging::supportsFromJs<HighResDuration, jsi::Value&>));
// Ensure we can convert some basic types to JSI values.
EXPECT_TRUE((bridging::supportsToJs<bool>));
EXPECT_TRUE((bridging::supportsToJs<int>));
EXPECT_TRUE((bridging::supportsToJs<double>));
EXPECT_TRUE((bridging::supportsToJs<std::string>));
EXPECT_TRUE((bridging::supportsToJs<std::string, jsi::String>));
EXPECT_TRUE((bridging::supportsToJs<std::set<int>>));
EXPECT_TRUE((bridging::supportsToJs<std::set<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsToJs<std::vector<int>>));
EXPECT_TRUE((bridging::supportsToJs<std::vector<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsToJs<std::map<std::string, int>>));
EXPECT_TRUE(
(bridging::supportsToJs<std::map<std::string, int>, jsi::Object>));
EXPECT_TRUE((bridging::supportsToJs<void (*)()>));
EXPECT_TRUE((bridging::supportsToJs<void (*)(), jsi::Function>));
// Ensure invalid conversions to JSI values are not supported.
EXPECT_FALSE((bridging::supportsToJs<void*>));
EXPECT_FALSE((bridging::supportsToJs<bool, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<int, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<double, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<std::string, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<std::vector<int>, jsi::Function>));
// Ensure we can convert HighResTimeStamp and HighResDuration to
// DOMHighResTimeStamp (double).
EXPECT_TRUE((bridging::supportsToJs<HighResTimeStamp, double>));
EXPECT_TRUE((bridging::supportsToJs<HighResDuration, double>));
}
TEST_F(BridgingTest, dynamicTest) {
// Null
auto nullFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value::null(), invoker);
EXPECT_TRUE(nullFromJsResult.isNull());
auto nullToJsResult = bridging::toJs<folly::dynamic>(rt, nullptr, invoker);
EXPECT_TRUE(nullToJsResult.isNull());
// Boolean
auto booleanFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value(true), invoker);
EXPECT_TRUE(booleanFromJsResult.isBool());
EXPECT_TRUE(booleanFromJsResult.asBool());
auto booleanToJsResult = bridging::toJs<folly::dynamic>(rt, true, invoker);
EXPECT_TRUE(booleanToJsResult.isBool());
EXPECT_TRUE(booleanToJsResult.asBool());
// Number
auto numberFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value(1.2), invoker);
EXPECT_TRUE(numberFromJsResult.isNumber());
EXPECT_DOUBLE_EQ(1.2, numberFromJsResult.asDouble());
auto numberToJsResult = bridging::toJs<folly::dynamic>(rt, 1.2, invoker);
EXPECT_TRUE(numberToJsResult.isNumber());
EXPECT_DOUBLE_EQ(1.2, numberToJsResult.asNumber());
// String
auto stringFromJsResult = bridging::fromJs<folly::dynamic>(
rt, jsi::Value(jsi::String::createFromAscii(rt, "hello")), invoker);
EXPECT_TRUE(stringFromJsResult.isString());
EXPECT_EQ("hello"s, stringFromJsResult.asString());
auto stringToJsResult = bridging::toJs<folly::dynamic>(rt, "hello", invoker);
EXPECT_TRUE(stringToJsResult.isString());
EXPECT_EQ("hello"s, stringToJsResult.asString(rt).utf8(rt));
// Array
auto arrayFromJsResult = bridging::fromJs<folly::dynamic>(
rt,
jsi::Value(jsi::Array::createWithElements(rt, "foo", "bar")),
invoker);
EXPECT_TRUE(arrayFromJsResult.isArray());
EXPECT_EQ(2, arrayFromJsResult.size());
EXPECT_EQ("foo"s, arrayFromJsResult[0].asString());
EXPECT_EQ("bar"s, arrayFromJsResult[1].asString());
auto arrayToJsResult = bridging::toJs<folly::dynamic>(
rt, folly::dynamic::array("foo", "bar"), invoker);
EXPECT_TRUE(arrayToJsResult.isObject());
EXPECT_TRUE(arrayToJsResult.asObject(rt).isArray(rt));
auto arrayToJsResultArray = arrayToJsResult.asObject(rt).asArray(rt);
EXPECT_EQ(2, arrayToJsResultArray.size(rt));
EXPECT_EQ(
"foo"s,
arrayToJsResultArray.getValueAtIndex(rt, 0).asString(rt).utf8(rt));
EXPECT_EQ(
"bar"s,
arrayToJsResultArray.getValueAtIndex(rt, 1).asString(rt).utf8(rt));
// Object
auto jsiObject = jsi::Object(rt);
jsiObject.setProperty(rt, "foo", "bar");
auto objectFromJsResult = bridging::fromJs<folly::dynamic>(
rt, jsi::Value(std::move(jsiObject)), invoker);
EXPECT_TRUE(objectFromJsResult.isObject());
EXPECT_EQ(1, objectFromJsResult.size());
EXPECT_EQ("bar"s, objectFromJsResult["foo"].asString());
auto objectToJsResult = bridging::toJs<folly::dynamic>(
rt, folly::dynamic::object("foo", "bar"), invoker);
EXPECT_TRUE(objectToJsResult.isObject());
auto objectToJsResultObject = objectToJsResult.asObject(rt);
EXPECT_EQ(
"bar"s,
objectToJsResultObject.getProperty(rt, "foo").asString(rt).utf8(rt));
// Undefined
auto undefinedFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value::undefined(), invoker);
EXPECT_TRUE(undefinedFromJsResult.isNull());
}
TEST_F(BridgingTest, highResTimeStampTest) {
HighResTimeStamp timestamp = HighResTimeStamp::now();
EXPECT_EQ(
timestamp,
bridging::fromJs<HighResTimeStamp>(
rt, bridging::toJs(rt, timestamp), invoker));
auto duration = HighResDuration::fromNanoseconds(1);
EXPECT_EQ(
duration,
bridging::fromJs<HighResDuration>(
rt, bridging::toJs(rt, duration), invoker));
EXPECT_EQ(1.0, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6)));
EXPECT_EQ(
1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1)));
}
} // namespace facebook::react