move collection notification changes on top of master
This commit is contained in:
commit
49aa5bc0f5
|
@ -34,6 +34,7 @@ TodoList.schema = {
|
|||
name: 'TodoList',
|
||||
properties: {
|
||||
name: 'string',
|
||||
creationDate: 'date',
|
||||
items: {type: 'list', objectType: 'Todo'},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 <route.component {...route.passProps} />
|
||||
console.log(this.todoLists);
|
||||
return <route.component items={this.todoLists} {...route.passProps} />
|
||||
}
|
||||
|
||||
_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);
|
||||
|
|
|
@ -123,6 +123,7 @@
|
|||
029048101C0428DF00ABDED4 /* rpc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rpc.hpp; sourceTree = "<group>"; };
|
||||
029048351C042A3C00ABDED4 /* platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = platform.hpp; sourceTree = "<group>"; };
|
||||
029048381C042A8F00ABDED4 /* platform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform.mm; sourceTree = "<group>"; };
|
||||
0290934A1CEFA9170009769E /* js_observable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_observable.hpp; sourceTree = "<group>"; };
|
||||
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 */,
|
||||
|
|
|
@ -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<typename T>
|
||||
struct CollectionClass : ClassDefinition<T, Collection> {
|
||||
struct CollectionClass : ClassDefinition<T, Collection, ObservableClass<T>> {
|
||||
using ContextType = typename T::Context;
|
||||
using ValueType = typename T::Value;
|
||||
using ObjectType = typename T::Object;
|
||||
using Object = js::Object<T>;
|
||||
using Value = js::Value<T>;
|
||||
|
||||
std::string const name = "Collection";
|
||||
|
||||
static inline ValueType create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
typename T::Value CollectionClass<T>::create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set)
|
||||
{
|
||||
ObjectType object = Object::create_empty(ctx);
|
||||
std::vector<ValueType> 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
|
||||
|
|
|
@ -33,10 +33,20 @@ namespace realm {
|
|||
namespace js {
|
||||
|
||||
template<typename T>
|
||||
struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
|
||||
class List : public realm::List {
|
||||
public:
|
||||
List(std::shared_ptr<Realm> r, const ObjectSchema& s, LinkViewRef l) noexcept : realm::List(r, l) {}
|
||||
List(const realm::List &l) : realm::List(l) {}
|
||||
|
||||
std::map<Protected<typename T::Function>, NotificationToken, typename Protected<typename T::Function>::Comparator> m_notification_tokens;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
|
||||
using ContextType = typename T::Context;
|
||||
using ObjectType = typename T::Object;
|
||||
using ValueType = typename T::Value;
|
||||
using FunctionType = typename T::Function;
|
||||
using Object = js::Object<T>;
|
||||
using Value = js::Value<T>;
|
||||
using ReturnValue = js::ReturnValue<T>;
|
||||
|
@ -58,7 +68,12 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
|
|||
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<T> const methods = {
|
||||
|
@ -71,6 +86,9 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
|
|||
{"filtered", wrap<filtered>},
|
||||
{"sorted", wrap<sorted>},
|
||||
{"isValid", wrap<is_valid>},
|
||||
{"addListener", wrap<add_listener>},
|
||||
{"removeListener", wrap<remove_listener>},
|
||||
{"removeAllListeners", wrap<remove_all_listeners>},
|
||||
};
|
||||
|
||||
PropertyMap<T> const properties = {
|
||||
|
@ -82,7 +100,7 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
|
|||
|
||||
template<typename T>
|
||||
typename T::Object ListClass<T>::create_instance(ContextType ctx, realm::List list) {
|
||||
return create_object<T, ListClass<T>>(ctx, new realm::List(std::move(list)));
|
||||
return create_object<T, ListClass<T>>(ctx, new realm::js::List<T>(std::move(list)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
@ -230,6 +248,41 @@ template<typename T>
|
|||
void ListClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::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<T, ListClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
Protected<FunctionType> protected_callback(ctx, callback);
|
||||
Protected<ObjectType> protected_this(ctx, this_object);
|
||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||
|
||||
list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
|
||||
ValueType arguments[2];
|
||||
arguments[0] = static_cast<ObjectType>(protected_this);
|
||||
arguments[1] = Value::from_undefined(protected_ctx);
|
||||
Function<T>::call(protected_ctx, protected_callback, protected_this, 2, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::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<T, ListClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
list->m_notification_tokens.erase(Protected<FunctionType>(ctx, callback));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::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<T, ListClass<T>>(this_object);
|
||||
list->m_notification_tokens.clear();
|
||||
}
|
||||
|
||||
} // js
|
||||
} // realm
|
||||
|
|
|
@ -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<typename T>
|
||||
struct ObservableClass : ClassDefinition<T, Observable> {
|
||||
std::string const name = "Observable";
|
||||
};
|
||||
|
||||
} // js
|
||||
} // realm
|
|
@ -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<typename T>
|
||||
class RealmClass : public ClassDefinition<T, SharedRealm> {
|
||||
class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
|
||||
using GlobalContextType = typename T::GlobalContext;
|
||||
using ContextType = typename T::Context;
|
||||
using FunctionType = typename T::Function;
|
||||
|
|
|
@ -30,10 +30,29 @@ namespace realm {
|
|||
namespace js {
|
||||
|
||||
template<typename T>
|
||||
struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
|
||||
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<Query> q = {}, SortOrder s = {}) : realm::Results(r, lv, q, s) {}
|
||||
|
||||
std::map<Protected<typename T::Function>, NotificationToken, typename Protected<typename T::Function>::Comparator> m_notification_tokens;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<T>> {
|
||||
using ContextType = typename T::Context;
|
||||
using ObjectType = typename T::Object;
|
||||
using ValueType = typename T::Value;
|
||||
using FunctionType = typename T::Function;
|
||||
using Object = js::Object<T>;
|
||||
using Value = js::Value<T>;
|
||||
using ReturnValue = js::ReturnValue<T>;
|
||||
|
@ -55,6 +74,11 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
|
|||
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<T> const methods = {
|
||||
|
@ -62,6 +86,9 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
|
|||
{"filtered", wrap<filtered>},
|
||||
{"sorted", wrap<sorted>},
|
||||
{"isValid", wrap<is_valid>},
|
||||
{"addListener", wrap<add_listener>},
|
||||
{"removeListener", wrap<remove_listener>},
|
||||
{"removeAllListeners", wrap<remove_all_listeners>},
|
||||
};
|
||||
|
||||
PropertyMap<T> const properties = {
|
||||
|
@ -73,13 +100,13 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
|
|||
|
||||
template<typename T>
|
||||
typename T::Object ResultsClass<T>::create_instance(ContextType ctx, realm::Results results) {
|
||||
return create_object<T, ResultsClass<T>>(ctx, new realm::Results(std::move(results)));
|
||||
return create_object<T, ResultsClass<T>>(ctx, new realm::js::Results<T>(std::move(results)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
typename T::Object ResultsClass<T>::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<T, ResultsClass<T>>(ctx, new realm::Results(realm, *table));
|
||||
return create_object<T, ResultsClass<T>>(ctx, new realm::js::Results<T>(realm, *table));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
@ -153,7 +180,7 @@ typename T::Object ResultsClass<T>::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<T>(realm, collection.get_query(), {std::move(columns), std::move(ascending)});
|
||||
return create_object<T, ResultsClass<T>>(ctx, results);
|
||||
}
|
||||
|
||||
|
@ -206,6 +233,42 @@ template<typename T>
|
|||
void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::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<T, ResultsClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
Protected<FunctionType> protected_callback(ctx, callback);
|
||||
Protected<ObjectType> protected_this(ctx, this_object);
|
||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||
|
||||
auto token = results->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
|
||||
ValueType arguments[2];
|
||||
arguments[0] = static_cast<ObjectType>(protected_this);
|
||||
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
|
||||
Function<T>::call(protected_ctx, protected_callback, protected_this, 2, arguments);
|
||||
});
|
||||
results->m_notification_tokens.emplace(protected_callback, std::move(token));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::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<T, ResultsClass<T>>(this_object);
|
||||
auto callback = Value::validated_to_function(ctx, arguments[0]);
|
||||
results->m_notification_tokens.erase(Protected<FunctionType>(ctx, callback));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::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<T, ResultsClass<T>>(this_object);
|
||||
results->m_notification_tokens.clear();
|
||||
}
|
||||
|
||||
} // js
|
||||
} // realm
|
||||
|
|
|
@ -247,6 +247,10 @@ class Protected {
|
|||
bool operator!=(const ValueType &) const;
|
||||
bool operator==(const Protected<ValueType> &) const;
|
||||
bool operator!=(const Protected<ValueType> &) const;
|
||||
|
||||
struct Comparator {
|
||||
bool operator()(const Protected<ValueType>& a, const Protected<ValueType>& b) const;
|
||||
};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -47,6 +47,12 @@ class Protected<JSGlobalContextRef> {
|
|||
operator bool() const {
|
||||
return m_context != nullptr;
|
||||
}
|
||||
|
||||
struct Comparator {
|
||||
bool operator() (const Protected<JSGlobalContextRef>& a, const Protected<JSGlobalContextRef>& b) const {
|
||||
return a.m_context == b.m_context;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template<>
|
||||
|
@ -75,6 +81,15 @@ class Protected<JSValueRef> {
|
|||
operator bool() const {
|
||||
return m_value != nullptr;
|
||||
}
|
||||
|
||||
struct Comparator {
|
||||
bool operator() (const Protected<JSValueRef>& a, const Protected<JSValueRef>& b) const {
|
||||
if (a.m_context != b.m_context) {
|
||||
return false;
|
||||
}
|
||||
return JSValueIsStrictEqual(a.m_context, a.m_value, b.m_value);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -50,6 +50,10 @@ class Protected {
|
|||
bool operator!=(const Protected<MemberType> &other) const {
|
||||
return m_value != other.m_value;
|
||||
}
|
||||
|
||||
struct Comparator {
|
||||
bool operator()(const Protected<MemberType>& a, const Protected<MemberType>& b) const { return a == b; }
|
||||
};
|
||||
};
|
||||
|
||||
} // node
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"name": "realm-tests",
|
||||
"version": "0.0.1",
|
||||
"private": true
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"es6-promise": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue