Support ES6 class inheritance from Realm.Object
Unfortunately, this was broken because our native Realm.Object constructor was not presenting itself as a function in JS. This fixes that and adds tests.
This commit is contained in:
parent
651449108e
commit
26d2c169a9
|
@ -20,20 +20,22 @@
|
||||||
|
|
||||||
import Realm from 'realm';
|
import Realm from 'realm';
|
||||||
|
|
||||||
class Todo {}
|
class Todo extends Realm.Object {}
|
||||||
Todo.schema = {
|
Todo.schema = {
|
||||||
name: 'Todo',
|
name: 'Todo',
|
||||||
properties: {
|
properties: {
|
||||||
done: {type: Realm.Types.BOOL, default: false},
|
done: {type: 'bool', default: false},
|
||||||
text: Realm.Types.STRING,
|
text: 'string',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
class TodoList {}
|
|
||||||
|
class TodoList extends Realm.Object {}
|
||||||
TodoList.schema = {
|
TodoList.schema = {
|
||||||
name: 'TodoList',
|
name: 'TodoList',
|
||||||
properties: {
|
properties: {
|
||||||
name: Realm.Types.STRING,
|
name: 'string',
|
||||||
items: {type: Realm.Types.LIST, objectType: 'Todo', default: []},
|
items: {type: 'list', objectType: 'Todo'},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default new Realm({schema: [Todo, TodoList]});
|
export default new Realm({schema: [Todo, TodoList]});
|
||||||
|
|
|
@ -48,10 +48,7 @@ class ObjectWrap {
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSObjectRef create_constructor(JSContextRef ctx) {
|
static JSObjectRef create_constructor(JSContextRef ctx) {
|
||||||
if (JSClassRef constructor_class = get_constructor_class()) {
|
return JSObjectMake(ctx, get_constructor_class(), nullptr);
|
||||||
return JSObjectMake(ctx, constructor_class, nullptr);
|
|
||||||
}
|
|
||||||
return JSObjectMakeConstructor(ctx, get_class(), construct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSClassRef get_class() {
|
static JSClassRef get_class() {
|
||||||
|
@ -92,7 +89,9 @@ class ObjectWrap {
|
||||||
static std::vector<JSStaticFunction> get_methods(const MethodMap &);
|
static std::vector<JSStaticFunction> get_methods(const MethodMap &);
|
||||||
static std::vector<JSStaticValue> get_properties(const PropertyMap &);
|
static std::vector<JSStaticValue> get_properties(const PropertyMap &);
|
||||||
|
|
||||||
|
static JSValueRef call(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);
|
||||||
static JSObjectRef construct(JSContextRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);
|
static JSObjectRef construct(JSContextRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);
|
||||||
|
static void initialize_constructor(JSContextRef, JSObjectRef);
|
||||||
static void finalize(JSObjectRef);
|
static void finalize(JSObjectRef);
|
||||||
static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef);
|
static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef);
|
||||||
static JSValueRef get_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*);
|
static JSValueRef get_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*);
|
||||||
|
@ -104,7 +103,7 @@ class ObjectWrap {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool has_instance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, JSValueRef* exception) {
|
static bool has_instance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, JSValueRef* exception) {
|
||||||
return JSValueIsObjectOfClass(ctx, value, get_class());
|
return has_instance(ctx, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -158,19 +157,18 @@ inline JSClassRef ObjectWrap<ClassType>::create_class() {
|
||||||
|
|
||||||
template<typename ClassType>
|
template<typename ClassType>
|
||||||
inline JSClassRef ObjectWrap<ClassType>::create_constructor_class() {
|
inline JSClassRef ObjectWrap<ClassType>::create_constructor_class() {
|
||||||
// Skip creating a special constructor class if possible.
|
|
||||||
if (!s_class.constructor && s_class.static_methods.empty() && s_class.static_properties.empty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSClassDefinition definition = kJSClassDefinitionEmpty;
|
JSClassDefinition definition = kJSClassDefinitionEmpty;
|
||||||
std::vector<JSStaticFunction> methods;
|
std::vector<JSStaticFunction> methods;
|
||||||
std::vector<JSStaticValue> properties;
|
std::vector<JSStaticValue> properties;
|
||||||
|
|
||||||
definition.attributes = kJSClassAttributeNoAutomaticPrototype;
|
definition.attributes = kJSClassAttributeNoAutomaticPrototype;
|
||||||
definition.className = s_class.name.c_str();
|
definition.className = "Function";
|
||||||
|
definition.initialize = initialize_constructor;
|
||||||
definition.hasInstance = has_instance;
|
definition.hasInstance = has_instance;
|
||||||
|
|
||||||
|
// This must be set for `typeof constructor` to be 'function'.
|
||||||
|
definition.callAsFunction = call;
|
||||||
|
|
||||||
if (s_class.constructor) {
|
if (s_class.constructor) {
|
||||||
definition.callAsConstructor = construct;
|
definition.callAsConstructor = construct;
|
||||||
}
|
}
|
||||||
|
@ -220,22 +218,57 @@ inline std::vector<JSStaticValue> ObjectWrap<ClassType>::get_properties(const Pr
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ClassType>
|
template<typename ClassType>
|
||||||
inline JSObjectRef ObjectWrap<ClassType>::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
|
inline JSValueRef ObjectWrap<ClassType>::call(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
|
||||||
if (!s_class.constructor) {
|
// This should only be called as a super() call in the constructor of a subclass.
|
||||||
*exception = jsc::Exception::value(ctx, "Illegal constructor");
|
if (!has_instance(ctx, this_object)) {
|
||||||
|
*exception = jsc::Exception::value(ctx, s_class.name + " cannot be called as a function");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSObjectRef this_object = ObjectWrap<ClassType>::create_instance(ctx);
|
// Classes without a constructor should still be subclassable.
|
||||||
|
if (s_class.constructor) {
|
||||||
try {
|
try {
|
||||||
s_class.constructor(ctx, this_object, argc, arguments);
|
s_class.constructor(ctx, this_object, argc, arguments);
|
||||||
}
|
}
|
||||||
catch (std::exception &e) {
|
catch (std::exception &e) {
|
||||||
*exception = jsc::Exception::value(ctx, e);
|
*exception = jsc::Exception::value(ctx, e);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSValueMakeUndefined(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ClassType>
|
||||||
|
inline JSObjectRef ObjectWrap<ClassType>::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
|
||||||
|
if (!s_class.constructor) {
|
||||||
|
*exception = jsc::Exception::value(ctx, s_class.name + " is not a constructor");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSObjectRef this_object = create_instance(ctx);
|
||||||
|
try {
|
||||||
|
s_class.constructor(ctx, this_object, argc, arguments);
|
||||||
|
}
|
||||||
|
catch (std::exception &e) {
|
||||||
|
*exception = jsc::Exception::value(ctx, e);
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return this_object;
|
return this_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename ClassType>
|
||||||
|
inline void ObjectWrap<ClassType>::initialize_constructor(JSContextRef ctx, JSObjectRef constructor) {
|
||||||
|
static const String prototype_string = "prototype";
|
||||||
|
|
||||||
|
// Set the prototype of the constructor to be Function.prototype.
|
||||||
|
Object::set_prototype(ctx, constructor, Object::get_prototype(ctx, JSObjectMakeFunctionWithCallback(ctx, nullptr, call)));
|
||||||
|
|
||||||
|
// Set the constructor prototype to be the prototype generated from the instance JSClassRef.
|
||||||
|
JSObjectRef prototype = Object::validated_get_object(ctx, JSObjectMakeConstructor(ctx, get_class(), construct), prototype_string);
|
||||||
|
Object::set_property(ctx, constructor, prototype_string, prototype, js::ReadOnly | js::DontEnum | js::DontDelete);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename ClassType>
|
template<typename ClassType>
|
||||||
inline void ObjectWrap<ClassType>::finalize(JSObjectRef object) {
|
inline void ObjectWrap<ClassType>::finalize(JSObjectRef object) {
|
||||||
// This is called for the most derived class before superclasses.
|
// This is called for the most derived class before superclasses.
|
||||||
|
|
|
@ -36,6 +36,9 @@ module.exports = BaseTest.extend({
|
||||||
TestCase.assertThrows(function() {
|
TestCase.assertThrows(function() {
|
||||||
new Realm.List();
|
new Realm.List();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TestCase.assertEqual(typeof Realm.List, 'function');
|
||||||
|
TestCase.assertTrue(Realm.List instanceof Function);
|
||||||
},
|
},
|
||||||
|
|
||||||
testListLength: function() {
|
testListLength: function() {
|
||||||
|
|
|
@ -435,10 +435,14 @@ module.exports = BaseTest.extend({
|
||||||
|
|
||||||
testObjectConstructor: function() {
|
testObjectConstructor: function() {
|
||||||
var realm = new Realm({schema: [schemas.TestObject]});
|
var realm = new Realm({schema: [schemas.TestObject]});
|
||||||
|
|
||||||
realm.write(function() {
|
realm.write(function() {
|
||||||
var obj = realm.create('TestObject', {doubleCol: 1});
|
var obj = realm.create('TestObject', {doubleCol: 1});
|
||||||
TestCase.assertTrue(obj instanceof Realm.Object);
|
TestCase.assertTrue(obj instanceof Realm.Object);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TestCase.assertEqual(typeof Realm.Object, 'function');
|
||||||
|
TestCase.assertTrue(Realm.Object instanceof Function);
|
||||||
},
|
},
|
||||||
|
|
||||||
testIsValid: function() {
|
testIsValid: function() {
|
||||||
|
|
|
@ -27,6 +27,9 @@ module.exports = BaseTest.extend({
|
||||||
testRealmConstructor: function() {
|
testRealmConstructor: function() {
|
||||||
var realm = new Realm({schema: []});
|
var realm = new Realm({schema: []});
|
||||||
TestCase.assertTrue(realm instanceof Realm);
|
TestCase.assertTrue(realm instanceof Realm);
|
||||||
|
|
||||||
|
TestCase.assertEqual(typeof Realm, 'function');
|
||||||
|
TestCase.assertTrue(Realm instanceof Function);
|
||||||
},
|
},
|
||||||
|
|
||||||
testRealmConstructorPath: function() {
|
testRealmConstructorPath: function() {
|
||||||
|
|
|
@ -34,6 +34,9 @@ module.exports = BaseTest.extend({
|
||||||
TestCase.assertThrows(function() {
|
TestCase.assertThrows(function() {
|
||||||
new Realm.Results();
|
new Realm.Results();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TestCase.assertEqual(typeof Realm.Results, 'function');
|
||||||
|
TestCase.assertTrue(Realm.Results instanceof Function);
|
||||||
},
|
},
|
||||||
|
|
||||||
testResultsLength: function() {
|
testResultsLength: function() {
|
||||||
|
|
Loading…
Reference in New Issue