diff --git a/examples/ReactExample/components/realm.js b/examples/ReactExample/components/realm.js
index 13d042ad..6e71639a 100644
--- a/examples/ReactExample/components/realm.js
+++ b/examples/ReactExample/components/realm.js
@@ -34,6 +34,7 @@ TodoList.schema = {
name: 'TodoList',
properties: {
name: 'string',
+ creationDate: 'date',
items: {type: 'list', objectType: 'Todo'},
},
};
diff --git a/examples/ReactExample/components/todo-app.js b/examples/ReactExample/components/todo-app.js
index c0d03f70..d2ff4fe4 100644
--- a/examples/ReactExample/components/todo-app.js
+++ b/examples/ReactExample/components/todo-app.js
@@ -38,15 +38,18 @@ export default class TodoApp extends React.Component {
constructor(props) {
super(props);
- let todoLists = realm.objects('TodoList');
- if (todoLists.length < 1) {
+ // This is a Results object, which will live-update.
+ this.todoLists = realm.objects('TodoList').sorted('creationDate');
+ if (this.todoLists.length < 1) {
realm.write(() => {
- realm.create('TodoList', {name: 'Todo List'});
+ realm.create('TodoList', {name: 'Todo List', creationDate: new Date()});
});
}
+ this.todoLists.addListener((name, changes) => {
+ console.log("changed: " + JSON.stringify(changes));
+ });
+ console.log("registered listener");
- // This is a Results object, which will live-update.
- this.todoLists = todoLists;
// Bind all the methods that we will be passing as props.
this.renderScene = this.renderScene.bind(this);
@@ -79,7 +82,6 @@ export default class TodoApp extends React.Component {
component: TodoListView,
passProps: {
ref: 'listView',
- items: this.todoLists,
extraItems: extraItems,
onPressItem: this._onPressTodoList,
},
@@ -105,7 +107,8 @@ export default class TodoApp extends React.Component {
}
renderScene(route) {
- return
+ console.log(this.todoLists);
+ return
}
_addNewTodoItem(list) {
@@ -128,7 +131,7 @@ export default class TodoApp extends React.Component {
}
realm.write(() => {
- realm.create('TodoList', {name: ''});
+ realm.create('TodoList', {name: '', creationDate: new Date()});
});
this._setEditingRow(items.length - 1);
diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj
index 7613a1dc..52761cd8 100644
--- a/src/RealmJS.xcodeproj/project.pbxproj
+++ b/src/RealmJS.xcodeproj/project.pbxproj
@@ -123,6 +123,7 @@
029048101C0428DF00ABDED4 /* rpc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rpc.hpp; sourceTree = ""; };
029048351C042A3C00ABDED4 /* platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = platform.hpp; sourceTree = ""; };
029048381C042A8F00ABDED4 /* platform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform.mm; sourceTree = ""; };
+ 0290934A1CEFA9170009769E /* js_observable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_observable.hpp; sourceTree = ""; };
02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -288,6 +289,7 @@
F6874A441CAD2ACD00EEEE36 /* JSC */,
F62BF9001CAC72C40022BCDC /* Node */,
F62A35141C18E783004A917D /* Object Store */,
+ 0290934A1CEFA9170009769E /* js_observable.hpp */,
F60102F71CBDA6D400EC01BA /* js_collection.hpp */,
029048041C0428DF00ABDED4 /* js_list.hpp */,
029048061C0428DF00ABDED4 /* js_realm_object.hpp */,
diff --git a/src/js_collection.hpp b/src/js_collection.hpp
index 510333a7..9a758d18 100644
--- a/src/js_collection.hpp
+++ b/src/js_collection.hpp
@@ -19,6 +19,10 @@
#pragma once
#include "js_class.hpp"
+#include "js_types.hpp"
+#include "js_observable.hpp"
+
+#include "collection_notifications.hpp"
namespace realm {
namespace js {
@@ -27,9 +31,40 @@ namespace js {
class Collection {};
template
-struct CollectionClass : ClassDefinition {
+struct CollectionClass : ClassDefinition> {
+ using ContextType = typename T::Context;
+ using ValueType = typename T::Value;
+ using ObjectType = typename T::Object;
+ using Object = js::Object;
+ using Value = js::Value;
+
std::string const name = "Collection";
+
+ static inline ValueType create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set);
};
+template
+typename T::Value CollectionClass::create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set)
+{
+ ObjectType object = Object::create_empty(ctx);
+ std::vector deletions, insertions, modifications;
+ for (auto index : change_set.deletions.as_indexes()) {
+ deletions.push_back(Value::from_number(ctx, index));
+ }
+ Object::set_property(ctx, object, "deletions", Object::create_array(ctx, deletions));
+
+ for (auto index : change_set.insertions.as_indexes()) {
+ insertions.push_back(Value::from_number(ctx, index));
+ }
+ Object::set_property(ctx, object, "insertions", Object::create_array(ctx, insertions));
+
+ for (auto index : change_set.modifications.as_indexes()) {
+ modifications.push_back(Value::from_number(ctx, index));
+ }
+ Object::set_property(ctx, object, "modifications", Object::create_array(ctx, modifications));
+
+ return object;
+}
+
} // js
} // realm
diff --git a/src/js_list.hpp b/src/js_list.hpp
index 35c632e1..e8d40432 100644
--- a/src/js_list.hpp
+++ b/src/js_list.hpp
@@ -33,10 +33,20 @@ namespace realm {
namespace js {
template
-struct ListClass : ClassDefinition> {
+class List : public realm::List {
+ public:
+ List(std::shared_ptr r, const ObjectSchema& s, LinkViewRef l) noexcept : realm::List(r, l) {}
+ List(const realm::List &l) : realm::List(l) {}
+
+ std::map, NotificationToken, typename Protected::Comparator> m_notification_tokens;
+};
+
+template
+struct ListClass : ClassDefinition, CollectionClass> {
using ContextType = typename T::Context;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
+ using FunctionType = typename T::Function;
using Object = js::Object;
using Value = js::Value;
using ReturnValue = js::ReturnValue;
@@ -58,7 +68,12 @@ struct ListClass : ClassDefinition> {
static void filtered(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
-
+
+ // observable
+ static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
+ static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
+ static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
+
std::string const name = "List";
MethodMap const methods = {
@@ -71,6 +86,9 @@ struct ListClass : ClassDefinition> {
{"filtered", wrap},
{"sorted", wrap},
{"isValid", wrap},
+ {"addListener", wrap},
+ {"removeListener", wrap},
+ {"removeAllListeners", wrap},
};
PropertyMap const properties = {
@@ -82,7 +100,7 @@ struct ListClass : ClassDefinition> {
template
typename T::Object ListClass::create_instance(ContextType ctx, realm::List list) {
- return create_object>(ctx, new realm::List(std::move(list)));
+ return create_object>(ctx, new realm::js::List(std::move(list)));
}
template
@@ -230,6 +248,41 @@ template
void ListClass::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
return_value.set(get_internal>(this_object)->is_valid());
}
+
+template
+void ListClass::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
+ validate_argument_count(argc, 1);
+
+ auto list = get_internal>(this_object);
+ auto callback = Value::validated_to_function(ctx, arguments[0]);
+ Protected protected_callback(ctx, callback);
+ Protected protected_this(ctx, this_object);
+ Protected protected_ctx(Context::get_global_context(ctx));
+
+ list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
+ ValueType arguments[2];
+ arguments[0] = static_cast(protected_this);
+ arguments[1] = Value::from_undefined(protected_ctx);
+ Function::call(protected_ctx, protected_callback, protected_this, 2, arguments);
+ });
+}
+
+template
+void ListClass::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
+ validate_argument_count(argc, 1);
+
+ auto list = get_internal>(this_object);
+ auto callback = Value::validated_to_function(ctx, arguments[0]);
+ list->m_notification_tokens.erase(Protected(ctx, callback));
+}
+
+template
+void ListClass::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
+ validate_argument_count(argc, 0);
+
+ auto list = get_internal>(this_object);
+ list->m_notification_tokens.clear();
+}
} // js
} // realm
diff --git a/src/js_observable.hpp b/src/js_observable.hpp
new file mode 100644
index 00000000..a49a1d0e
--- /dev/null
+++ b/src/js_observable.hpp
@@ -0,0 +1,35 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2016 Realm Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include "js_class.hpp"
+
+namespace realm {
+namespace js {
+
+// Empty class that merely serves as useful type for now.
+class Observable {};
+
+template
+struct ObservableClass : ClassDefinition {
+ std::string const name = "Observable";
+};
+
+} // js
+} // realm
diff --git a/src/js_realm.hpp b/src/js_realm.hpp
index 06821ac3..a5098c7f 100644
--- a/src/js_realm.hpp
+++ b/src/js_realm.hpp
@@ -28,6 +28,7 @@
#include "js_list.hpp"
#include "js_results.hpp"
#include "js_schema.hpp"
+#include "js_observable.hpp"
#include "shared_realm.hpp"
#include "binding_context.hpp"
@@ -124,7 +125,7 @@ void set_default_path(std::string path);
void delete_all_realms();
template
-class RealmClass : public ClassDefinition {
+class RealmClass : public ClassDefinition> {
using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
diff --git a/src/js_results.hpp b/src/js_results.hpp
index 6f1ef8e9..bc909f2e 100644
--- a/src/js_results.hpp
+++ b/src/js_results.hpp
@@ -30,10 +30,29 @@ namespace realm {
namespace js {
template
-struct ResultsClass : ClassDefinition> {
+class Results : public realm::Results {
+ public:
+ Results(Results const& r) : realm::Results(r) {};
+ Results(realm::Results const& r) : realm::Results(r) {};
+ Results(Results&&) = default;
+ Results& operator=(Results&&) = default;
+ Results& operator=(Results const&) = default;
+
+ Results() = default;
+ Results(SharedRealm r, Table& table) : realm::Results(r, table) {}
+ Results(SharedRealm r, Query q, SortOrder s = {}) : realm::Results(r, q, s) {}
+ Results(SharedRealm r, TableView tv, SortOrder s) : realm::Results(r, tv, s) {}
+ Results(SharedRealm r, LinkViewRef lv, util::Optional q = {}, SortOrder s = {}) : realm::Results(r, lv, q, s) {}
+
+ std::map, NotificationToken, typename Protected::Comparator> m_notification_tokens;
+};
+
+template
+struct ResultsClass : ClassDefinition, CollectionClass> {
using ContextType = typename T::Context;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
+ using FunctionType = typename T::Function;
using Object = js::Object;
using Value = js::Value;
using ReturnValue = js::ReturnValue;
@@ -55,6 +74,11 @@ struct ResultsClass : ClassDefinition> {
static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
+ // observable
+ static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
+ static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
+ static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
+
std::string const name = "Results";
MethodMap const methods = {
@@ -62,6 +86,9 @@ struct ResultsClass : ClassDefinition> {
{"filtered", wrap},
{"sorted", wrap},
{"isValid", wrap},
+ {"addListener", wrap},
+ {"removeListener", wrap},
+ {"removeAllListeners", wrap},
};
PropertyMap const properties = {
@@ -73,13 +100,13 @@ struct ResultsClass : ClassDefinition> {
template
typename T::Object ResultsClass::create_instance(ContextType ctx, realm::Results results) {
- return create_object>(ctx, new realm::Results(std::move(results)));
+ return create_object>(ctx, new realm::js::Results(std::move(results)));
}
template
typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema) {
auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
- return create_object>(ctx, new realm::Results(realm, *table));
+ return create_object>(ctx, new realm::js::Results(realm, *table));
}
template
@@ -153,7 +180,7 @@ typename T::Object ResultsClass::create_sorted(ContextType ctx, const U &coll
columns.push_back(prop->table_column);
}
- auto results = new realm::Results(realm, collection.get_query(), {std::move(columns), std::move(ascending)});
+ auto results = new realm::js::Results(realm, collection.get_query(), {std::move(columns), std::move(ascending)});
return create_object>(ctx, results);
}
@@ -206,6 +233,42 @@ template
void ResultsClass::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
return_value.set(get_internal>(this_object)->is_valid());
}
+
+template
+void ResultsClass::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
+ validate_argument_count(argc, 1);
+
+ auto results = get_internal>(this_object);
+ auto callback = Value::validated_to_function(ctx, arguments[0]);
+ Protected protected_callback(ctx, callback);
+ Protected protected_this(ctx, this_object);
+ Protected protected_ctx(Context::get_global_context(ctx));
+
+ auto token = results->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
+ ValueType arguments[2];
+ arguments[0] = static_cast(protected_this);
+ arguments[1] = CollectionClass::create_collection_change_set(protected_ctx, change_set);
+ Function::call(protected_ctx, protected_callback, protected_this, 2, arguments);
+ });
+ results->m_notification_tokens.emplace(protected_callback, std::move(token));
+}
+template
+void ResultsClass::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
+ validate_argument_count(argc, 1);
+
+ auto results = get_internal>(this_object);
+ auto callback = Value::validated_to_function(ctx, arguments[0]);
+ results->m_notification_tokens.erase(Protected(ctx, callback));
+}
+
+template
+void ResultsClass::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
+ validate_argument_count(argc, 0);
+
+ auto results = get_internal>(this_object);
+ results->m_notification_tokens.clear();
+}
+
} // js
} // realm
diff --git a/src/js_types.hpp b/src/js_types.hpp
index 2b1f1fbc..d0f3b6a4 100644
--- a/src/js_types.hpp
+++ b/src/js_types.hpp
@@ -247,6 +247,10 @@ class Protected {
bool operator!=(const ValueType &) const;
bool operator==(const Protected &) const;
bool operator!=(const Protected &) const;
+
+ struct Comparator {
+ bool operator()(const Protected& a, const Protected& b) const;
+ };
};
template
diff --git a/src/jsc/jsc_protected.hpp b/src/jsc/jsc_protected.hpp
index 6c20d039..34ac870f 100644
--- a/src/jsc/jsc_protected.hpp
+++ b/src/jsc/jsc_protected.hpp
@@ -47,6 +47,12 @@ class Protected {
operator bool() const {
return m_context != nullptr;
}
+
+ struct Comparator {
+ bool operator() (const Protected& a, const Protected& b) const {
+ return a.m_context == b.m_context;
+ }
+ };
};
template<>
@@ -75,6 +81,15 @@ class Protected {
operator bool() const {
return m_value != nullptr;
}
+
+ struct Comparator {
+ bool operator() (const Protected& a, const Protected& b) const {
+ if (a.m_context != b.m_context) {
+ return false;
+ }
+ return JSValueIsStrictEqual(a.m_context, a.m_value, b.m_value);
+ }
+ };
};
template<>
diff --git a/src/node/node_protected.hpp b/src/node/node_protected.hpp
index 042a10a1..7253816c 100644
--- a/src/node/node_protected.hpp
+++ b/src/node/node_protected.hpp
@@ -50,6 +50,10 @@ class Protected {
bool operator!=(const Protected &other) const {
return m_value != other.m_value;
}
+
+ struct Comparator {
+ bool operator()(const Protected& a, const Protected& b) const { return a == b; }
+ };
};
} // node
diff --git a/tests/ios/RJSModuleLoader.h b/tests/ios/RJSModuleLoader.h
index 91ab5bac..a9c8f77c 100644
--- a/tests/ios/RJSModuleLoader.h
+++ b/tests/ios/RJSModuleLoader.h
@@ -27,5 +27,6 @@
- (JSValue *)loadModuleFromURL:(NSURL *)url error:(NSError **)error;
- (JSValue *)loadJSONFromURL:(NSURL *)url error:(NSError **)error;
+- (JSValue *)loadGlobalModule:(NSString *)name relativeToURL:(NSURL *)url error:(NSError **)error;
@end
diff --git a/tests/ios/RJSModuleLoader.m b/tests/ios/RJSModuleLoader.m
index 3c0613a9..39cbb066 100644
--- a/tests/ios/RJSModuleLoader.m
+++ b/tests/ios/RJSModuleLoader.m
@@ -195,6 +195,18 @@ static NSString * const RJSModuleLoaderErrorDomain = @"RJSModuleLoaderErrorDomai
BOOL isDirectory;
if ([fileManager fileExistsAtPath:moduleURL.path isDirectory:&isDirectory] && isDirectory) {
+ NSURL *packageURL = [moduleURL URLByAppendingPathComponent:@"package.json"];
+ NSDictionary *package;
+
+ if ([fileManager fileExistsAtPath:packageURL.path]) {
+ NSError *error;
+ NSData *data = [NSData dataWithContentsOfURL:packageURL options:0 error:&error];
+
+ package = data ? [NSJSONSerialization JSONObjectWithData:data options:0 error:&error] : nil;
+ NSAssert(package, @"%@", error);
+ }
+
+ moduleURL = [moduleURL URLByAppendingPathComponent:package[@"main"] ?: @"index.js"];
return [self loadModuleFromURL:moduleURL error:error];
}
diff --git a/tests/ios/RealmJSCoreTests.m b/tests/ios/RealmJSCoreTests.m
index d3bf62c9..5fd0cf91 100644
--- a/tests/ios/RealmJSCoreTests.m
+++ b/tests/ios/RealmJSCoreTests.m
@@ -31,12 +31,23 @@
+ (XCTestSuite *)defaultTestSuite {
XCTestSuite *suite = [super defaultTestSuite];
- JSContext *context = [[JSContext alloc] init];
+
+ // We need a JS context from a UIWebView so it has setTimeout, Promise, etc.
+ UIWebView *webView = [[UIWebView alloc] init];
+ JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
RJSModuleLoader *moduleLoader = [[RJSModuleLoader alloc] initWithContext:context];
NSURL *realmURL = [[NSBundle bundleForClass:self] URLForResource:@"index" withExtension:@"js" subdirectory:@"lib"];
NSURL *scriptURL = [[NSBundle bundleForClass:self] URLForResource:@"index" withExtension:@"js" subdirectory:@"js"];
NSError *error;
+ // The ES6 global Promise constructor was added in iOS 8.
+ if (![context[@"Promise"] isObject]) {
+ JSValue *promiseModule = [moduleLoader loadGlobalModule:@"es6-promise" relativeToURL:scriptURL error:&error];
+ NSAssert(promiseModule, @"%@", error);
+
+ context[@"Promise"] = promiseModule[@"Promise"];
+ }
+
// Create Realm constructor in the JS context.
RJSInitializeInContext(context.JSGlobalContextRef);
@@ -73,25 +84,46 @@
JSContext *context = testObject.context;
context.exception = nil;
- [testObject invokeMethod:@"runTest" withArguments:@[NSStringFromClass(self.class), method]];
+ JSValue *promise = [testObject invokeMethod:@"runTest" withArguments:@[NSStringFromClass(self.class), method]];
- JSValue *exception = context.exception;
- if (exception) {
- JSValue *message = [exception hasProperty:@"message"] ? exception[@"message"] : exception;
- NSString *source = [exception hasProperty:@"sourceURL"] ? [exception[@"sourceURL"] toString] : nil;
- NSUInteger line = [exception hasProperty:@"line"] ? [exception[@"line"] toUInt32] - 1 : 0;
- NSURL *sourceURL = nil;
+ if (context.exception) {
+ [self recordException:context.exception];
+ return;
+ }
- if (source) {
- NSString *path = [NSString pathWithComponents:@[[@(__FILE__) stringByDeletingLastPathComponent], @"..", @"js", source.lastPathComponent]];
- sourceURL = [NSURL URLWithString:path];
- }
+ if ([promise isObject]) {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Promise resolved or rejected"];
- [self recordFailureWithDescription:message.description
- inFile:sourceURL ? sourceURL.absoluteString : @(__FILE__)
- atLine:sourceURL ? line : __LINE__
- expected:YES];
+ JSValue *onFulfilled = [JSValue valueWithObject:^() {
+ [expectation fulfill];
+ } inContext:context];
+
+ JSValue *onRejected = [JSValue valueWithObject:^(JSValue *error) {
+ [self recordException:error];
+ [expectation fulfill];
+ } inContext:context];
+
+ [promise invokeMethod:@"then" withArguments:@[onFulfilled, onRejected]];
+
+ [self waitForExpectationsWithTimeout:5.0 handler:NULL];
}
}
+- (void)recordException:(JSValue *)exception {
+ JSValue *message = [exception hasProperty:@"message"] ? exception[@"message"] : exception;
+ NSString *source = [exception hasProperty:@"sourceURL"] ? [exception[@"sourceURL"] toString] : nil;
+ NSUInteger line = [exception hasProperty:@"line"] ? [exception[@"line"] toUInt32] - 1 : 0;
+ NSURL *sourceURL = nil;
+
+ if (source) {
+ NSString *path = [NSString pathWithComponents:@[[@(__FILE__) stringByDeletingLastPathComponent], @"..", @"js", source.lastPathComponent]];
+ sourceURL = [NSURL URLWithString:path];
+ }
+
+ [self recordFailureWithDescription:message.description
+ inFile:sourceURL ? sourceURL.absoluteString : @(__FILE__)
+ atLine:sourceURL ? line : __LINE__
+ expected:YES];
+}
+
@end
diff --git a/tests/js/package.json b/tests/js/package.json
index 07bbe26a..c7afa5d5 100644
--- a/tests/js/package.json
+++ b/tests/js/package.json
@@ -1,5 +1,8 @@
{
"name": "realm-tests",
"version": "0.0.1",
- "private": true
+ "private": true,
+ "dependencies": {
+ "es6-promise": "^3.2.1"
+ }
}