diff --git a/README.md b/README.md index 54497a31..158d1240 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ const Realm = require('realm'); const personSchema = { name: 'Person', primaryKey: 'name', - properties: [ - {name: 'name', type: Realm.Types.STRING}, - {name: 'birthday', type: Realm.Types.DATE}, - {name: 'friends', type: Realm.Types.LIST, objectType: 'Person'}, - {name: 'points', type: Realm.Types.INT, default: 0}, - ], + properties: { + name: 'string', + birthday: 'date', + friends: {type: 'list', objectType: 'Person'}, + points: {type: 'int', default: 0}, + }, }; const realm = new Realm({schema: [personSchema]}); @@ -101,27 +101,28 @@ The `realmConfig` passed to the constructor can contain the following: ### ObjectSchema - `name` – string used to refer to this object type -- `properties` - array of property defitions (see below) +- `properties` - object with property definitions (see below) - `primaryKey` – optional - name of `STRING` or `INT` property that should be unique ### Property Types -When definining object `properties` in a `schema`, each should have a unique `name`, and the `type` of each property must be defined as either the name of an object type in the same schema **or** as one of the following: +When defining object `properties` in a `schema`, the value for each property must either be the `type` **OR** an object with a `type` key along with other options detailed below. The `type` of each property must be defined as either the name of an object type in the same schema **or** as one of the following: -- `Realm.Types.BOOL` -- `Realm.Types.INT` -- `Realm.Types.FLOAT` -- `Realm.Types.DOUBLE` -- `Realm.Types.STRING` -- `Realm.Types.DATE` -- `Realm.Types.DATA` -- `Realm.Types.OBJECT` (requires `objectType`, is always `optional`) -- `Realm.Types.LIST` (requires `objectType`, is never `optional`) +- `Realm.Types.BOOL` (`"bool"`) +- `Realm.Types.INT` (`"int"`) +- `Realm.Types.FLOAT` (`"float"`) +- `Realm.Types.DOUBLE` (`"double"`) +- `Realm.Types.STRING` (`"string"`) +- `Realm.Types.DATE` (`"date"`) +- `Realm.Types.DATA` (`"data"`) +- `Realm.Types.LIST` (`"list"` – requires `objectType` and cannot be `optional`) You _may_ specify these property options as well: - `default` – default value when property was not specified on creation - `optional` – boolean indicating if this property may be assigned `null` or `undefined` +**Note:** When the `type` of a property is that of an object type in the same schema, it _always_ will be `optional`. + ### `Realm` Instance Methods #### `create(type, props [, update])` - `type` – string matching object `name` in the `schema` definition diff --git a/examples/ReactExample/components/realm.js b/examples/ReactExample/components/realm.js index 7ab84d75..d3f44bed 100644 --- a/examples/ReactExample/components/realm.js +++ b/examples/ReactExample/components/realm.js @@ -10,17 +10,17 @@ module.exports = new Realm({ schema: [ { name: 'Todo', - properties: [ - {name: 'done', type: Realm.Types.BOOL, default: false}, - {name: 'text', type: Realm.Types.STRING, default: ''}, - ] + properties: { + done: {type: Realm.Types.BOOL, default: false}, + text: Realm.Types.STRING, + }, }, { name: 'TodoList', - properties: [ - {name: 'name', type: Realm.Types.STRING}, - {name: 'items', type: Realm.Types.LIST, objectType: 'Todo'}, - ] + properties: { + name: Realm.Types.STRING, + items: {type: Realm.Types.LIST, objectType: 'Todo', default: []}, + }, }, ], }); diff --git a/examples/ReactExample/components/todo-app.js b/examples/ReactExample/components/todo-app.js index def213bc..d7c97329 100644 --- a/examples/ReactExample/components/todo-app.js +++ b/examples/ReactExample/components/todo-app.js @@ -23,7 +23,7 @@ class TodoApp extends React.Component { let todoLists = realm.objects('TodoList'); if (todoLists.length < 1) { realm.write(() => { - realm.create('TodoList', {name: 'Todo List', items: []}); + realm.create('TodoList', {name: 'Todo List'}); }); } @@ -103,7 +103,7 @@ class TodoApp extends React.Component { } realm.write(() => { - realm.create('TodoList', {name: '', items: []}); + realm.create('TodoList', {name: ''}); }); this._setEditingRow(items.length - 1); diff --git a/lib/constants.js b/lib/constants.js index 063d3eef..841dc5b8 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -17,12 +17,18 @@ let propTypes = {}; }); [ + 'DATA', + 'DATE', + 'DICT', 'FUNCTION', + 'LIST', + 'OBJECT', 'REALM', 'RESULTS', + 'UNDEFINED', ].forEach(function(type) { Object.defineProperty(objectTypes, type, { - value: 'ObjectTypes' + type, + value: type.toLowerCase(), }); }); diff --git a/lib/lists.js b/lib/lists.js index 5232f843..3be63612 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -7,6 +7,8 @@ const constants = require('./constants'); const util = require('./util'); +const {objectTypes} = constants; + module.exports = { create, }; @@ -14,12 +16,12 @@ module.exports = { class List {} // Non-mutating methods: -util.createMethods(List.prototype, constants.propTypes.LIST, [ +util.createMethods(List.prototype, objectTypes.LIST, [ 'snapshot', ]); // Mutating methods: -util.createMethods(List.prototype, constants.propTypes.LIST, [ +util.createMethods(List.prototype, objectTypes.LIST, [ 'pop', 'shift', 'push', diff --git a/lib/objects.js b/lib/objects.js index 190d401f..135005b6 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -24,9 +24,7 @@ function create(realmId, info) { object[keys.id] = info.id; object[keys.type] = info.type; - schema.properties.forEach((prop) => { - let name = prop.name; - + schema.properties.forEach((name) => { Object.defineProperty(object, name, { enumerable: true, get: util.getterForProperty(name), diff --git a/lib/realm.js b/lib/realm.js index d9eeb336..608e0b08 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -14,8 +14,8 @@ const util = require('./util'); const {keys, propTypes, objectTypes} = constants; const listenersKey = Symbol(); -rpc.registerTypeConverter(propTypes.LIST, lists.create); -rpc.registerTypeConverter(propTypes.OBJECT, objects.create); +rpc.registerTypeConverter(objectTypes.LIST, lists.create); +rpc.registerTypeConverter(objectTypes.OBJECT, objects.create); rpc.registerTypeConverter(objectTypes.RESULTS, results.create); class Realm { @@ -125,7 +125,10 @@ Object.defineProperties(Realm, { set: util.setterForProperty('defaultPath'), }, clearTestState: { - value: rpc.clearTestState, + value: function() { + util.clearMutationListeners(); + rpc.clearTestState(); + }, }, }); diff --git a/lib/rpc.js b/lib/rpc.js index e69275dc..00ab8d16 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -9,7 +9,8 @@ const constants = require('./constants'); const DEVICE_HOST = 'localhost:8082'; -const {keys, objectTypes, propTypes} = constants; +const {keys, objectTypes} = constants; +const {id: idKey, realm: realmKey} = keys; const typeConverters = {}; let XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest; @@ -38,8 +39,9 @@ module.exports = { clearTestState, }; -registerTypeConverter(propTypes.DATA, (_, {value}) => base64.decode(value)); -registerTypeConverter(propTypes.DATE, (_, {value}) => new Date(value)); +registerTypeConverter(objectTypes.DATA, (_, {value}) => base64.decode(value)); +registerTypeConverter(objectTypes.DATE, (_, {value}) => new Date(value)); +registerTypeConverter(objectTypes.DICT, deserializeDict); function registerTypeConverter(type, handler) { typeConverters[type] = handler; @@ -94,21 +96,19 @@ function clearTestState() { } function serialize(realmId, value) { + if (typeof value == 'undefined') { + return {type: objectTypes.UNDEFINED}; + } if (typeof value == 'function') { return {type: objectTypes.FUNCTION}; } - - if (typeof value === 'undefined') { - return {type: 'undefined'}; - } - if (!value || typeof value != 'object') { return {value: value}; } - let id = value[keys.id]; + let id = value[idKey]; if (id) { - if (value[keys.realm] != realmId) { + if (value[realmKey] != realmId) { throw new Error('Unable to serialize value from another Realm'); } @@ -116,7 +116,7 @@ function serialize(realmId, value) { } if (value instanceof Date) { - return {type: propTypes.DATE, value: value.getTime()}; + return {type: objectTypes.DATE, value: value.getTime()}; } if (Array.isArray(value)) { @@ -125,14 +125,12 @@ function serialize(realmId, value) { } if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) { - return {type: propTypes.DATA, value: base64.encode(value)}; + return {type: objectTypes.DATA, value: base64.encode(value)}; } - let object = {}; - for (let key in value) { - object[key] = serialize(realmId, value[key]); - } - return {value: object}; + let keys = Object.keys(value); + let values = keys.map((key) => serialize(realmId, value[key])); + return {type: objectTypes.DICT, keys, values}; } function deserialize(realmId, info) { @@ -150,6 +148,17 @@ function deserialize(realmId, info) { return value; } +function deserializeDict(realmId, info) { + let {keys, values} = info; + let object = {}; + + for (let i = 0, len = keys.length; i < len; i++) { + object[keys[i]] = values[i]; + } + + return object; +} + function sendRequest(command, data) { data = Object.assign({}, data, sessionId ? {sessionId} : null); diff --git a/lib/util.js b/lib/util.js index 6ea4f95c..5733e54d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -8,9 +8,10 @@ const constants = require('./constants'); const rpc = require('./rpc'); const {keys} = constants; -const mutationListeners = {}; +let mutationListeners = {}; module.exports = { + clearMutationListeners, fireMutationListeners, createList, createMethods, @@ -31,6 +32,10 @@ function removeMutationListener(realmId, callback) { } } +function clearMutationListeners() { + mutationListeners = {}; +} + function fireMutationListeners(realmId) { let listeners = mutationListeners[realmId]; if (listeners) { diff --git a/src/js_init.cpp b/src/js_init.cpp index 09572007..3afa1037 100644 --- a/src/js_init.cpp +++ b/src/js_init.cpp @@ -33,7 +33,7 @@ JSClassRef RJSRealmTypeClass() { { "DATE", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "DATA", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "OBJECT", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "LIST", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "LIST", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { NULL, NULL, NULL, 0 } }; realmTypesDefinition.staticValues = types; diff --git a/src/js_object.cpp b/src/js_object.cpp index d527df7f..19e1ce7b 100644 --- a/src/js_object.cpp +++ b/src/js_object.cpp @@ -69,13 +69,16 @@ namespace realm { template<> bool RJSAccessor::dict_has_value_for_key(JSContextRef ctx, JSValueRef dict, const std::string &prop_name) { JSObjectRef object = RJSValidatedValueToObject(ctx, dict); - JSStringRef propStr =JSStringCreateWithUTF8CString(prop_name.c_str()); - return JSObjectHasProperty(ctx, object, propStr); + JSStringRef propStr = RJSStringForString(prop_name); + bool ret = JSObjectHasProperty(ctx, object, propStr); + + JSStringRelease(propStr); + return ret; } template<> JSValueRef RJSAccessor::dict_value_for_key(JSContextRef ctx, JSValueRef dict, const std::string &prop_name) { JSObjectRef object = RJSValidatedValueToObject(ctx, dict); - JSStringRef propStr =JSStringCreateWithUTF8CString(prop_name.c_str()); + JSStringRef propStr = RJSStringForString(prop_name); JSValueRef ex = NULL; JSValueRef ret = JSObjectGetProperty(ctx, object, propStr, &ex); if (ex) { diff --git a/src/js_schema.cpp b/src/js_schema.cpp index 6392c86d..20e02f32 100644 --- a/src/js_schema.cpp +++ b/src/js_schema.cpp @@ -31,25 +31,34 @@ JSObjectRef RJSSchemaCreate(JSContextRef ctx, Schema &schema) { return RJSWrapObject(ctx, RJSSchemaClass(), wrapper); } -static inline Property RJSParseProperty(JSContextRef ctx, JSObjectRef propertyObject) { - static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); +static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAttributes, std::string propertyName, ObjectDefaults &objectDefaults) { + static JSStringRef defaultString = JSStringCreateWithUTF8CString("default"); static JSStringRef typeString = JSStringCreateWithUTF8CString("type"); static JSStringRef objectTypeString = JSStringCreateWithUTF8CString("objectType"); static JSStringRef optionalString = JSStringCreateWithUTF8CString("optional"); Property prop; - prop.name = RJSValidatedStringProperty(ctx, propertyObject, nameString); + prop.name = propertyName; - prop.is_nullable = false; - JSValueRef optionalValue = JSObjectGetProperty(ctx, propertyObject, optionalString, NULL); - if (!JSValueIsUndefined(ctx, optionalValue)) { - if (!JSValueIsBoolean(ctx, optionalValue)) { - throw std::runtime_error("'optional' designation expected to be of type boolean"); + JSObjectRef propertyObject = NULL; + std::string type; + + if (JSValueIsObject(ctx, propertyAttributes)) { + propertyObject = RJSValidatedValueToObject(ctx, propertyAttributes); + type = RJSValidatedStringProperty(ctx, propertyObject, typeString); + + JSValueRef optionalValue = JSObjectGetProperty(ctx, propertyObject, optionalString, NULL); + if (!JSValueIsUndefined(ctx, optionalValue)) { + if (!JSValueIsBoolean(ctx, optionalValue)) { + throw std::runtime_error("'optional' designation expected to be of type boolean"); + } + prop.is_nullable = JSValueToBoolean(ctx, optionalValue); } - prop.is_nullable = JSValueToBoolean(ctx, optionalValue); + } + else { + type = RJSValidatedStringForValue(ctx, propertyAttributes); } - std::string type = RJSValidatedStringProperty(ctx, propertyObject, typeString); if (type == "bool") { prop.type = PropertyTypeBool; } @@ -72,22 +81,49 @@ static inline Property RJSParseProperty(JSContextRef ctx, JSObjectRef propertyOb prop.type = PropertyTypeData; } else if (type == "list") { + if (!propertyObject) { + throw std::runtime_error("List property must specify 'objectType'"); + } prop.type = PropertyTypeArray; prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); } else { prop.type = PropertyTypeObject; prop.is_nullable = true; - prop.object_type = type == "object" ? RJSValidatedStringProperty(ctx, propertyObject, objectTypeString) : type; + + // The type could either be 'object' or the name of another object type in the same schema. + if (type == "object") { + if (!propertyObject) { + throw std::runtime_error("Object property must specify 'objectType'"); + } + prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); + } + else { + prop.object_type = type; + } } + + if (propertyObject) { + JSValueRef defaultValue = RJSValidatedPropertyValue(ctx, propertyObject, defaultString); + if (!JSValueIsUndefined(ctx, defaultValue)) { + JSValueProtect(ctx, defaultValue); + objectDefaults.emplace(prop.name, defaultValue); + } + } + return prop; } static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef objectSchemaObject, std::map &defaults, std::map &prototypes) { - static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); + static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); + static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey"); static JSStringRef prototypeString = JSStringCreateWithUTF8CString("prototype"); + static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); + static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); + JSObjectRef prototypeObject = NULL; JSValueRef prototypeValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, prototypeString); + if (!JSValueIsUndefined(ctx, prototypeValue)) { prototypeObject = RJSValidatedValueToObject(ctx, prototypeValue); objectSchemaObject = RJSValidatedObjectProperty(ctx, prototypeObject, schemaString, "Realm object prototype must have a 'schema' property."); @@ -99,29 +135,30 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob } } - static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); - JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema object must have a 'properties' array."); - - ObjectSchema objectSchema; ObjectDefaults objectDefaults; - static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); + ObjectSchema objectSchema; objectSchema.name = RJSValidatedStringProperty(ctx, objectSchemaObject, nameString); - size_t numProperties = RJSValidatedListLength(ctx, propertiesObject); - for (unsigned int p = 0; p < numProperties; p++) { - JSObjectRef property = RJSValidatedObjectAtIndex(ctx, propertiesObject, p); - objectSchema.properties.emplace_back(RJSParseProperty(ctx, property)); - - static JSStringRef defaultString = JSStringCreateWithUTF8CString("default"); - JSValueRef defaultValue = JSObjectGetProperty(ctx, property, defaultString, NULL); - if (!JSValueIsUndefined(ctx, defaultValue)) { - JSValueProtect(ctx, defaultValue); - objectDefaults.emplace(objectSchema.properties.back().name, defaultValue); + JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object."); + if (RJSIsValueArray(ctx, propertiesObject)) { + size_t propertyCount = RJSValidatedListLength(ctx, propertiesObject); + for (size_t i = 0; i < propertyCount; i++) { + JSObjectRef propertyObject = RJSValidatedObjectAtIndex(ctx, propertiesObject, (unsigned int)i); + std::string propertyName = RJSValidatedStringProperty(ctx, propertyObject, nameString); + objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyObject, propertyName, objectDefaults)); } } - defaults.emplace(objectSchema.name, std::move(objectDefaults)); + else { + JSPropertyNameArrayRef propertyNames = JSObjectCopyPropertyNames(ctx, propertiesObject); + size_t propertyCount = JSPropertyNameArrayGetCount(propertyNames); + for (size_t i = 0; i < propertyCount; i++) { + JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); + JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, propertiesObject, propertyName); + objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyValue, RJSStringForJSString(propertyName), objectDefaults)); + } + JSPropertyNameArrayRelease(propertyNames); + } - static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey"); JSValueRef primaryValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, primaryString); if (!JSValueIsUndefined(ctx, primaryValue)) { objectSchema.primary_key = RJSValidatedStringForValue(ctx, primaryValue); @@ -132,12 +169,14 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob property->is_primary = true; } - // store prototype + // Store prototype so that objects of this type will have their prototype set to this prototype object. if (prototypeObject) { JSValueProtect(ctx, prototypeObject); prototypes[objectSchema.name] = std::move(prototypeObject); } + defaults.emplace(objectSchema.name, std::move(objectDefaults)); + return objectSchema; } diff --git a/src/js_util.cpp b/src/js_util.cpp index fcd804c3..5ed9b494 100644 --- a/src/js_util.cpp +++ b/src/js_util.cpp @@ -24,21 +24,6 @@ JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message) { return JSObjectMakeError(ctx, 1, &value, NULL); } -std::string RJSTypeGet(PropertyType propertyType) { - switch (propertyType) { - case PropertyTypeBool: return "bool"; - case PropertyTypeInt: return "int"; - case PropertyTypeFloat: return "float"; - case PropertyTypeDouble:return "double"; - case PropertyTypeString:return "string"; - case PropertyTypeDate: return "date"; - case PropertyTypeData: return "data"; - case PropertyTypeObject:return "object"; - case PropertyTypeArray: return "list"; - default: return nullptr; - } -} - std::string RJSStringForJSString(JSStringRef jsString) { std::string str; size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString); @@ -48,10 +33,10 @@ std::string RJSStringForJSString(JSStringRef jsString) { } std::string RJSStringForValue(JSContextRef ctx, JSValueRef value) { - JSValueRef *exception = nullptr; - JSStringRef jsString = JSValueToStringCopy(ctx, value, exception); + JSValueRef exception = nullptr; + JSStringRef jsString = JSValueToStringCopy(ctx, value, &exception); if (!jsString) { - throw RJSException(ctx, *exception); + throw RJSException(ctx, exception); } std::string string = RJSStringForJSString(jsString); @@ -98,4 +83,3 @@ bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) { static JSStringRef dateString = JSStringCreateWithUTF8CString("Date"); return RJSIsValueObjectOfType(ctx, value, dateString); } - diff --git a/src/js_util.hpp b/src/js_util.hpp index 8da23e65..f74f9991 100644 --- a/src/js_util.hpp +++ b/src/js_util.hpp @@ -51,8 +51,6 @@ JSClassRef RJSCreateWrapperClass(const char * name, JSObjectGetPropertyCallback return JSClassCreate(&classDefinition); } -std::string RJSTypeGet(realm::PropertyType propertyType); - std::string RJSStringForJSString(JSStringRef jsString); std::string RJSStringForValue(JSContextRef ctx, JSValueRef value); std::string RJSValidatedStringForValue(JSContextRef ctx, JSValueRef value, const char * name = nullptr); @@ -148,7 +146,7 @@ static inline JSValueRef RJSValidatedPropertyValue(JSContextRef ctx, JSObjectRef static inline JSObjectRef RJSValidatedObjectProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property, const char *err = NULL) { JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, object, property); if (JSValueIsUndefined(ctx, propertyValue)) { - throw std::runtime_error(err ?: "Object property is undefined"); + throw std::runtime_error(err ?: "Object property '" + RJSStringForJSString(property) + "' is undefined"); } return RJSValidatedValueToObject(ctx, propertyValue, err); } @@ -168,7 +166,7 @@ static inline std::string RJSValidatedStringProperty(JSContextRef ctx, JSObjectR if (exception) { throw RJSException(ctx, exception); } - return RJSValidatedStringForValue(ctx, propertyValue); + return RJSValidatedStringForValue(ctx, propertyValue, RJSStringForJSString(property).c_str()); } static inline size_t RJSValidatedListLength(JSContextRef ctx, JSObjectRef object) { diff --git a/src/rpc.cpp b/src/rpc.cpp index 4904e967..08490b0f 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -23,8 +23,14 @@ using RJSAccessor = realm::NativeAccessor; using namespace realm_js; -static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION"; -static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; +static const char * const RealmObjectTypesData = "data"; +static const char * const RealmObjectTypesDate = "date"; +static const char * const RealmObjectTypesDictionary = "dict"; +static const char * const RealmObjectTypesFunction = "function"; +static const char * const RealmObjectTypesList = "list"; +static const char * const RealmObjectTypesObject = "object"; +static const char * const RealmObjectTypesResults = "results"; +static const char * const RealmObjectTypesUndefined = "undefined"; RPCServer::RPCServer() { m_context = JSGlobalContextCreate(NULL); @@ -219,7 +225,7 @@ json RPCServer::serialize_json_value(JSValueRef value) { if (JSValueIsObjectOfClass(m_context, value, RJSObjectClass())) { realm::Object *object = RJSGetInternal(js_object); return { - {"type", RJSTypeGet(realm::PropertyTypeObject)}, + {"type", RealmObjectTypesObject}, {"id", store_object(js_object)}, {"schema", serialize_object_schema(object->get_object_schema())} }; @@ -227,7 +233,7 @@ json RPCServer::serialize_json_value(JSValueRef value) { else if (JSValueIsObjectOfClass(m_context, value, RJSListClass())) { realm::List *list = RJSGetInternal(js_object); return { - {"type", RJSTypeGet(realm::PropertyTypeArray)}, + {"type", RealmObjectTypesList}, {"id", store_object(js_object)}, {"size", list->size()}, {"schema", serialize_object_schema(list->get_object_schema())} @@ -253,26 +259,47 @@ json RPCServer::serialize_json_value(JSValueRef value) { else if (RJSIsValueArrayBuffer(m_context, value)) { std::string data = RJSAccessor::to_binary(m_context, value); return { - {"type", RJSTypeGet(realm::PropertyTypeData)}, + {"type", RealmObjectTypesData}, {"value", base64_encode((unsigned char *)data.data(), data.size())}, }; } else if (RJSIsValueDate(m_context, value)) { return { - {"type", RJSTypeGet(realm::PropertyTypeDate)}, + {"type", RealmObjectTypesDate}, {"value", RJSValidatedValueToNumber(m_context, value)}, }; } + else { + // Serialize this JS object as a plain object since it doesn't match any known types above. + JSPropertyNameArrayRef js_keys = JSObjectCopyPropertyNames(m_context, js_object); + size_t count = JSPropertyNameArrayGetCount(js_keys); + std::vector keys; + std::vector values; + + for (size_t i = 0; i < count; i++) { + JSStringRef js_key = JSPropertyNameArrayGetNameAtIndex(js_keys, i); + JSValueRef js_value = RJSValidatedPropertyValue(m_context, js_object, js_key); + + keys.push_back(RJSStringForJSString(js_key)); + values.push_back(serialize_json_value(js_value)); + } + + JSPropertyNameArrayRelease(js_keys); + + return { + {"type", RealmObjectTypesDictionary}, + {"keys", keys}, + {"values", values}, + }; + } assert(0); } json RPCServer::serialize_object_schema(const realm::ObjectSchema &object_schema) { - json properties = json::array(); + std::vector properties; + for (realm::Property prop : object_schema.properties) { - properties.push_back({ - {"name", prop.name}, - {"type", RJSTypeGet(prop.type)}, - }); + properties.push_back(prop.name); } return { @@ -300,14 +327,30 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) return js_function; } - else if (type_string == RJSTypeGet(realm::PropertyTypeData)) { + else if (type_string == RealmObjectTypesDictionary) { + JSObjectRef js_object = JSObjectMake(m_context, NULL, NULL); + json keys = dict["keys"]; + json values = dict["values"]; + size_t count = keys.size(); + + for (size_t i = 0; i < count; i++) { + JSStringRef js_key = RJSStringForString(keys.at(i)); + JSValueRef js_value = deserialize_json_value(values.at(i)); + + JSObjectSetProperty(m_context, js_object, js_key, js_value, 0, NULL); + JSStringRelease(js_key); + } + + return js_object; + } + else if (type_string == RealmObjectTypesData) { std::string bytes; if (!base64_decode(value.get(), &bytes)) { throw std::runtime_error("Failed to decode base64 encoded data"); } return RJSAccessor::from_binary(m_context, realm::BinaryData(bytes)); } - else if (type_string == RJSTypeGet(realm::PropertyTypeDate)) { + else if (type_string == RealmObjectTypesDate) { JSValueRef exception = NULL; JSValueRef time = JSValueMakeNumber(m_context, value.get()); JSObjectRef date = JSObjectMakeDate(m_context, 1, &time, &exception); @@ -317,7 +360,7 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) } return date; } - else if (type_string == "undefined") { + else if (type_string == RealmObjectTypesUndefined) { return JSValueMakeUndefined(m_context); } assert(0); @@ -345,19 +388,5 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) return JSObjectMakeArray(m_context, count, js_values, NULL); } - else if (value.is_object()) { - JSObjectRef js_object = JSObjectMake(m_context, NULL, NULL); - - for (json::iterator it = value.begin(); it != value.end(); ++it) { - JSValueRef js_value = deserialize_json_value(it.value()); - JSStringRef js_key = JSStringCreateWithUTF8CString(it.key().c_str()); - - JSObjectSetProperty(m_context, js_object, js_key, js_value, 0, NULL); - JSStringRelease(js_key); - } - - return js_object; - } - assert(0); } diff --git a/tests/lib/list-tests.js b/tests/lib/list-tests.js index ddd629e9..0bea2ce0 100644 --- a/tests/lib/list-tests.js +++ b/tests/lib/list-tests.js @@ -12,20 +12,30 @@ var schemas = require('./schemas'); module.exports = BaseTest.extend({ testArrayLength: function() { var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); + var array; + realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3]]]); - TestCase.assertEqual(obj.arrayCol.length, 1); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}], + }); + + array = obj.arrayCol; + TestCase.assertEqual(array.length, 1); obj.arrayCol = []; - TestCase.assertEqual(obj.arrayCol.length, 0); + TestCase.assertEqual(array.length, 0); - obj.arrayCol = [[1], [2]]; - TestCase.assertEqual(obj.arrayCol.length, 2); + obj.arrayCol = [{doubleCol: 1}, {doubleCol: 2}]; + TestCase.assertEqual(array.length, 2); TestCase.assertThrows(function() { - obj.arrayCol.length = 0; + array.length = 0; }, 'cannot set length property on lists'); }); + + TestCase.assertEqual(array.length, 2); }, testArraySubscriptGetters: function() { @@ -33,7 +43,12 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; }); @@ -48,26 +63,36 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - array = obj.arrayCol; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); - array[0] = [5]; - array[1] = [6]; + array = obj.arrayCol; + array[0] = {doubleCol: 5}; + array[1] = {doubleCol: 6}; TestCase.assertEqual(array[0].doubleCol, 5); TestCase.assertEqual(array[1].doubleCol, 6); + array[0] = obj.objectCol; + array[1] = obj.objectCol1; + + TestCase.assertEqual(array[0].doubleCol, 1); + TestCase.assertEqual(array[1].doubleCol, 2); + TestCase.assertThrows(function() { - array[2] = [1]; + array[2] = {doubleCol: 1}; }, 'cannot set list item beyond its bounds'); TestCase.assertThrows(function() { - array[-1] = [1]; + array[-1] = {doubleCol: 1}; }, 'cannot set list item with negative index'); }); TestCase.assertThrows(function() { - array[0] = [3]; + array[0] = {doubleCol: 1}; }, 'cannot set list item outside write transaction'); }, @@ -76,7 +101,12 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; }); @@ -88,21 +118,26 @@ module.exports = BaseTest.extend({ var obj; realm.write(function() { - obj = realm.create('LinkTypesObject', [[1], [2], []]); + obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [], + }); }); - for (var index in obj.arrayCol) { + var index; + for (index in obj.arrayCol) { TestCase.assertTrue(false, "No objects should have been enumerated: " + index); } realm.write(function() { - obj.arrayCol = [[0], [1]]; + obj.arrayCol = [{doubleCol: 0}, {doubleCol: 1}]; TestCase.assertEqual(obj.arrayCol.length, 2); }); var count = 0; var keys = Object.keys(obj.arrayCol); - for (var index in obj.arrayCol) { + for (index in obj.arrayCol) { TestCase.assertEqual(count++, +index); TestCase.assertEqual(keys[index], index); } @@ -116,11 +151,16 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3]]]); - TestCase.assertEqual(obj.arrayCol.length, 1); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}], + }); array = obj.arrayCol; - TestCase.assertEqual(array.push([4]), 2); + TestCase.assertEqual(array.length, 1); + + TestCase.assertEqual(array.push({doubleCol: 4}), 2); TestCase.assertEqual(array.length, 2); TestCase.assertEqual(array[1].doubleCol, 4); @@ -145,9 +185,13 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - array = obj.arrayCol; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; TestCase.assertEqual(array.pop().doubleCol, 4); TestCase.assertEqual(array.pop().doubleCol, 3); TestCase.assertEqual(array.length, 0); @@ -169,11 +213,16 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3]]]); - TestCase.assertEqual(obj.arrayCol.length, 1); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}], + }); array = obj.arrayCol; - TestCase.assertEqual(array.unshift([5]), 2); + TestCase.assertEqual(array.length, 1); + + TestCase.assertEqual(array.unshift({doubleCol: 5}), 2); TestCase.assertEqual(array.length, 2); TestCase.assertEqual(array[0].doubleCol, 5); @@ -185,7 +234,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(array.length, 4); TestCase.assertThrows(function() { - array.unshift([1]); + array.unshift({doubleCol: 1}); }, 'can only unshift in a write transaction'); }, @@ -194,9 +243,13 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - array = obj.arrayCol; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; TestCase.assertEqual(array.shift().doubleCol, 3); TestCase.assertEqual(array.shift().doubleCol, 4); TestCase.assertEqual(array.length, 0); @@ -218,10 +271,14 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - var removed; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); array = obj.arrayCol; + var removed; removed = array.splice(0, 0, obj.objectCol, obj.objectCol1); TestCase.assertEqual(removed.length, 0); @@ -229,7 +286,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(array[0].doubleCol, 1); TestCase.assertEqual(array[1].doubleCol, 2); - removed = array.splice(2, 2, [5], [6]); + removed = array.splice(2, 2, {doubleCol: 5}, {doubleCol: 6}); TestCase.assertEqual(removed.length, 2); TestCase.assertEqual(removed[0].doubleCol, 3); TestCase.assertEqual(removed[1].doubleCol, 4); @@ -270,7 +327,7 @@ module.exports = BaseTest.extend({ }); TestCase.assertThrows(function() { - array.splice(0, 0, [1]); + array.splice(0, 0, {doubleCol: 1}); }, 'can only splice in a write transaction'); }, @@ -280,7 +337,12 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - object = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + object = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = object.arrayCol; }); @@ -313,21 +375,27 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; }); + TestCase.assertEqual(array.length, 2); TestCase.assertEqual(objects.length, 4); try { realm.write(function() { - array.push([5]); + array.push({doubleCol: 5}); TestCase.assertEqual(objects.length, 5); - array.unshift([2]); + array.unshift({doubleCol: 2}); TestCase.assertEqual(objects.length, 6); - array.splice(0, 0, [1]); + array.splice(0, 0, {doubleCol: 1}); TestCase.assertEqual(objects.length, 7); array.push(objects[0], objects[1]); diff --git a/tests/lib/object-tests.js b/tests/lib/object-tests.js index 53861f8f..c2cf03c2 100644 --- a/tests/lib/object-tests.js +++ b/tests/lib/object-tests.js @@ -16,67 +16,105 @@ var RANDOM_DATA = new Uint8Array([ module.exports = BaseTest.extend({ testBasicTypesPropertyGetters: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), RANDOM_DATA]; var realm = new Realm({schema: [schemas.BasicTypes]}); var object; + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }; + realm.write(function() { object = realm.create('BasicTypesObject', basicTypesValues); }); - for (var i = 0; i < schemas.BasicTypes.properties.length; i++) { - var prop = schemas.BasicTypes.properties[i]; - if (prop.type == Realm.Types.FLOAT) { - TestCase.assertEqualWithTolerance(object[prop.name], basicTypesValues[i], 0.000001); + for (var name in schemas.BasicTypes.properties) { + var prop = schemas.BasicTypes.properties[name]; + var type = typeof prop == 'object' ? prop.type : prop; + + if (type == Realm.Types.FLOAT || type == Realm.Types.DOUBLE) { + TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001); } - else if (prop.type == Realm.Types.DATA) { - TestCase.assertArraysEqual(new Uint8Array(object[prop.name]), RANDOM_DATA); + else if (type == Realm.Types.DATA) { + TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA); } - else if (prop.type == Realm.Types.DATE) { - TestCase.assertEqual(object[prop.name].getTime(), basicTypesValues[i].getTime()); + else if (type == Realm.Types.DATE) { + TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime()); } else { - TestCase.assertEqual(object[prop.name], basicTypesValues[i]); + TestCase.assertEqual(object[name], basicTypesValues[name]); } } TestCase.assertEqual(object.nonexistent, undefined); }, testNullableBasicTypesPropertyGetters: function() { - var nullValues = [null, null, null, null, null, null, null]; - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), RANDOM_DATA]; - var realm = new Realm({schema: [schemas.NullableBasicTypes]}); - var nullObject = null; - var object = null; + var object, nullObject; + + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }; + realm.write(function() { - nullObject = realm.create('NullableBasicTypesObject', nullValues); object = realm.create('NullableBasicTypesObject', basicTypesValues); + + nullObject = realm.create('NullableBasicTypesObject', { + boolCol: null, + intCol: null, + floatCol: null, + doubleCol: null, + stringCol: null, + dateCol: null, + dataCol: null, + }); }); - for (var i = 0; i < schemas.BasicTypes.properties.length; i++) { - var prop = schemas.BasicTypes.properties[i]; - TestCase.assertEqual(nullObject[prop.name], null); + for (var name in schemas.BasicTypes.properties) { + var prop = schemas.BasicTypes.properties[name]; + var type = typeof prop == 'object' ? prop.type : prop; - if (prop.type == Realm.Types.FLOAT) { - TestCase.assertEqualWithTolerance(object[prop.name], basicTypesValues[i], 0.000001); + TestCase.assertEqual(nullObject[name], null); + + if (type == Realm.Types.FLOAT || type == Realm.Types.DOUBLE) { + TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001); } - else if (prop.type == Realm.Types.DATA) { - TestCase.assertArraysEqual(new Uint8Array(object[prop.name]), RANDOM_DATA); + else if (type == Realm.Types.DATA) { + TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA); } - else if (prop.type == Realm.Types.DATE) { - TestCase.assertEqual(object[prop.name].getTime(), basicTypesValues[i].getTime()); + else if (type == Realm.Types.DATE) { + TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime()); } else { - TestCase.assertEqual(object[prop.name], basicTypesValues[i]); + TestCase.assertEqual(object[name], basicTypesValues[name]); } } }, testBasicTypesPropertySetters: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), new ArrayBuffer()]; var realm = new Realm({schema: [schemas.BasicTypes]}); - var obj = null; + var obj; + + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: new ArrayBuffer(), + }; realm.write(function() { obj = realm.create('BasicTypesObject', basicTypesValues); @@ -92,7 +130,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.boolCol, false, 'wrong bool value'); TestCase.assertEqual(obj.intCol, 2, 'wrong int value'); TestCase.assertEqualWithTolerance(obj.floatCol, 2.2, 0.000001, 'wrong float value'); - TestCase.assertEqual(obj.doubleCol, 2.22, 'wrong double value'); + TestCase.assertEqualWithTolerance(obj.doubleCol, 2.22, 0.000001, 'wrong double value'); TestCase.assertEqual(obj.stringCol, 'STRING', 'wrong string value'); TestCase.assertEqual(obj.dateCol.getTime(), 2, 'wrong date value'); TestCase.assertArraysEqual(new Uint8Array(obj.dataCol), RANDOM_DATA, 'wrong data value'); @@ -156,24 +194,32 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.boolCol, false, 'bool value changed outside transaction'); }, testNullableBasicTypesPropertySetters: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), RANDOM_DATA]; var realm = new Realm({schema: [schemas.NullableBasicTypes]}); var obj, obj1; + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }; + realm.write(function() { obj = realm.create('NullableBasicTypesObject', basicTypesValues); obj1 = realm.create('NullableBasicTypesObject', basicTypesValues); - for (var index in schemas.NullableBasicTypes.properties) { - var prop = schemas.NullableBasicTypes.properties[index]; - obj[prop.name] = null; - obj1[prop.name] = undefined; + + for (var name in schemas.NullableBasicTypes.properties) { + obj[name] = null; + obj1[name] = undefined; } }); - for (var index in schemas.NullableBasicTypes.properties) { - var prop = schemas.NullableBasicTypes.properties[index]; - TestCase.assertEqual(obj[prop.name], null); - TestCase.assertEqual(obj1[prop.name], null); + for (var name in schemas.NullableBasicTypes.properties) { + TestCase.assertEqual(obj[name], null); + TestCase.assertEqual(obj1[name], null); } realm.write(function() { @@ -192,8 +238,13 @@ module.exports = BaseTest.extend({ testLinkTypesPropertyGetters: function() { var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); var obj = null; + realm.write(function() { - obj = realm.create('LinkTypesObject', [[1], null, [[3]]]); + obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: null, + arrayCol: [{doubleCol: 3}], + }); }); var objVal = obj.objectCol; @@ -212,11 +263,16 @@ module.exports = BaseTest.extend({ testLinkTypesPropertySetters: function() { var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); var objects = realm.objects('TestObject'); - var obj = null; + var obj; realm.write(function() { - obj = realm.create('LinkTypesObject', [[1], null, [[3]]]); + obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: null, + arrayCol: [{doubleCol: 3}], + }); }); + TestCase.assertEqual(objects.length, 2); TestCase.assertThrows(function() { @@ -240,15 +296,20 @@ module.exports = BaseTest.extend({ // set object as JSON realm.write(function() { - obj.objectCol = { doubleCol: 1 }; + obj.objectCol = {doubleCol: 1}; }); TestCase.assertEqual(obj.objectCol.doubleCol, 1); TestCase.assertEqual(objects.length, 3); // set array property realm.write(function() { - obj.arrayCol = [obj.arrayCol[0], obj.objectCol, realm.create('TestObject', [2])]; + obj.arrayCol = [ + obj.arrayCol[0], + obj.objectCol, + realm.create('TestObject', {doubleCol: 2}), + ]; }); + TestCase.assertEqual(objects.length, 4); TestCase.assertEqual(obj.arrayCol.length, 3); TestCase.assertEqual(obj.arrayCol[0].doubleCol, 3); @@ -256,15 +317,22 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.arrayCol[2].doubleCol, 2); }, testEnumerablePropertyNames: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), new ArrayBuffer()]; var realm = new Realm({schema: [schemas.BasicTypes]}); var object; realm.write(function() { - object = realm.create('BasicTypesObject', basicTypesValues); + object = realm.create('BasicTypesObject', { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }); }); - var propNames = schemas.BasicTypes.properties.map(function(prop) { return prop.name; }); + var propNames = Object.keys(schemas.BasicTypes.properties); TestCase.assertArraysEqual(Object.keys(object), propNames, 'Object.keys'); for (var key in object) { diff --git a/tests/lib/realm-tests.js b/tests/lib/realm-tests.js index a404c9e9..42649448 100644 --- a/tests/lib/realm-tests.js +++ b/tests/lib/realm-tests.js @@ -12,8 +12,12 @@ var util = require('./util'); module.exports = BaseTest.extend({ testRealmConstructorPath: function() { - TestCase.assertThrows(function() { new Realm('/invalidpath'); }); - TestCase.assertThrows(function() { new Realm(util.realmPathForFile('test1.realm'), 'invalidArgument'); }); + TestCase.assertThrows(function() { + new Realm('/invalidpath'); + }, 'Realm cannot be created with an invalid path'); + TestCase.assertThrows(function() { + new Realm(util.realmPathForFile('test1.realm'), 'invalidArgument'); + }, 'Realm constructor can only have 0 or 1 argument(s)'); var defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); @@ -57,11 +61,33 @@ module.exports = BaseTest.extend({ realm = new Realm({path: testPath, schema: [schemas.TestObject], schemaVersion: 2}); realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertEqual(realm.objects('TestObject')[0].doubleCol, 1) }, + testRealmConstructorSchemaValidation: function() { + TestCase.assertThrows(function() { + new Realm({schema: schemas.AllTypes}); + }, 'The schema should be an array'); + + TestCase.assertThrows(function() { + new Realm({schema: ['SomeType']}); + }, 'The schema should be an array of objects'); + + TestCase.assertThrows(function() { + new Realm({schema: [{}]}); + }, 'The schema should be an array of ObjectSchema objects'); + + TestCase.assertThrows(function() { + new Realm({schema: [{name: 'SomeObject'}]}); + }, 'The schema should be an array of ObjectSchema objects'); + + TestCase.assertThrows(function() { + new Realm({schema: [{properties: {intCol: Realm.Types.INT}}]}); + }, 'The schema should be an array of ObjectSchema objects'); + }, + testDefaultPath: function() { var defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); @@ -77,12 +103,12 @@ module.exports = BaseTest.extend({ var realm = new Realm({schema: [schemas.IntPrimary, schemas.AllTypes, schemas.TestObject]}); TestCase.assertThrows(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); }, 'can only create inside a write transaction'); realm.write(function() { - realm.create('TestObject', [1]); - realm.create('TestObject', {'doubleCol': 2}); + realm.create('TestObject', {doubleCol: 1}); + realm.create('TestObject', {doubleCol: 2}); }); var objects = realm.objects('TestObject'); @@ -92,16 +118,31 @@ module.exports = BaseTest.extend({ // test int primary object realm.write(function() { - var obj0 = realm.create('IntPrimaryObject', [0, 'val0']); + var obj0 = realm.create('IntPrimaryObject', { + primaryCol: 0, + valueCol: 'val0', + }); TestCase.assertThrows(function() { - realm.create('IntPrimaryObject', [0, 'val0']); - }); - realm.create('IntPrimaryObject', [1, 'val1'], true); + realm.create('IntPrimaryObject', { + primaryCol: 0, + valueCol: 'val0', + }); + }, 'cannot create object with conflicting primary key'); + + realm.create('IntPrimaryObject', { + primaryCol: 1, + valueCol: 'val1', + }, true); + var objects = realm.objects('IntPrimaryObject'); TestCase.assertEqual(objects.length, 2); - realm.create('IntPrimaryObject', [0, 'newVal0'], true); + realm.create('IntPrimaryObject', { + primaryCol: 0, + valueCol: 'newVal0', + }, true); + TestCase.assertEqual(obj0.valueCol, 'newVal0'); TestCase.assertEqual(objects.length, 2); @@ -111,47 +152,89 @@ module.exports = BaseTest.extend({ // test upsert with all type and string primary object realm.write(function() { - var values = ['0', true, 1, 1.1, 1.11, '1', new Date(1), new ArrayBuffer(1), [1], []]; + var values = { + primaryCol: '0', + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: '1', + dateCol: new Date(1), + dataCol: new ArrayBuffer(1), + objectCol: {doubleCol: 1}, + arrayCol: [], + }; + var obj0 = realm.create('AllTypesObject', values); TestCase.assertThrows(function() { realm.create('AllTypesObject', values); - }); - var obj1 = realm.create('AllTypesObject', ['1', false, 2, 2.2, 2.11, '2', new Date(2), new ArrayBuffer(2), [0], [[2]]], true); + }, 'cannot create object with conflicting primary key'); + + var obj1 = realm.create('AllTypesObject', { + primaryCol: '1', + boolCol: false, + intCol: 2, + floatCol: 2.2, + doubleCol: 2.22, + stringCol: '2', + dateCol: new Date(2), + dataCol: new ArrayBuffer(2), + objectCol: {doubleCol: 0}, + arrayCol: [{doubleCol: 2}], + }, true); + var objects = realm.objects('AllTypesObject'); TestCase.assertEqual(objects.length, 2); - realm.create('AllTypesObject', ['0', false, 2, 2.2, 2.22, '2', new Date(2), new ArrayBuffer(2), null, [[2]]], true); + realm.create('AllTypesObject', { + primaryCol: '0', + boolCol: false, + intCol: 2, + floatCol: 2.2, + doubleCol: 2.22, + stringCol: '2', + dateCol: new Date(2), + dataCol: new ArrayBuffer(2), + objectCol: null, + arrayCol: [{doubleCol: 2}], + }, true); + TestCase.assertEqual(objects.length, 2); TestCase.assertEqual(obj0.stringCol, '2'); TestCase.assertEqual(obj0.boolCol, false); TestCase.assertEqual(obj0.intCol, 2); TestCase.assertEqualWithTolerance(obj0.floatCol, 2.2, 0.000001); - TestCase.assertEqual(obj0.doubleCol, 2.22); + TestCase.assertEqualWithTolerance(obj0.doubleCol, 2.22, 0.000001); TestCase.assertEqual(obj0.dateCol.getTime(), 2); TestCase.assertEqual(obj0.dataCol.byteLength, 2); TestCase.assertEqual(obj0.objectCol, null); TestCase.assertEqual(obj0.arrayCol.length, 1); - realm.create('AllTypesObject', { primaryCol: '0' }, true); - realm.create('AllTypesObject', { primaryCol: '1' }, true); + realm.create('AllTypesObject', {primaryCol: '0'}, true); + realm.create('AllTypesObject', {primaryCol: '1'}, true); TestCase.assertEqual(obj0.stringCol, '2'); TestCase.assertEqual(obj0.objectCol, null); TestCase.assertEqual(obj1.objectCol.doubleCol, 0); - realm.create('AllTypesObject', { primaryCol: '0', stringCol: '3', objectCol: [0] }, true); + realm.create('AllTypesObject', { + primaryCol: '0', + stringCol: '3', + objectCol: {doubleCol: 0}, + }, true); + TestCase.assertEqual(obj0.stringCol, '3'); TestCase.assertEqual(obj0.boolCol, false); TestCase.assertEqual(obj0.intCol, 2); TestCase.assertEqualWithTolerance(obj0.floatCol, 2.2, 0.000001); - TestCase.assertEqual(obj0.doubleCol, 2.22); + TestCase.assertEqualWithTolerance(obj0.doubleCol, 2.22, 0.000001); TestCase.assertEqual(obj0.dateCol.getTime(), 2); TestCase.assertEqual(obj0.dataCol.byteLength, 2); TestCase.assertEqual(obj0.objectCol.doubleCol, 0); TestCase.assertEqual(obj0.arrayCol.length, 1); - realm.create('AllTypesObject', { primaryCol: '0', objectCol: undefined }, true); - realm.create('AllTypesObject', { primaryCol: '1', objectCol: null }, true); + realm.create('AllTypesObject', {primaryCol: '0', objectCol: undefined}, true); + realm.create('AllTypesObject', {primaryCol: '1', objectCol: null}, true); TestCase.assertEqual(obj0.objectCol, null); TestCase.assertEqual(obj1.objectCol, null); }); @@ -159,27 +242,31 @@ module.exports = BaseTest.extend({ testRealmCreateWithDefaults: function() { var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); + realm.write(function() { var obj = realm.create('DefaultValuesObject', {}); - TestCase.assertEqual(obj.boolCol, schemas.DefaultValues.properties[0].default); - TestCase.assertEqual(obj.intCol, schemas.DefaultValues.properties[1].default); - TestCase.assertEqualWithTolerance(obj.floatCol, schemas.DefaultValues.properties[2].default, 0.000001); - TestCase.assertEqual(obj.doubleCol, schemas.DefaultValues.properties[3].default); - TestCase.assertEqual(obj.stringCol, schemas.DefaultValues.properties[4].default); - TestCase.assertEqual(obj.dateCol.getTime(), schemas.DefaultValues.properties[5].default.getTime()); - TestCase.assertEqual(obj.dataCol.byteLength, schemas.DefaultValues.properties[6].default.byteLength); - TestCase.assertEqual(obj.objectCol.doubleCol, schemas.DefaultValues.properties[7].default[0]); + var properties = schemas.DefaultValues.properties; + + TestCase.assertEqual(obj.boolCol, properties.boolCol.default); + TestCase.assertEqual(obj.intCol, properties.intCol.default); + TestCase.assertEqualWithTolerance(obj.floatCol, properties.floatCol.default, 0.000001); + TestCase.assertEqualWithTolerance(obj.doubleCol, properties.doubleCol.default, 0.000001); + TestCase.assertEqual(obj.stringCol, properties.stringCol.default); + TestCase.assertEqual(obj.dateCol.getTime(), properties.dateCol.default.getTime()); + TestCase.assertEqual(obj.dataCol.byteLength, properties.dataCol.default.byteLength); + TestCase.assertEqual(obj.objectCol.doubleCol, properties.objectCol.default.doubleCol); TestCase.assertEqual(obj.nullObjectCol, null); - TestCase.assertEqual(obj.arrayCol.length, schemas.DefaultValues.properties[9].default.length); - TestCase.assertEqual(obj.arrayCol[0].doubleCol, schemas.DefaultValues.properties[9].default[0][0]); + TestCase.assertEqual(obj.arrayCol.length, properties.arrayCol.default.length); + TestCase.assertEqual(obj.arrayCol[0].doubleCol, properties.arrayCol.default[0].doubleCol); }); }, testRealmDelete: function() { var realm = new Realm({schema: [schemas.TestObject]}); + realm.write(function() { for (var i = 0; i < 10; i++) { - realm.create('TestObject', [i]); + realm.create('TestObject', {doubleCol: i}); } }); @@ -213,11 +300,13 @@ module.exports = BaseTest.extend({ testDeleteAll: function() { var realm = new Realm({schema: [schemas.TestObject, schemas.IntPrimary]}); + realm.write(function() { - realm.create('TestObject', [1]); - realm.create('TestObject', [2]); - realm.create('IntPrimaryObject', [2, 'value']); + realm.create('TestObject', {doubleCol: 1}); + realm.create('TestObject', {doubleCol: 2}); + realm.create('IntPrimaryObject', {primaryCol: 2, valueCol: 'value'}); }); + TestCase.assertEqual(realm.objects('TestObject').length, 2); TestCase.assertEqual(realm.objects('IntPrimaryObject').length, 1); @@ -236,10 +325,10 @@ module.exports = BaseTest.extend({ testRealmObjects: function() { var realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); realm.write(function() { - realm.create('PersonObject', ['Ari', 10, false]); - realm.create('PersonObject', ['Tim', 11, false]); - realm.create('PersonObject', {'name': 'Bjarne', 'age': 12}); - realm.create('PersonObject', {'name': 'Alex', 'age': 12, 'married': true}); + realm.create('PersonObject', {name: 'Ari', age: 10}); + realm.create('PersonObject', {name: 'Tim', age: 11}); + realm.create('PersonObject', {name: 'Bjarne', age: 12}); + realm.create('PersonObject', {name: 'Alex', age: 12, married: true}); }); TestCase.assertThrows(function() { diff --git a/tests/lib/results-tests.js b/tests/lib/results-tests.js index f26f7633..26d088a5 100644 --- a/tests/lib/results-tests.js +++ b/tests/lib/results-tests.js @@ -16,7 +16,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(objects.length, 0); realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); TestCase.assertEqual(objects.length, 1); }); TestCase.assertEqual(objects.length, 1); @@ -24,8 +24,8 @@ module.exports = BaseTest.extend({ testResultsSubscript: function() { var realm = new Realm({schema: [schemas.PersonObject]}); realm.write(function() { - realm.create('PersonObject', ['name1', 1, false]); - realm.create('PersonObject', ['name2', 2, false]); + realm.create('PersonObject', {name: 'name1', age: 1}); + realm.create('PersonObject', {name: 'name2', age: 2}); }); var people = realm.objects('PersonObject'); @@ -41,17 +41,17 @@ module.exports = BaseTest.extend({ var objects = realm.objects('TestObject'); realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertThrows(function() { - objects[-1] = [0]; + objects[-1] = {doubleCol: 0}; }); TestCase.assertThrows(function() { - objects[0] = [0]; + objects[0] = {doubleCol: 0}; }); TestCase.assertThrows(function() { - objects[1] = [0]; + objects[1] = {doubleCol: 0}; }); TestCase.assertThrows(function() { objects.length = 0; @@ -71,12 +71,13 @@ module.exports = BaseTest.extend({ testResultsEnumerate: function() { var realm = new Realm({schema: [schemas.TestObject]}); var objects = realm.objects('TestObject'); - for (var object in objects) { + + for (var index in objects) { TestCase.assertTrue(false, "No objects should have been enumerated"); } realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); TestCase.assertEqual(objects.length, 1); }); @@ -93,12 +94,13 @@ module.exports = BaseTest.extend({ testSort: function() { var realm = new Realm({schema: [schemas.TestObject]}); var objects = realm.objects('TestObject'); + realm.write(function() { - realm.create('TestObject', [2]); - realm.create('TestObject', [3]); - realm.create('TestObject', [1]); - realm.create('TestObject', [4]); - realm.create('TestObject', [0]); + realm.create('TestObject', {doubleCol: 2}); + realm.create('TestObject', {doubleCol: 3}); + realm.create('TestObject', {doubleCol: 1}); + realm.create('TestObject', {doubleCol: 4}); + realm.create('TestObject', {doubleCol: 0}); }); objects.sortByProperty('doubleCol'); diff --git a/tests/lib/schemas.js b/tests/lib/schemas.js index 2aac328f..d4bac072 100644 --- a/tests/lib/schemas.js +++ b/tests/lib/schemas.js @@ -7,20 +7,20 @@ var Realm = require('realm'); exports.TestObject = { - name: 'TestObject', - properties: [ - {name: 'doubleCol', type: Realm.Types.DOUBLE}, - ] + name: 'TestObject', + properties: { + doubleCol: Realm.Types.DOUBLE, + } }; function PersonObject() {} PersonObject.prototype.schema = { - name: 'PersonObject', - properties: [ - {name: 'name', type: Realm.Types.STRING}, - {name: 'age', type: Realm.Types.DOUBLE}, - {name: 'married', type: Realm.Types.BOOL, default: false} - ] + name: 'PersonObject', + properties: { + name: Realm.Types.STRING, + age: Realm.Types.DOUBLE, + married: {type: Realm.Types.BOOL, default: false}, + } }; PersonObject.prototype.description = function() { return this.name + ' ' + this.age; @@ -29,79 +29,79 @@ exports.PersonObject = PersonObject; exports.BasicTypes = { name: 'BasicTypesObject', - properties: [ - {name: 'boolCol', type: Realm.Types.BOOL}, - {name: 'intCol', type: Realm.Types.INT}, - {name: 'floatCol', type: Realm.Types.FLOAT}, - {name: 'doubleCol', type: Realm.Types.DOUBLE}, - {name: 'stringCol', type: Realm.Types.STRING}, - {name: 'dateCol', type: Realm.Types.DATE}, - {name: 'dataCol', type: Realm.Types.DATA}, - ] + properties: { + boolCol: Realm.Types.BOOL, + intCol: Realm.Types.INT, + floatCol: Realm.Types.FLOAT, + doubleCol: Realm.Types.DOUBLE, + stringCol: Realm.Types.STRING, + dateCol: Realm.Types.DATE, + dataCol: Realm.Types.DATA, + } }; exports.NullableBasicTypes = { name: 'NullableBasicTypesObject', - properties: [ - {name: 'boolCol', type: Realm.Types.BOOL, optional: true}, - {name: 'intCol', type: Realm.Types.INT, optional: true}, - {name: 'floatCol', type: Realm.Types.FLOAT, optional: true}, - {name: 'doubleCol', type: Realm.Types.DOUBLE, optional: true}, - {name: 'stringCol', type: Realm.Types.STRING, optional: true}, - {name: 'dateCol', type: Realm.Types.DATE, optional: true}, - {name: 'dataCol', type: Realm.Types.DATA, optional: true}, - ] + properties: { + boolCol: {type: Realm.Types.BOOL, optional: true}, + intCol: {type: Realm.Types.INT, optional: true}, + floatCol: {type: Realm.Types.FLOAT, optional: true}, + doubleCol: {type: Realm.Types.DOUBLE, optional: true}, + stringCol: {type: Realm.Types.STRING, optional: true}, + dateCol: {type: Realm.Types.DATE, optional: true}, + dataCol: {type: Realm.Types.DATA, optional: true}, + } }; exports.LinkTypes = { name: 'LinkTypesObject', - properties: [ - {name: 'objectCol', type: 'TestObject'}, - {name: 'objectCol1', type: Realm.Types.OBJECT, objectType: 'TestObject'}, - {name: 'arrayCol', type: Realm.Types.LIST, objectType: 'TestObject'}, - ] + properties: { + objectCol: 'TestObject', + objectCol1: {type: Realm.Types.OBJECT, objectType: 'TestObject'}, + arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject'}, + } }; exports.IntPrimary = { - name: 'IntPrimaryObject', - primaryKey: 'primaryCol', - properties: [ - {name: 'primaryCol', type: Realm.Types.INT}, - {name: 'valueCol', type: Realm.Types.STRING}, - ] + name: 'IntPrimaryObject', + primaryKey: 'primaryCol', + properties: { + primaryCol: Realm.Types.INT, + valueCol: Realm.Types.STRING, + } }; exports.AllTypes = { - name: 'AllTypesObject', - primaryKey: 'primaryCol', - properties: [ - {name: 'primaryCol',type: Realm.Types.STRING}, - {name: 'boolCol', type: Realm.Types.BOOL}, - {name: 'intCol', type: Realm.Types.INT}, - {name: 'floatCol', type: Realm.Types.FLOAT}, - {name: 'doubleCol', type: Realm.Types.DOUBLE}, - {name: 'stringCol', type: Realm.Types.STRING}, - {name: 'dateCol', type: Realm.Types.DATE}, - {name: 'dataCol', type: Realm.Types.DATA}, - {name: 'objectCol', type: 'TestObject'}, - {name: 'arrayCol', type: Realm.Types.LIST, objectType: 'TestObject'}, - ] + name: 'AllTypesObject', + primaryKey: 'primaryCol', + properties: { + primaryCol: Realm.Types.STRING, + boolCol: Realm.Types.BOOL, + intCol: Realm.Types.INT, + floatCol: Realm.Types.FLOAT, + doubleCol: Realm.Types.DOUBLE, + stringCol: Realm.Types.STRING, + dateCol: Realm.Types.DATE, + dataCol: Realm.Types.DATA, + objectCol: 'TestObject', + arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject'}, + } }; exports.DefaultValues = { - name: 'DefaultValuesObject', - properties: [ - {name: 'boolCol', type: Realm.Types.BOOL, default: true}, - {name: 'intCol', type: Realm.Types.INT, default: -1}, - {name: 'floatCol', type: Realm.Types.FLOAT, default: -1.1}, - {name: 'doubleCol', type: Realm.Types.DOUBLE, default: -1.11}, - {name: 'stringCol', type: Realm.Types.STRING, default: 'defaultString'}, - {name: 'dateCol', type: Realm.Types.DATE, default: new Date(1.111)}, - {name: 'dataCol', type: Realm.Types.DATA, default: new ArrayBuffer(1)}, - {name: 'objectCol', type: 'TestObject', default: [1]}, - {name: 'nullObjectCol', type: 'TestObject', default: null}, - {name: 'arrayCol', type: Realm.Types.LIST, objectType: 'TestObject', default: [[2]]}, - ] + name: 'DefaultValuesObject', + properties: { + boolCol: {type: Realm.Types.BOOL, default: true}, + intCol: {type: Realm.Types.INT, default: -1}, + floatCol: {type: Realm.Types.FLOAT, default: -1.1}, + doubleCol: {type: Realm.Types.DOUBLE, default: -1.11}, + stringCol: {type: Realm.Types.STRING, default: 'defaultString'}, + dateCol: {type: Realm.Types.DATE, default: new Date(1.111)}, + dataCol: {type: Realm.Types.DATA, default: new ArrayBuffer(1)}, + objectCol: {type: 'TestObject', default: {doubleCol: 1}}, + nullObjectCol: {type: 'TestObject', default: null}, + arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject', default: [{doubleCol: 2}]}, + } }; exports.QueryObject = {