Merge branch '2.0.x' into kneth/lazy-enable-sync

This commit is contained in:
blagoev 2017-09-28 10:29:37 +03:00 committed by GitHub
commit 37105e7f00
41 changed files with 2097 additions and 1214 deletions

View File

@ -1,4 +1,4 @@
x.x.x Release notes (yyyy-MM-dd)
x.x.x Release notes (yyyy-MM-dd)
=============================================================
### Breaking changes
* None.
@ -7,10 +7,22 @@ x.x.x Release notes (yyyy-MM-dd)
* None.
### Bug fixes
* Configuration of file system is delay to after module import (#1351).
* Configuration of sync file system is not done on module import but later when actually needed by sync (#1351)
2.0.0 Release notes (2017-9-26)
2.0.0 Release notes (2017-9-28)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* An issue where access tokens were not refreshed correctly has been addressed.
2.0.0-rc11 Release notes (2017-9-26)
=============================================================
### Breaking changes
* None
@ -25,6 +37,9 @@ x.x.x Release notes (yyyy-MM-dd)
* Alignment of permission schemas.
* Updating sync (2.0.0-rc24).
2.0.0-rc10 Release notes (2017-9-19)
=============================================================
### Breaking changes

View File

@ -1,5 +1,5 @@
PACKAGE_NAME=realm-js
VERSION=2.0.0-rc11
VERSION=2.0.0-rc13
REALM_CORE_VERSION=3.2.1
REALM_SYNC_VERSION=2.0.0-rc24
REALM_OBJECT_SERVER_VERSION=2.0.0-alpha.36

View File

@ -18,17 +18,39 @@
/**
* Abstract base class containing methods shared by {@link Realm.List} and {@link Realm.Results}.
*
* A Realm Collection is a homogenous sequence of values of any of the types
* that can be stored as properties of Realm objects. A collection can be
* accessed in any of the ways that a normal Javascript Array can, including
* subscripting, enumerating with `for-of` and so on.
*
* @memberof Realm
* @since 0.11.0
*/
class Collection {
/**
* The number of objects in the collection.
* The number of values in the collection.
* @type {number}
* @readonly
*/
get length() {}
/**
* The {@linkplain Realm~PropertyType type} of values in the collection.
* @type {string}
* @readonly
* @since 2.0.0
*/
get type() {}
/**
* Whether `null` is a valid value for the collection.
* @type {boolean}
* @readonly
* @since 2.0.0
*/
get optional() {}
/**
* Checks if this collection has not been deleted and is part of a valid Realm.
* @returns {boolean} indicating if the collection can be safely accessed.
@ -38,12 +60,15 @@ class Collection {
/**
* Returns new _Results_ that represent this collection being filtered by the provided query.
*
* @param {string} query - Query used to filter objects from the collection.
* @param {...any} [arg] - Each subsequent argument is used by the placeholders
* (e.g. `$0`, `$1`, `$2`, ) in the query.
* @throws {Error} If the query or any other argument passed into this method is invalid.
* @returns {Realm.Results} filtered according to the provided query.
*
* @returns {Realm.Results<T>} filtered according to the provided query.
*
* This is currently only supported for collections of Realm Objects.
*
* See {@tutorial query-language} for details about the query language.
* @example
* let merlots = wines.filtered('variety == "Merlot" && vintage <= $0', maxYear);
@ -51,43 +76,76 @@ class Collection {
filtered(query, ...arg) {}
/**
* Returns new _Results_ that represent this collection being sorted by the provided property
* (or properties) of each object.
* @param {string|Realm.Results~SortDescriptor[]} descriptor - The property name(s) to sort
* the objects in the collection.
* @param {boolean} [reverse=false] - May only be provided if `descriptor` is a string.
* Returns new _Results_ that represent a sorted view of this collection.
*
* A collection of Realm Objects can be sorted on one or more properties of
* those objects, or of properties of objects linked to by those objects.
* To sort by a single property, simply pass the name of that property to
* `sorted()`, optionally followed by a boolean indicating if the sort should be reversed.
* For more than one property, you must pass an array of
* {@linkplain Realm.Collection~SortDescriptor sort descriptors} which list
* which properties to sort on.
*
* Collections of other types sort on the values themselves rather than
* properties of the values, and so no property name or sort descriptors
* should be supplied.
*
* @example
* // Sort wines by age
* wines.sorted('age')
* @example
* // Sort wines by price in descending order, then sort ties by age in
* // ascending order
* wines.sorted([['price', false], ['age'])
* @example
* // Sort a list of numbers in ascending order
* let sortedPrices = wine.pricesSeen.sort()
* @example
* // Sort people by how expensive their favorite wine is
* people.sort("favoriteWine.price")
*
* @param {string|Realm.Collection~SortDescriptor[]} [descriptor] - The property name(s) to sort the collection on.
* @param {boolean} [reverse=false] - Sort in descending order rather than ascended.
* May not be supplied if `descriptor` is an array of sort descriptors.
* @throws {Error} If a specified property does not exist.
* @returns {Realm.Results} sorted according to the arguments passed in
* @returns {Realm.Results<T>} sorted according to the arguments passed in.
*/
sorted(descriptor, reverse) {}
/**
* Create a frozen snapshot of the collection. This means objects added to and removed from the
* original collection will not be reflected in the _Results_ returned by this method.
* However, objects deleted from the Realm will become `null` at their respective indices.
* This is **not** a _deep_ snapshot, meaning the objects contained in this snapshot will
* continue to update as changes are made to them.
* @returns {Realm.Results} which will **not** live update.
* Create a frozen snapshot of the collection.
*
* Values added to and removed from the original collection will not be
* reflected in the _Results_ returned by this method, including if the
* values of properties are changed to make them match or not match any
* filters applied.
*
* This is **not** a _deep_ snapshot. Realm objects contained in this
* snapshot will continue to update as changes are made to them, and if
* they are deleted from the Realm they will be replaced by `null` at the
* respective indices.
*
* @returns {Realm.Results<T>} which will **not** live update.
*/
snapshot() {}
/**
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries Array.prototype.entries}
* @returns {Realm.Collection~Iterator} of each `[index, object]` pair in the collection
* @returns {Realm.Collection~Iterator<T>} of each `[index, object]` pair in the collection
* @since 0.11.0
*/
entries() {}
/**
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/keys Array.prototype.keys}
* @returns {Realm.Collection~Iterator} of each index in the collection
* @returns {Realm.Collection~Iterator<T>} of each index in the collection
* @since 0.11.0
*/
keys() {}
/**
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values Array.prototype.values}
* @returns {Realm.Collection~Iterator} of each Realm object in the collection
* @returns {Realm.Collection~Iterator<T>} of each Realm object in the collection
* @since 0.11.0
*/
values() {}
@ -101,7 +159,7 @@ class Collection {
* spread operators, and more.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator Symbol.iterator}
* and the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterable iterable protocol}
* @returns {Realm.Collection~Iterator} of each Realm object in the collection
* @returns {Realm.Collection~Iterator<T>} of each Realm object in the collection
* @since 0.11.0
* @example
* for (let object of collection) {
@ -128,7 +186,7 @@ class Collection {
* index will be include in the return value. If negative, then the end index will be
* counted from the end of the collection. If omitted, then all objects from the start
* index will be included in the return value.
* @returns {Realm.Object[]} containing the objects from the start index up to, but not
* @returns {T[]} containing the objects from the start index up to, but not
* including, the end index.
* @since 0.11.0
*/
@ -143,7 +201,7 @@ class Collection {
* - `index` The index of the object being processed in the collection.
* - `collection` The collection itself.
* @param {object} [thisArg] - The value of `this` when `callback` is called.
* @returns {Realm.Object|undefined} if the `callback` did not return `true` for any object
* @returns {T|undefined} if the `callback` did not return `true` for any object
* in the collection.
* @since 0.11.0
*/
@ -166,10 +224,11 @@ class Collection {
/**
Finds the index of the given object in the collection.
* @param {Realm.Object} [object] - The object to search for in the collection.
* @throws {Error} If the argument does not belong to the realm.
* @returns {number} representing the index where the object was found, or `-1`
* if not in collection.
* @param {T} object - The value to search for in the collection.
* @throws {Error} If the argument is a {@link Realm.Object} that does not
* belong to the same Realm as the collection.
* @returns {number} representing the index where the value was found, or
* `-1` if not in collection.
* @since 1.8.2
*/
indexOf(object) {}
@ -294,7 +353,7 @@ class Collection {
/**
* Remove the listener `callback` from the collection instance.
* @param {function(collection, changes)} callback - Callback function that was previously
* added as a listener through the {@link Collection#addListener addListener} method.
* added as a listener through the {@link Collection#addListener addListener} method.
* @throws {Error} If `callback` is not a function.
*/
removeListener(callback) {}
@ -324,8 +383,9 @@ class Collection {
*/
/**
* The sort descriptors may either just be a string representing the property name, **or** an
* array with two items: `[propertyName, reverse]`
* A sort descriptor is either a string containing one or more property names
* separate by dots, **or** an array with two items: `[propertyName, reverse]`.
*
* @typedef Realm.Collection~SortDescriptor
* @type {string|Array}
*/

View File

@ -19,59 +19,70 @@
/**
* Instances of this class will be returned when accessing object properties whose type is `"list"`
* (see {@linkplain Realm~ObjectSchemaProperty ObjectSchemaProperty}).
* The objects contained in a list are accessible through its index properties and may only be
* modified inside a {@linkplain Realm#write write} transaction.
*
* Lists mostly behave like normal Javascript Arrays, except for that they can
* only store values of a single type (indicated by the `type` and `optional`
* properties of the List), and can only be modified inside a {@linkplain
* Realm#write write} transaction.
*
* @extends Realm.Collection
* @memberof Realm
*/
class List extends Collection {
/**
* Remove the **last** object from the list and return it.
* Remove the **last** value from the list and return it.
* @throws {Error} If not inside a write transaction.
* @returns {Realm.Object|undefined} if the list is empty.
* @returns {T|undefined} if the list is empty.
*/
pop() {}
/**
* Add one or more objects to the _end_ of the list.
* @param {...Realm.Object} object - Each objects type must match
* {@linkcode Realm~ObjectSchemaProperty objectType} specified in the schema.
* @throws {TypeError} If an `object` is of the wrong type.
* Add one or more values to the _end_ of the list.
*
* @param {...T} value - Values to add to the list.
* @throws {TypeError} If a `value` is not of a type which can be stored in
* the list, or if an object being added to the list does not match the
* {@linkcode Realm~ObjectSchema object schema} for the list.
*
* @throws {Error} If not inside a write transaction.
* @returns {number} equal to the new {@link Realm.List#length length} of the list
* after adding objects.
* @returns {number} equal to the new {@link Realm.List#length length} of
* the list after adding the values.
*/
push(...object) {}
push(...value) {}
/**
* Remove the **first** object from the list and return it.
* Remove the **first** value from the list and return it.
* @throws {Error} If not inside a write transaction.
* @returns {Realm.Object|undefined} if the list is empty.
* @returns {T|undefined} if the list is empty.
*/
shift() {}
/**
* Changes the contents of the list by removing objects and/or inserting new objects.
* Changes the contents of the list by removing value and/or inserting new value.
*
* @see {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice Array.prototype.splice}
* @param {number} index - The start index. If greater than the length of the list,
* the start index will be set to the length instead. If negative, then the start index
* will be counted from the end of the list (e.g. `list.length - index`).
* @param {number} [count] - The number of objects to remove from the list. If not provided,
* then all objects from the start index through the end of the list will be removed.
* @param {...Realm.Object} [object] - Objects to insert into the list starting at `index`.
* @returns {Realm.Object[]} containing the objects that were removed from the list. The
* array is empty if no objects were removed.
* @param {number} [count] - The number of values to remove from the list.
* If not provided, then all values from the start index through the end of
* the list will be removed.
* @param {...T} [value] - Values to insert into the list starting at `index`.
* @returns {T[]} containing the value that were removed from the list. The
* array is empty if no value were removed.
*/
splice(index, count, ...object) {}
/**
* Add one or more objects to the _beginning_ of the list.
* @param {...Realm.Object} object - Each objects type must match
* {@linkcode Realm~ObjectSchemaProperty objectType} specified in the schema.
* @throws {TypeError} If an `object` is of the wrong type.
* Add one or more values to the _beginning_ of the list.
*
* @param {...T} value - Values to add to the list.
* @throws {TypeError} If a `value` is not of a type which can be stored in
* the list, or if an object being added to the list does not match the
* {@linkcode Realm~ObjectSchema object schema} for the list.
* @throws {Error} If not inside a write transaction.
* @returns {number} equal to the new {@link Realm.List#length length} of the list
* after adding objects.
* @returns {number} equal to the new {@link Realm.List#length length} of
* the list after adding the values.
*/
unshift(...object) {}
}
}

View File

@ -95,7 +95,7 @@ class Realm {
* Open a Realm asynchronously with a promise. If the Realm is synced, it will be fully
* synchronized before it is available.
* @param {Realm~Configuration} config
* @returns {ProgressPromise} - a promise that will be resolved with the realm instance when it's available.
* @returns {ProgressPromise} - a promise that will be resolved with the Realm instance when it's available.
*/
static open(config) {}
@ -103,7 +103,7 @@ class Realm {
* Open a Realm asynchronously with a callback. If the Realm is synced, it will be fully
* synchronized before it is available.
* @param {Realm~Configuration} config
* @param {callback(error, realm)} - will be called when the realm is ready.
* @param {callback(error, realm)} - will be called when the Realm is ready.
* @param {callback(transferred, transferable)} [progressCallback] - an optional callback for download progress notifications
* @throws {Error} If anything in the provided `config` is invalid.
*/
@ -286,6 +286,7 @@ Realm.defaultPath;
* @property {boolean} [readOnly=false] - Specifies if this Realm should be opened as read-only.
* @property {Array<Realm~ObjectClass|Realm~ObjectSchema>} [schema] - Specifies all the
* object types in this Realm. **Required** when first creating a Realm at this `path`.
* If omitted, the schema will be read from the existing Realm file.
* @property {number} [schemaVersion] - **Required** (and must be incremented) after
* changing the `schema`.
* @property {Object} [sync] - Sync configuration parameters with the following
@ -350,19 +351,39 @@ Realm.defaultPath;
* that must be unique across all objects of this type within the same Realm.
* @property {Object<string, (Realm~PropertyType|Realm~ObjectSchemaProperty)>} properties -
* An object where the keys are property names and the values represent the property type.
*
* @example
* let MyClassSchema = {
* name: 'MyClass',
* primaryKey: 'pk',
* properties: {
* pk: 'int',
* optionalFloatValue: 'float?' // or {type: 'float', optional: true}
* listOfStrings: 'string[]',
* listOfOptionalDates: 'date?[]',
* indexedInt: {type: 'int', indexed: true}
*
* linkToObject: 'MyClass',
* listOfObjects: 'MyClass[]', // or {type: 'list', objectType: 'MyClass'}
* objectsLinkingToThisObject: {type: 'linkingObjects', objectType: 'MyClass', property: 'linkToObject'}
* }
* };
*/
/**
* @typedef Realm~ObjectSchemaProperty
* @type {Object}
* @property {Realm~PropertyType} type - The type of this property.
* @property {string} [objectType] - **Required** when `type` is `"list"` or `"linkingObjects"`,
* and must match the type of an object in the same schema.
* @property {Realm~PropertyType} [objectType] - **Required** when `type` is `"list"` or `"linkingObjects"`,
* and must match the type of an object in the same schema, or, for `"list"`
* only, any other type which may be stored as a Realm property.
* @property {string} [property] - **Required** when `type` is `"linkingObjects"`, and must match
* the name of a property on the type specified in `objectType` that links to the type this property belongs to.
* @property {any} [default] - The default value for this property on creation when not
* otherwise specified.
* @property {boolean} [optional] - Signals if this property may be assigned `null` or `undefined`.
* For `"list"` properties of non-object types, this instead signals whether the values inside the list may be assigned `null` or `undefined`.
* This is not supported for `"list"` properties of object types and `"linkingObjects"` properties.
* @property {boolean} [indexed] - Signals if this property should be indexed. Only supported for
* `"string"`, `"int"`, and `"bool"` properties.
*/
@ -376,10 +397,21 @@ Realm.defaultPath;
*/
/**
* A property type may be specified as one of the standard builtin types, or as an object type
* inside the same schema.
* A property type may be specified as one of the standard builtin types, or as
* an object type inside the same schema.
*
* When specifying property types in an {@linkplain Realm~ObjectSchema object schema}, you
* may append `?` to any of the property types to indicate that it is optional
* (i.e. it can be `null` in addition to the normal values) and `[]` to
* indicate that it is instead a list of that type. For example,
* `optionalIntList: 'int?[]'` would declare a property which is a list of
* nullable integers. The property types reported by {@linkplain Realm.Collection
* collections} and in a Realm's schema will never
* use these forms.
*
* @typedef Realm~PropertyType
* @type {("bool"|"int"|"float"|"double"|"string"|"date"|"data"|"list"|"linkingObjects"|"<ObjectType>")}
*
* @property {boolean} "bool" - Property value may either be `true` or `false`.
* @property {number} "int" - Property may be assigned any number, but will be stored as a
* round integer, meaning anything after the decimal will be truncated.

View File

@ -23,6 +23,7 @@
* {@link Realm.Results#snapshot snapshot()}, however, will **not** live update
* (and listener callbacks added through {@link Realm.Results#addListener addListener()}
* will thus never be called).
*
* @extends Realm.Collection
* @memberof Realm
*/

View File

@ -54,7 +54,7 @@ export function fireMutationListeners(realmId) {
}
function isIndex(propertyName) {
return typeof propertyName === 'number' || (typeof propertyName === 'string' && /^\d+$/.test(propertyName));
return typeof propertyName === 'number' || (typeof propertyName === 'string' && /^-?\d+$/.test(propertyName));
}
const mutable = Symbol('mutable');
@ -83,7 +83,10 @@ const traps = {
return true;
}
return Reflect.set(collection, property, value, collection);
if (!Reflect.set(collection, property, value, collection)) {
throw new TypeError(`Cannot assign to read only property '${property}'`)
}
return true;
},
ownKeys(collection) {
return Reflect.ownKeys(collection).concat(Array.from({ length: collection.length }, (value, key) => String(key)));
@ -117,8 +120,11 @@ export function createCollection(prototype, realmId, info, _mutable) {
'length': {
get: getterForProperty('length'),
},
'-1': {
value: undefined,
'type': {
get: getterForProperty('type'),
},
'optional': {
get: getterForProperty('optional'),
},
});

View File

@ -72,6 +72,12 @@ export function createUser(args) {
return deserialize(undefined, result);
}
export function _adminUser(args) {
args = args.map((arg) => serialize(null, arg));
const result = sendRequest('_adminUser', {arguments: args});
return deserialize(undefined, result);
}
export function callMethod(realmId, id, name, args) {
if (args) {
args = args.map((arg) => serialize(realmId, arg));

View File

@ -19,7 +19,7 @@
'use strict';
import { createUser as createUserRPC, getAllUsers as getAllUsersRPC } from './rpc';
import { createUser as createUserRPC, _adminUser as _adminUserRPC, getAllUsers as getAllUsersRPC } from './rpc';
import { keys, objectTypes } from './constants';
import { createMethods } from './util';
@ -28,6 +28,10 @@ export default class User {
return createUserRPC(Array.from(arguments));
}
static _adminUser(adminToken, server) {
return _adminUserRPC(Array.from(arguments));
}
static get all() {
return getAllUsersRPC();
}

View File

@ -76,7 +76,10 @@ function refreshAccessToken(user, localRealmPath, realmUrl) {
provider: 'realm',
app_id: ''
}),
headers: postHeaders
headers: postHeaders,
// FIXME: This timeout appears to be necessary in order for some requests to be sent at all.
// See https://github.com/realm/realm-js-private/issues/338 for details.
timeout: 1000.0
};
performFetch(url, options)
.then((response) => response.json().then((json) => { return { response, json }; }))

View File

@ -1,7 +1,7 @@
{
"name": "realm",
"description": "Realm is a mobile database: an alternative to SQLite and key-value stores",
"version": "2.0.0-rc11",
"version": "2.0.0-rc13",
"license": "Apache-2.0",
"homepage": "https://realm.io",
"keywords": [

View File

@ -0,0 +1,24 @@
#!/usr/bin/ruby
require 'json'
ios_sim_default_device_type = ENV["IOS_SIM_DEVICE_TYPE"] or "iPhone 5s"
ios_sim_default_ios_version = ENV["IOS_SIM_OS"] or "iOS 10.1"
mode = ARGV[0]
devices = JSON.parse(%x{xcrun simctl list devices --json})['devices']
.each { |os, group| group.each{ |dev| dev['os'] = os } }
.flat_map { |x| x[1] }
if mode == "booted" then
device = devices.select{|x| x['state'] == 'Booted'}
else
device = devices
.select{ |x| x['availability'] == '(available)' }
.each { |x| x['score'] = (x['name'] == '$ios_sim_default_device_type' ? 1 : 0) + (x['os'] == '$ios_sim_default_ios_version' ? 1 : 0) }
.sort_by! { |x| [x['score'], x['name']] }
.reverse!
end
if device and device[0] then
puts device[0]['udid']
end

View File

@ -14,8 +14,6 @@ if echo "$CONFIGURATION" | grep -i "^Debug$" > /dev/null ; then
fi
IOS_SIM_DEVICE=${IOS_SIM_DEVICE:-} # use preferentially, otherwise will be set and re-exported
ios_sim_default_device_type=${IOS_SIM_DEVICE_TYPE:-iPhone 5s}
ios_sim_default_ios_version=${IOS_SIM_OS:-iOS 10.1}
PATH="/opt/android-sdk-linux/platform-tools:$PATH"
SRCROOT=$(cd "$(dirname "$0")/.." && pwd)
@ -165,42 +163,43 @@ setup_ios_simulator() {
# -- Ensure that the simulator is ready
if [ $CI_RUN == true ]; then
# - Kill the Simulator to ensure we are running the correct one, only when running in CI
echo "Resetting simulator using toolchain from: $DEVELOPER_DIR"
# - Kill the Simulator to ensure we are running the correct one, only when running in CI
echo "Resetting simulator using toolchain from: $DEVELOPER_DIR"
# Quit Simulator.app to give it a chance to go down gracefully
local deadline=$((SECONDS+5))
while pgrep -qx Simulator && [ $SECONDS -lt $deadline ]; do
osascript -e 'tell app "Simulator" to quit without saving' || true
sleep 0.25 # otherwise the pkill following will get it too early
done
# Quit Simulator.app to give it a chance to go down gracefully
local deadline=$((SECONDS+5))
while pgrep -qx Simulator && [ $SECONDS -lt $deadline ]; do
osascript -e 'tell app "Simulator" to quit without saving' || true
sleep 0.25 # otherwise the pkill following will get it too early
done
# stop CoreSimulatorService
launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
sleep 0.25 # launchtl can take a small moment to kill services
# stop CoreSimulatorService
launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
sleep 0.25 # launchtl can take a small moment to kill services
# kill them with fire
while pgrep -qx Simulator com.apple.CoreSimulator.CoreSimulatorService; do
pkill -9 -x Simulator com.apple.CoreSimulator.CoreSimulatorService || true
sleep 0.05
done
# kill them with fire
while pgrep -qx Simulator com.apple.CoreSimulator.CoreSimulatorService; do
pkill -9 -x Simulator com.apple.CoreSimulator.CoreSimulatorService || true
sleep 0.05
done
# - Prod `simctl` a few times as sometimes it fails the first couple of times after switching XCode vesions
local deadline=$((SECONDS+5))
while [ -z "$(xcrun simctl list devices 2>/dev/null)" ] && [ $SECONDS -lt $deadline ]; do
: # nothing to see here, will stop cycling on the first successful run
done
# - Prod `simctl` a few times as sometimes it fails the first couple of times after switching XCode vesions
local deadline=$((SECONDS+5))
while [ -z "$(xcrun simctl list devices 2>/dev/null)" ] && [ $SECONDS -lt $deadline ]; do
: # nothing to see here, will stop cycling on the first successful run
done
# - Choose a device, if it has not already been chosen
local deadline=$((SECONDS+5))
while [ -z "$IOS_SIM_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
IOS_DEVICE=$(ruby -rjson -e "puts JSON.parse(%x{xcrun simctl list devices --json})['devices'].each{|os,group| group.each{|dev| dev['os'] = os}}.flat_map{|x| x[1]}.select{|x| x['availability'] == '(available)'}.each{|x| x['score'] = (x['name'] == '$ios_sim_default_device_type' ? 1 : 0) + (x['os'] == '$ios_sim_default_ios_version' ? 1 : 0)}.sort_by!{|x| [x['score'], x['name']]}.reverse![0]['udid']")
export IOS_SIM_DEVICE=$IOS_DEVICE
IOS_DEVICE=""
while [ -z "$IOS_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
IOS_DEVICE="$(ruby $SRCROOT/scripts/find-ios-device.rb best)"
done
if [ -z "$IOS_SIM_DEVICE" ]; then
if [ -z "$IOS_DEVICE" ]; then
echo "*** Failed to determine the iOS Simulator device to use ***"
exit 1
fi
export IOS_SIM_DEVICE=$IOS_DEVICE
# - Reset the device we will be using if running in CI
xcrun simctl shutdown "$IOS_SIM_DEVICE" 1>/dev/null 2>/dev/null || true # sometimes simctl gets confused
@ -214,19 +213,20 @@ setup_ios_simulator() {
startedSimulator=true
else
# - ensure that the simulator is running on a developer's workstation
# - ensure that the simulator is running on a developer's workstation
open "$DEVELOPER_DIR/Applications/Simulator.app"
# - Select the first device booted in the simulator, since it will boot something for us
local deadline=$((SECONDS+10))
while [ -z "$IOS_SIM_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
IOS_DEVICE=$(ruby -rjson -e "puts JSON.parse(%x{xcrun simctl list devices --json})['devices'].each{|os,group| group.each{|dev| dev['os'] = os}}.flat_map{|x| x[1]}.select{|x| x['state'] == 'Booted'}[0]['udid']")
export IOS_SIM_DEVICE=$IOS_DEVICE
IOS_DEVICE=""
while [ -z "$IOS_DEVICE" ] && [ $SECONDS -lt $deadline ]; do
IOS_DEVICE="$(ruby $SRCROOT/scripts/find-ios-device.rb booted)"
done
if [ -z "$IOS_SIM_DEVICE" ]; then
if [ -z "$IOS_DEVICE" ]; then
echo "*** Failed to determine the iOS Simulator device in use ***"
exit 1
fi
export IOS_SIM_DEVICE=$IOS_DEVICE
fi
# Wait until the boot completes

View File

@ -54,7 +54,7 @@ struct Arguments {
};
template<typename T>
using ArgumentsMethodType = void(typename T::Context, typename T::Function, typename T::Object, Arguments<T>, ReturnValue<T> &);
using ArgumentsMethodType = void(typename T::Context, typename T::Object, Arguments<T>, ReturnValue<T> &);
template<typename T>
struct PropertyType {

View File

@ -54,30 +54,33 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
using Object = js::Object<T>;
using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>;
using Arguments = js::Arguments<T>;
static ObjectType create_instance(ContextType, realm::List);
// properties
static void get_length(ContextType, ObjectType, ReturnValue &);
static void get_type(ContextType, ObjectType, ReturnValue &);
static void get_optional(ContextType, ObjectType, ReturnValue &);
static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &);
static bool set_index(ContextType, ObjectType, uint32_t, ValueType);
// methods
static void push(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void pop(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void unshift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void shift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void splice(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void push(ContextType, ObjectType, Arguments, ReturnValue &);
static void pop(ContextType, ObjectType, Arguments, ReturnValue &);
static void unshift(ContextType, ObjectType, Arguments, ReturnValue &);
static void shift(ContextType, ObjectType, Arguments, ReturnValue &);
static void splice(ContextType, ObjectType, Arguments, ReturnValue &);
static void snapshot(ContextType, ObjectType, Arguments, ReturnValue &);
static void filtered(ContextType, ObjectType, Arguments, ReturnValue &);
static void sorted(ContextType, ObjectType, Arguments, ReturnValue &);
static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &);
static void index_of(ContextType, ObjectType, Arguments, ReturnValue &);
// observable
static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
std::string const name = "List";
@ -99,9 +102,14 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
PropertyMap<T> const properties = {
{"length", {wrap<get_length>, nullptr}},
{"type", {wrap<get_type>, nullptr}},
{"optional", {wrap<get_optional>, nullptr}},
};
IndexPropertyType<T> const index_accessor = {wrap<get_index>, wrap<set_index>};
private:
static void validate_value(ContextType, realm::List&, ValueType);
};
template<typename T>
@ -115,70 +123,83 @@ void ListClass<T>::get_length(ContextType, ObjectType object, ReturnValue &retur
return_value.set((uint32_t)list->size());
}
template<typename T>
void ListClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(object);
return_value.set(string_for_property_type(list->get_type() & ~realm::PropertyType::Flags));
}
template<typename T>
void ListClass<T>::get_optional(ContextType, ObjectType object, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(object);
return_value.set(is_nullable(list->get_type()));
}
template<typename T>
void ListClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(object);
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
NativeAccessor<T> accessor(ctx, *list);
return_value.set(list->get(accessor, index));
}
template<typename T>
bool ListClass<T>::set_index(ContextType ctx, ObjectType object, uint32_t index, ValueType value) {
auto list = get_internal<T, ListClass<T>>(object);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
validate_value(ctx, *list, value);
NativeAccessor<T> accessor(ctx, *list);
list->set(accessor, index, value);
return true;
}
template<typename T>
void ListClass<T>::push(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::push(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
for (size_t i = 0; i < argc; i++) {
list->add(accessor, arguments[i]);
for (size_t i = 0; i < args.count; i++) {
validate_value(ctx, *list, args[i]);
}
NativeAccessor<T> accessor(ctx, *list);
for (size_t i = 0; i < args.count; i++) {
list->add(accessor, args[i]);
}
return_value.set((uint32_t)list->size());
}
template<typename T>
void ListClass<T>::pop(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ListClass<T>::pop(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
size_t size = list->size();
auto size = static_cast<unsigned int>(list->size());
if (size == 0) {
list->verify_in_transaction();
return_value.set_undefined();
}
else {
size_t index = size - 1;
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
list->remove(index);
get_index(ctx, this_object, size - 1, return_value);
list->remove(size - 1);
}
}
template<typename T>
void ListClass<T>::unshift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::unshift(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
for (size_t i = 0; i < argc; i++) {
list->insert(accessor, i, arguments[i]);
for (size_t i = 0; i < args.count; i++) {
validate_value(ctx, *list, args[i]);
}
NativeAccessor<T> accessor(ctx, *list);
for (size_t i = 0; i < args.count; i++) {
list->insert(accessor, i, args[i]);
}
return_value.set((uint32_t)list->size());
}
template<typename T>
void ListClass<T>::shift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ListClass<T>::shift(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
if (list->size() == 0) {
@ -186,151 +207,108 @@ void ListClass<T>::shift(ContextType ctx, FunctionType, ObjectType this_object,
return_value.set_undefined();
}
else {
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(0));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
get_index(ctx, this_object, 0, return_value);
list->remove(0);
}
}
template<typename T>
void ListClass<T>::splice(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::splice(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
size_t size = list->size();
long index = std::min<long>(Value::to_number(ctx, arguments[0]), size);
long index = std::min<long>(Value::to_number(ctx, args[0]), size);
if (index < 0) {
index = std::max<long>(size + index, 0);
}
size_t remove;
if (argc < 2) {
if (args.count < 2) {
remove = size - index;
}
else {
remove = std::max<long>(Value::to_number(ctx, arguments[1]), 0);
remove = std::max<long>(Value::to_number(ctx, args[1]), 0);
remove = std::min<long>(remove, size - index);
}
std::vector<ValueType> removed_objects;
removed_objects.reserve(remove);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
NativeAccessor<T> accessor(ctx, *list);
for (size_t i = 0; i < remove; i++) {
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
removed_objects.push_back(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
removed_objects.push_back(list->get(accessor, index));
list->remove(index);
}
for (size_t i = 2; i < argc; i++) {
list->insert(accessor, index + i - 2, arguments[i]);
for (size_t i = 2; i < args.count; i++) {
list->insert(accessor, index + i - 2, args[i]);
}
return_value.set(Object::create_array(ctx, removed_objects));
}
template<typename T>
void ListClass<T>::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ListClass<T>::snapshot(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, list->snapshot()));
}
template<typename T>
void ListClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::filtered(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_filtered(ctx, *list, argc, arguments));
return_value.set(ResultsClass<T>::create_filtered(ctx, *list, args));
}
template<typename T>
void ListClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ListClass<T>::sorted(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, list->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
return_value.set(ResultsClass<T>::create_instance(ctx, list->sort(ResultsClass<T>::get_keypaths(ctx, args))));
}
template<typename T>
void ListClass<T>::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ListClass<T>::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid());
}
template<typename T>
void ListClass<T>::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
ObjectType arg = Value::validated_to_object(ctx, arguments[0]);
if (Object::template is_instance<RealmObjectClass<T>>(ctx, arg)) {
auto object = get_internal<T, RealmObjectClass<T>>(arg);
if (!object->is_valid()) {
throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.");
}
void ListClass<T>::index_of(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto fn = [&](auto&& row) {
auto list = get_internal<T, ListClass<T>>(this_object);
size_t ndx = list->find(object->row());
if (ndx == realm::not_found) {
return_value.set(-1);
}
else {
return_value.set((uint32_t)ndx);
}
}
else {
return_value.set(-1);
}
NativeAccessor<T> accessor(ctx, *list);
return list->find(accessor, row);
};
ResultsClass<T>::index_of(ctx, fn, args, return_value);
}
template<typename T>
void ListClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void ListClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
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));
auto token = list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
HANDLESCOPE
ValueType arguments[2];
arguments[0] = static_cast<ObjectType>(protected_this);
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
list->m_notification_tokens.emplace_back(protected_callback, std::move(token));
ResultsClass<T>::add_listener(ctx, *list, this_object, args);
}
template<typename T>
void ListClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void ListClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto iter = list->m_notification_tokens.begin();
typename Protected<FunctionType>::Comparator compare;
while (iter != list->m_notification_tokens.end()) {
if(compare(iter->first, protected_function)) {
iter = list->m_notification_tokens.erase(iter);
}
else {
iter++;
}
}
ResultsClass<T>::remove_listener(ctx, *list, this_object, args);
}
template<typename T>
void ListClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
template<typename T>
void ListClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
list->m_notification_tokens.clear();
}
template<typename T>
void ListClass<T>::validate_value(ContextType ctx, realm::List& list, ValueType value) {
auto type = list.get_type();
StringData object_type;
if (type == realm::PropertyType::Object) {
object_type = list.get_object_schema().name;
}
if (!Value::is_valid_for_property_type(ctx, value, type, object_type)) {
throw TypeErrorException("Property", object_type ? object_type : string_for_property_type(type), Value::to_string(ctx, value));
}
}
} // js
} // realm

View File

@ -47,12 +47,19 @@ public:
using OptionalValue = util::Optional<ValueType>;
NativeAccessor(ContextType ctx, std::shared_ptr<Realm> realm, const ObjectSchema& object_schema)
: m_ctx(ctx), m_realm(std::move(realm)), m_object_schema(object_schema) { }
: m_ctx(ctx), m_realm(std::move(realm)), m_object_schema(&object_schema) { }
template<typename Collection>
NativeAccessor(ContextType ctx, Collection const& collection)
: m_ctx(ctx)
, m_realm(collection.get_realm())
, m_object_schema(collection.get_type() == realm::PropertyType::Object ? &collection.get_object_schema() : nullptr)
{ }
NativeAccessor(NativeAccessor& parent, const Property& prop)
: m_ctx(parent.m_ctx)
, m_realm(parent.m_realm)
, m_object_schema(*m_realm->schema().find(prop.object_type))
, m_object_schema(&*m_realm->schema().find(prop.object_type))
{ }
OptionalValue value_for_property(ValueType dict, std::string const& prop_name, size_t prop_index) {
@ -61,11 +68,9 @@ public:
return util::none;
}
ValueType value = Object::get_property(m_ctx, object, prop_name);
const auto& prop = m_object_schema.persisted_properties[prop_index];
const auto& prop = m_object_schema->persisted_properties[prop_index];
if (!Value::is_valid_for_property(m_ctx, value, prop)) {
throw TypeErrorException(m_object_schema.name, prop.name,
js_type_name_for_property_type(prop.type),
print(value));
throw TypeErrorException(*this, m_object_schema->name, prop, value);
}
return value;
}
@ -79,6 +84,14 @@ public:
template<typename T>
T unbox(ValueType value, bool create = false, bool update = false);
template<typename T>
util::Optional<T> unbox_optional(ValueType value) {
return is_null(value) ? util::none : util::make_optional(unbox<T>(value));
}
template<typename T>
ValueType box(util::Optional<T> v) { return v ? box(*v) : null_value(); }
ValueType box(bool boolean) { return Value::from_boolean(m_ctx, boolean); }
ValueType box(int64_t number) { return Value::from_number(m_ctx, number); }
ValueType box(float number) { return Value::from_number(m_ctx, number); }
@ -88,11 +101,20 @@ public:
ValueType box(Mixed) { throw std::runtime_error("'Any' type is unsupported"); }
ValueType box(Timestamp ts) {
if (ts.is_null()) {
return null_value();
}
return Object::create_date(m_ctx, ts.get_seconds() * 1000 + ts.get_nanoseconds() / 1000000);
}
ValueType box(realm::Object realm_object) {
return RealmObjectClass<JSEngine>::create_instance(m_ctx, std::move(realm_object));
}
ValueType box(RowExpr row) {
if (!row.is_attached()) {
return Value::from_null(m_ctx);
}
return RealmObjectClass<JSEngine>::create_instance(m_ctx, realm::Object(m_realm, *m_object_schema, row));
}
ValueType box(realm::List list) {
return ListClass<JSEngine>::create_instance(m_ctx, std::move(list));
}
@ -112,7 +134,7 @@ public:
auto obj = Value::validated_to_object(m_ctx, value);
uint32_t size = Object::validated_get_length(m_ctx, obj);
for (uint32_t i = 0; i < size; ++i) {
func(Object::validated_get_object(m_ctx, obj, i));
func(Object::get_property(m_ctx, obj, i));
}
}
@ -128,12 +150,14 @@ public:
void will_change(realm::Object&, realm::Property const&) { }
void did_change() { }
std::string print(ValueType const& v) { return Value::to_string(m_ctx, v); }
std::string print(ValueType const&);
void print(std::string&, ValueType const&);
const char *typeof(ValueType const& v) { return Value::typeof(m_ctx, v); }
private:
ContextType m_ctx;
std::shared_ptr<Realm> m_realm;
const ObjectSchema& m_object_schema;
const ObjectSchema* m_object_schema;
std::string m_string_buffer;
OwnedBinaryData m_owned_binary_data;
@ -173,34 +197,37 @@ struct Unbox<JSEngine, double> {
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<bool>> {
static util::Optional<bool> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_boolean(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<bool>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<int64_t>> {
static util::Optional<int64_t> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<int64_t>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<float>> {
static util::Optional<float> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<float>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<double>> {
static util::Optional<double> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<double>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, StringData> {
static StringData call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
if (ctx->is_null(value)) {
return StringData();
}
ctx->m_string_buffer = js::Value<JSEngine>::validated_to_string(ctx->m_ctx, value, "Property");
return ctx->m_string_buffer;
}
@ -209,6 +236,9 @@ struct Unbox<JSEngine, StringData> {
template<typename JSEngine>
struct Unbox<JSEngine, BinaryData> {
static BinaryData call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value value, bool, bool) {
if (ctx->is_null(value)) {
return BinaryData();
}
ctx->m_owned_binary_data = js::Value<JSEngine>::validated_to_binary(ctx->m_ctx, value, "Property");
return ctx->m_owned_binary_data.get();
}
@ -224,6 +254,9 @@ struct Unbox<JSEngine, Mixed> {
template<typename JSEngine>
struct Unbox<JSEngine, Timestamp> {
static Timestamp call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
if (ctx->is_null(value)) {
return Timestamp();
}
auto date = js::Value<JSEngine>::validated_to_date(ctx->m_ctx, value, "Property");
double milliseconds = js::Value<JSEngine>::to_number(ctx->m_ctx, date);
int64_t seconds = milliseconds / 1000;
@ -248,15 +281,15 @@ struct Unbox<JSEngine, RowExpr> {
throw std::runtime_error("Realm object is from another Realm");
}
}
else if (!create) {
throw std::runtime_error("object is not a Realm Object");
if (!create) {
throw NonRealmObjectException();
}
if (Value::is_array(ctx->m_ctx, object)) {
object = Schema<JSEngine>::dict_for_property_array(ctx->m_ctx, ctx->m_object_schema, object);
object = Schema<JSEngine>::dict_for_property_array(ctx->m_ctx, *ctx->m_object_schema, object);
}
auto child = realm::Object::create<ValueType>(*ctx, ctx->m_realm, ctx->m_object_schema,
auto child = realm::Object::create<ValueType>(*ctx, ctx->m_realm, *ctx->m_object_schema,
static_cast<ValueType>(object), try_update);
return child.row();
}
@ -269,6 +302,62 @@ U NativeAccessor<T>::unbox(ValueType value, bool create, bool update) {
return _impl::Unbox<T, U>::call(this, std::move(value), create, update);
}
template<typename T>
std::string NativeAccessor<T>::print(ValueType const& value) {
std::string ret;
print(ret, value);
return ret;
}
template<typename T>
void NativeAccessor<T>::print(std::string& str, ValueType const& value) {
if (Value::is_null(m_ctx, value)) {
str += "null";
}
else if (Value::is_undefined(m_ctx, value)) {
str += "undefined";
}
else if (Value::is_array(m_ctx, value)) {
auto array = Value::to_array(m_ctx, value);
auto length = Object::validated_get_length(m_ctx, array);
str += "[";
for (uint32_t i = 0; i < length; i++) {
print(str, Object::get_property(m_ctx, array, i));
if (i + 1 < length) {
str += ", ";
}
}
str += "]";
}
else if (Value::is_object(m_ctx, value)) {
auto object = Value::to_object(m_ctx, value);
if (Object::template is_instance<RealmObjectClass<T>>(m_ctx, object)) {
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
auto& object_schema = realm_object->get_object_schema();
str += object_schema.name;
str += "{";
for (size_t i = 0, count = object_schema.persisted_properties.size(); i < count; ++i) {
print(str, realm_object->template get_property_value<ValueType>(*this, object_schema.persisted_properties[i].name));
if (i + 1 < count) {
str += ", ";
}
}
str += "}";
}
else {
str += Value::to_string(m_ctx, value);
}
}
else if (Value::is_string(m_ctx, value)) {
str += "'";
str += Value::to_string(m_ctx, value);
str += "'";
}
else {
str += Value::to_string(m_ctx, value);
}
}
} // js
} // realm

View File

@ -18,6 +18,7 @@
#include "platform.hpp"
#include "realm_coordinator.hpp"
#include "js_types.hpp"
#if REALM_ENABLE_SYNC
#include "sync/sync_manager.hpp"
@ -61,6 +62,49 @@ void clear_test_state() {
SyncManager::shared().configure_file_system(default_realm_file_directory(), SyncManager::MetadataMode::NoEncryption);
#endif
}
std::string TypeErrorException::type_string(Property const& prop)
{
using realm::PropertyType;
std::string ret;
switch (prop.type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
ret = "number";
break;
case PropertyType::Bool:
ret = "boolean";
break;
case PropertyType::String:
ret = "string";
break;
case PropertyType::Date:
ret = "date";
break;
case PropertyType::Data:
ret = "binary";
break;
case PropertyType::LinkingObjects:
case PropertyType::Object:
ret = prop.object_type;
break;
case PropertyType::Any:
throw std::runtime_error("'Any' type is not supported");
default:
REALM_UNREACHABLE();
}
if (realm::is_nullable(prop.type)) {
ret += "?";
}
if (realm::is_array(prop.type)) {
ret += "[]";
}
return ret;
}
} // js
} // realm

View File

@ -164,22 +164,22 @@ public:
static FunctionType create_constructor(ContextType);
// methods
static void objects(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void object_for_primary_key(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void create(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_one(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_all(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void write(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void begin_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void commit_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void cancel_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void add_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void wait_for_download_completion(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void close(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void compact(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_model(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void objects(ContextType, ObjectType, Arguments, ReturnValue &);
static void object_for_primary_key(ContextType, ObjectType, Arguments, ReturnValue &);
static void create(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_one(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_all(ContextType, ObjectType, Arguments, ReturnValue &);
static void write(ContextType, ObjectType, Arguments, ReturnValue &);
static void begin_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void cancel_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void wait_for_download_completion(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
static void compact(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &);
// properties
static void get_empty(ContextType, ObjectType, ReturnValue &);
@ -197,10 +197,10 @@ public:
static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&);
static void schema_version(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void clear_test_state(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_file(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void schema_version(ContextType, ObjectType, Arguments, ReturnValue &);
static void clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &);
static void copy_bundled_realm_files(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_file(ContextType, ObjectType, Arguments, ReturnValue &);
// static properties
static void get_default_path(ContextType, ObjectType, ReturnValue &);
@ -503,7 +503,7 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
}
template<typename T>
void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::schema_version(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
realm::Realm::Config config;
@ -524,19 +524,19 @@ void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType thi
template<typename T>
void RealmClass<T>::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::clear_test_state(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
js::clear_test_state();
}
template<typename T>
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
realm::copy_bundled_realm_files();
}
template<typename T>
void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_file(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
ValueType value = args[0];
@ -567,7 +567,7 @@ void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_o
}
template<typename T>
void RealmClass<T>::delete_model(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_model(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
ValueType value = args[0];
@ -641,7 +641,7 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
#endif
template<typename T>
void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(3);
auto config_object = Value::validated_to_object(ctx, args[0]);
auto callback_function = Value::validated_to_function(ctx, args[1 + (args.count == 3)]);
@ -743,7 +743,8 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
}
ObjectType object = Object::create_empty(protected_ctx);
Object::set_property(protected_ctx, object, "message", Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error"));
Object::set_property(protected_ctx, object, "message",
Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error"));
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1));
ValueType callback_arguments[1];
@ -760,7 +761,7 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
}
template<typename T>
void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -771,7 +772,7 @@ void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_objec
}
template<typename T>
void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::object_for_primary_key(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -789,7 +790,7 @@ void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, Object
}
template<typename T>
void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::create(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(3);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -813,7 +814,7 @@ void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object
}
template<typename T>
void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_one(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -861,7 +862,7 @@ void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_ob
}
template<typename T>
void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_all(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -877,7 +878,7 @@ void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_ob
}
template<typename T>
void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::write(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -897,7 +898,7 @@ void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object,
}
template<typename T>
void RealmClass<T>::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::begin_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -905,7 +906,7 @@ void RealmClass<T>::begin_transaction(ContextType ctx, FunctionType, ObjectType
}
template<typename T>
void RealmClass<T>::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::commit_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -913,7 +914,7 @@ void RealmClass<T>::commit_transaction(ContextType ctx, FunctionType, ObjectType
}
template<typename T>
void RealmClass<T>::cancel_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::cancel_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -921,7 +922,7 @@ void RealmClass<T>::cancel_transaction(ContextType ctx, FunctionType, ObjectType
}
template<typename T>
void RealmClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
validated_notification_name(ctx, args[0]);
@ -933,7 +934,7 @@ void RealmClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_
}
template<typename T>
void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
validated_notification_name(ctx, args[0]);
@ -945,7 +946,7 @@ void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType th
}
template<typename T>
void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
if (args.count) {
validated_notification_name(ctx, args[0]);
@ -957,7 +958,7 @@ void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectTy
}
template<typename T>
void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::close(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -965,7 +966,7 @@ void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object,
}
template<typename T>
void RealmClass<T>::compact(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::compact(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);

View File

@ -106,14 +106,12 @@ typename T::Object RealmObjectClass<T>::create_instance(ContextType ctx, realm::
template<typename T>
void RealmObjectClass<T>::get_property(ContextType ctx, ObjectType object, const String &property, ReturnValue &return_value) {
try {
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
std::string name = property;
if (realm_object->get_object_schema().property_for_name(name)) {
NativeAccessor<T> accessor(ctx, realm_object->realm(), realm_object->get_object_schema());
std::string name = property;
auto result = realm_object->template get_property_value<ValueType>(accessor, name);
return_value.set(result);
} catch (InvalidPropertyException &ex) {
// getters for nonexistent properties in JS should always return undefined
}
}
@ -129,9 +127,7 @@ bool RealmObjectClass<T>::set_property(ContextType ctx, ObjectType object, const
NativeAccessor<T> accessor(ctx, realm_object->realm(), realm_object->get_object_schema());
if (!Value::is_valid_for_property(ctx, value, *prop)) {
throw TypeErrorException(realm_object->get_object_schema().name, property_name,
js_type_name_for_property_type(prop->type),
accessor.print(value));
throw TypeErrorException(accessor, realm_object->get_object_schema().name, *prop, value);
}
realm_object->set_property_value(accessor, property_name, value, true);
@ -141,12 +137,15 @@ bool RealmObjectClass<T>::set_property(ContextType ctx, ObjectType object, const
template<typename T>
std::vector<String<T>> RealmObjectClass<T>::get_property_names(ContextType ctx, ObjectType object) {
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
auto &properties = realm_object->get_object_schema().persisted_properties;
auto &object_schema = realm_object->get_object_schema();
std::vector<String> names;
names.reserve(properties.size());
names.reserve(object_schema.persisted_properties.size() + object_schema.computed_properties.size());
for (auto &prop : properties) {
for (auto &prop : object_schema.persisted_properties) {
names.push_back(prop.name);
}
for (auto &prop : object_schema.computed_properties) {
names.push_back(prop.name);
}

View File

@ -33,6 +33,10 @@ namespace js {
template<typename>
class NativeAccessor;
struct NonRealmObjectException : public std::logic_error {
NonRealmObjectException() : std::logic_error("Object is not a Realm object") { }
};
template<typename T>
class Results : public realm::Results {
public:
@ -56,29 +60,40 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
using Object = js::Object<T>;
using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>;
using Arguments = js::Arguments<T>;
static ObjectType create_instance(ContextType, realm::Results);
static ObjectType create_instance(ContextType, SharedRealm, const std::string &object_type);
template<typename U>
static ObjectType create_filtered(ContextType, const U &, size_t, const ValueType[]);
static ObjectType create_filtered(ContextType, const U &, Arguments);
static std::vector<std::pair<std::string, bool>> get_keypaths(ContextType, size_t, const ValueType[]);
static std::vector<std::pair<std::string, bool>> get_keypaths(ContextType, Arguments);
static void get_length(ContextType, ObjectType, ReturnValue &);
static void get_type(ContextType, ObjectType, ReturnValue &);
static void get_optional(ContextType, ObjectType, ReturnValue &);
static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &);
static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void snapshot(ContextType, ObjectType, Arguments, ReturnValue &);
static void filtered(ContextType, ObjectType, Arguments, ReturnValue &);
static void sorted(ContextType, ObjectType, Arguments, ReturnValue &);
static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &);
static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void index_of(ContextType, ObjectType, Arguments, ReturnValue &);
template<typename Fn>
static void index_of(ContextType, Fn&, Arguments, ReturnValue &);
// observable
static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
template<typename U>
static void add_listener(ContextType, U&, ObjectType, Arguments);
template<typename U>
static void remove_listener(ContextType, U&, ObjectType, Arguments);
std::string const name = "Results";
@ -95,6 +110,8 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
PropertyMap<T> const properties = {
{"length", {wrap<get_length>, nullptr}},
{"type", {wrap<get_type>, nullptr}},
{"optional", {wrap<get_optional>, nullptr}},
};
IndexPropertyType<T> const index_accessor = {wrap<get_index>, nullptr};
@ -116,15 +133,19 @@ typename T::Object ResultsClass<T>::create_instance(ContextType ctx, SharedRealm
template<typename T>
template<typename U>
typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &collection, size_t argc, const ValueType arguments[]) {
auto query_string = Value::validated_to_string(ctx, arguments[0], "predicate");
typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &collection, Arguments args) {
if (collection.get_type() != realm::PropertyType::Object) {
throw std::runtime_error("Filtering non-object Lists and Results is not yet implemented.");
}
auto query_string = Value::validated_to_string(ctx, args[0], "predicate");
auto query = collection.get_query();
auto const &realm = collection.get_realm();
auto const &object_schema = collection.get_object_schema();
parser::Predicate predicate = parser::parse(query_string);
NativeAccessor<T> accessor(ctx, realm, object_schema);
query_builder::ArgumentConverter<ValueType, NativeAccessor<T>> converter(accessor, &arguments[1], argc - 1);
query_builder::ArgumentConverter<ValueType, NativeAccessor<T>> converter(accessor, &args.value[1], args.count - 1);
query_builder::apply_predicate(query, predicate, converter, realm->schema(), object_schema.name);
return create_instance(ctx, collection.filter(std::move(query)));
@ -132,15 +153,18 @@ typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &co
template<typename T>
std::vector<std::pair<std::string, bool>>
ResultsClass<T>::get_keypaths(ContextType ctx, size_t argc, const ValueType arguments[]) {
validate_argument_count(argc, 1, 2);
ResultsClass<T>::get_keypaths(ContextType ctx, Arguments args) {
args.validate_maximum(2);
std::vector<std::pair<std::string, bool>> sort_order;
if (args.count == 0) {
sort_order.emplace_back("self", true);
return sort_order;
}
else if (Value::is_array(ctx, args[0])) {
validate_argument_count(args.count, 1, "Second argument is not allowed if passed an array of sort descriptors");
if (argc > 0 && Value::is_array(ctx, arguments[0])) {
validate_argument_count(argc, 1, "Second argument is not allowed if passed an array of sort descriptors");
ObjectType js_prop_names = Value::validated_to_object(ctx, arguments[0]);
ObjectType js_prop_names = Value::validated_to_object(ctx, args[0]);
size_t prop_count = Object::validated_get_length(ctx, js_prop_names);
sort_order.reserve(prop_count);
@ -158,8 +182,13 @@ ResultsClass<T>::get_keypaths(ContextType ctx, size_t argc, const ValueType argu
}
}
else {
sort_order.emplace_back(Value::validated_to_string(ctx, arguments[0]),
argc == 1 || !Value::to_boolean(ctx, arguments[1]));
if (Value::is_boolean(ctx, args[0])) {
sort_order.emplace_back("self", !Value::to_boolean(ctx, args[0]));
}
else {
sort_order.emplace_back(Value::validated_to_string(ctx, args[0]),
args.count == 1 || !Value::to_boolean(ctx, args[1]));
}
}
return sort_order;
}
@ -171,124 +200,136 @@ void ResultsClass<T>::get_length(ContextType ctx, ObjectType object, ReturnValue
}
template<typename T>
void ResultsClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
void ResultsClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(object);
auto row = results->get(index);
// Return null for deleted objects in a snapshot.
if (!row.is_attached()) {
return_value.set_null();
return;
}
auto realm_object = realm::Object(results->get_realm(), results->get_object_schema(), results->get(index));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
return_value.set(string_for_property_type(results->get_type() & ~realm::PropertyType::Flags));
}
template<typename T>
void ResultsClass<T>::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ResultsClass<T>::get_optional(ContextType, ObjectType object, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(object);
return_value.set(is_nullable(results->get_type()));
}
template<typename T>
void ResultsClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(object);
NativeAccessor<T> accessor(ctx, *results);
return_value.set(results->get(accessor, index));
}
template<typename T>
void ResultsClass<T>::snapshot(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto results = get_internal<T, ResultsClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, results->snapshot()));
}
template<typename T>
void ResultsClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ResultsClass<T>::filtered(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
return_value.set(create_filtered(ctx, *results, argc, arguments));
return_value.set(create_filtered(ctx, *results, args));
}
template<typename T>
void ResultsClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ResultsClass<T>::sorted(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, results->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
return_value.set(ResultsClass<T>::create_instance(ctx, results->sort(ResultsClass<T>::get_keypaths(ctx, args))));
}
template<typename T>
void ResultsClass<T>::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid());
}
template<typename T>
void ResultsClass<T>::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
ObjectType arg = Value::validated_to_object(ctx, arguments[0]);
if (Object::template is_instance<RealmObjectClass<T>>(ctx, arg)) {
auto object = get_internal<T, RealmObjectClass<T>>(arg);
if (!object->is_valid()) {
throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.");
}
size_t ndx;
try {
auto results = get_internal<T, ResultsClass<T>>(this_object);
ndx = results->index_of(object->row());
}
catch (realm::Results::IncorrectTableException &) {
throw std::runtime_error("Object type does not match the type contained in result");
}
if (ndx == realm::not_found) {
return_value.set(-1);
}
else {
return_value.set((uint32_t)ndx);
}
template<typename Fn>
void ResultsClass<T>::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
size_t ndx;
try {
ndx = fn(args[0]);
}
else {
catch (realm::Results::IncorrectTableException &) {
throw std::runtime_error("Object type does not match the type contained in result");
}
catch (NonRealmObjectException&) {
ndx = realm::not_found;
}
if (ndx == realm::not_found) {
return_value.set(-1);
}
else {
return_value.set((uint32_t)ndx);
}
}
template<typename T>
void ResultsClass<T>::add_listener(ContextType ctx, FunctionType, 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]);
void ResultsClass<T>::index_of(ContextType ctx, ObjectType this_object,
Arguments args, ReturnValue &return_value) {
auto fn = [&](auto&& row) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
NativeAccessor<T> accessor(ctx, *results);
return results->index_of(accessor, row);
};
index_of(ctx, fn, args, return_value);
}
template<typename T>
template<typename U>
void ResultsClass<T>::add_listener(ContextType ctx, U& collection, ObjectType this_object, Arguments args) {
args.validate_maximum(1);
auto callback = Value::validated_to_function(ctx, args[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) {
auto token = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) {
HANDLESCOPE
ValueType arguments[2];
arguments[0] = static_cast<ObjectType>(protected_this);
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
ValueType arguments[] {
static_cast<ObjectType>(protected_this),
CollectionClass<T>::create_collection_change_set(protected_ctx, change_set)
};
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
results->m_notification_tokens.emplace_back(protected_callback, std::move(token));
collection.m_notification_tokens.emplace_back(protected_callback, std::move(token));
}
template<typename T>
void ResultsClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void ResultsClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto iter = results->m_notification_tokens.begin();
typename Protected<FunctionType>::Comparator compare;
while (iter != results->m_notification_tokens.end()) {
if(compare(iter->first, protected_function)) {
iter = results->m_notification_tokens.erase(iter);
}
else {
iter++;
}
}
add_listener(ctx, *results, this_object, args);
}
template<typename T>
void ResultsClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
template<typename U>
void ResultsClass<T>::remove_listener(ContextType ctx, U& collection, ObjectType this_object, Arguments args) {
args.validate_maximum(1);
auto callback = Value::validated_to_function(ctx, args[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto& tokens = collection.m_notification_tokens;
auto compare = [&](auto&& token) {
return typename Protected<FunctionType>::Comparator()(token.first, protected_function);
};
tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end());
}
template<typename T>
void ResultsClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
remove_listener(ctx, *results, this_object, args);
}
template<typename T>
void ResultsClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto results = get_internal<T, ResultsClass<T>>(this_object);
results->m_notification_tokens.clear();
}

View File

@ -23,6 +23,8 @@
#include "js_types.hpp"
#include "schema.hpp"
#include "util/format.hpp"
namespace realm {
namespace js {
@ -41,7 +43,7 @@ struct Schema {
using ConstructorMap = std::map<std::string, Protected<FunctionType>>;
static ObjectType dict_for_property_array(ContextType, const ObjectSchema &, ObjectType);
static Property parse_property(ContextType, ValueType, std::string, ObjectDefaults &);
static Property parse_property(ContextType, ValueType, StringData, std::string, ObjectDefaults &);
static ObjectSchema parse_object_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
static realm::Schema parse_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
@ -68,8 +70,73 @@ typename T::Object Schema<T>::dict_for_property_array(ContextType ctx, const Obj
return dict;
}
static inline void parse_property_type(StringData object_name, Property& prop, StringData type)
{
using realm::PropertyType;
if (!type || !type.size()) {
throw std::logic_error(util::format("Property '%1.%2' must have a non-empty type", object_name, prop.name));
}
if (type.ends_with("[]")) {
prop.type |= PropertyType::Array;
type = type.substr(0, type.size() - 2);
}
if (type.ends_with("?")) {
prop.type |= PropertyType::Nullable;
type = type.substr(0, type.size() - 1);
}
if (type == "bool") {
prop.type |= PropertyType::Bool;
}
else if (type == "int") {
prop.type |= PropertyType::Int;
}
else if (type == "float") {
prop.type |= PropertyType::Float;
}
else if (type == "double") {
prop.type |= PropertyType::Double;
}
else if (type == "string") {
prop.type |= PropertyType::String;
}
else if (type == "date") {
prop.type |= PropertyType::Date;
}
else if (type == "data") {
prop.type |= PropertyType::Data;
}
else if (type == "list") {
if (is_nullable(prop.type)) {
throw std::logic_error(util::format("List property '%1.%2' cannot be optional", object_name, prop.name));
}
if (is_array(prop.type)) {
throw std::logic_error(util::format("List property '%1.%2' must have a non-list value type", object_name, prop.name));
}
prop.type |= PropertyType::Object | PropertyType::Array;
}
else if (type == "linkingObjects") {
prop.type |= PropertyType::LinkingObjects | PropertyType::Array;
}
else if (type == "object") {
prop.type |= PropertyType::Object;
}
else {
// The type could be the name of another object type in the same schema.
prop.type |= PropertyType::Object;
prop.object_type = type;
}
// Object properties are implicitly optional
if (prop.type == PropertyType::Object && !is_array(prop.type)) {
prop.type |= PropertyType::Nullable;
}
}
template<typename T>
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::string property_name, ObjectDefaults &object_defaults) {
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, StringData object_name,
std::string property_name, ObjectDefaults &object_defaults) {
static const String default_string = "default";
static const String indexed_string = "indexed";
static const String type_string = "type";
@ -78,79 +145,23 @@ Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::s
static const String property_string = "property";
Property prop;
prop.name = property_name;
prop.name = std::move(property_name);
ObjectType property_object = {};
std::string type;
using realm::PropertyType;
PropertyType is_optional = PropertyType::Required;
if (Value::is_object(ctx, attributes)) {
property_object = Value::validated_to_object(ctx, attributes);
type = Object::validated_get_string(ctx, property_object, type_string);
std::string property_type = Object::validated_get_string(ctx, property_object, type_string);
parse_property_type(object_name, prop, property_type);
ValueType optional_value = Object::get_property(ctx, property_object, optional_string);
if (!Value::is_undefined(ctx, optional_value) && Value::validated_to_boolean(ctx, optional_value, "optional")) {
is_optional = PropertyType::Nullable;
prop.type |= PropertyType::Nullable;
}
}
else {
type = Value::validated_to_string(ctx, attributes);
}
if (type == "bool") {
prop.type = PropertyType::Bool | is_optional;
}
else if (type == "int") {
prop.type = PropertyType::Int | is_optional;
}
else if (type == "float") {
prop.type = PropertyType::Float | is_optional;
}
else if (type == "double") {
prop.type = PropertyType::Double | is_optional;
}
else if (type == "string") {
prop.type = PropertyType::String | is_optional;
}
else if (type == "date") {
prop.type = PropertyType::Date | is_optional;
}
else if (type == "data") {
prop.type = PropertyType::Data | is_optional;
}
else if (type == "list") {
if (!Value::is_valid(property_object)) {
throw std::runtime_error("List property must specify 'objectType'");
}
prop.type = PropertyType::Object | PropertyType::Array;
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
}
else if (type == "linkingObjects") {
prop.type = PropertyType::LinkingObjects | PropertyType::Array;
if (!Value::is_valid(property_object)) {
throw std::runtime_error("Object property must specify 'objectType'");
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
prop.link_origin_property_name = Object::validated_get_string(ctx, property_object, property_string);
}
else if (type == "object") {
prop.type = PropertyType::Object | PropertyType::Nullable;
if (!Value::is_valid(property_object)) {
throw std::runtime_error("Object property must specify 'objectType'");
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
}
else {
// The type could be the name of another object type in the same schema.
prop.type = PropertyType::Object | PropertyType::Nullable;
prop.object_type = type;
}
if (Value::is_valid(property_object)) {
ValueType default_value = Object::get_property(ctx, property_object, default_string);
if (!Value::is_undefined(ctx, default_value)) {
object_defaults.emplace(prop.name, Protected<ValueType>(ctx, default_value));
@ -161,6 +172,27 @@ Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::s
prop.is_indexed = Value::validated_to_boolean(ctx, indexed_value);
}
}
else {
std::string property_type = Value::validated_to_string(ctx, attributes);
parse_property_type(object_name, prop, property_type);
}
if (prop.type == PropertyType::Object && prop.object_type.empty()) {
if (!Value::is_valid(property_object)) {
throw std::logic_error(util::format("%1 property %2.%3 must specify 'objectType'",
is_array(prop.type) ? "List" : "Object", object_name, prop.name));
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
}
if (prop.type == PropertyType::LinkingObjects) {
if (!Value::is_valid(property_object)) {
throw std::logic_error(util::format("Linking objects property %1.%2 must specify 'objectType'",
object_name, prop.name));
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
prop.link_origin_property_name = Object::validated_get_string(ctx, property_object, property_string);
}
return prop;
}
@ -188,7 +220,7 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
for (uint32_t i = 0; i < length; i++) {
ObjectType property_object = Object::validated_get_object(ctx, properties_object, i);
std::string property_name = Object::validated_get_string(ctx, property_object, name_string);
Property property = parse_property(ctx, property_object, property_name, object_defaults);
Property property = parse_property(ctx, property_object, object_schema.name, std::move(property_name), object_defaults);
if (property.type == realm::PropertyType::LinkingObjects) {
object_schema.computed_properties.emplace_back(std::move(property));
}
@ -200,9 +232,9 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
}
else {
auto property_names = Object::get_property_names(ctx, properties_object);
for (auto &property_name : property_names) {
for (auto& property_name : property_names) {
ValueType property_value = Object::get_property(ctx, properties_object, property_name);
Property property = parse_property(ctx, property_value, property_name, object_defaults);
Property property = parse_property(ctx, property_value, object_schema.name, property_name, object_defaults);
if (property.type == realm::PropertyType::LinkingObjects) {
object_schema.computed_properties.emplace_back(std::move(property));
}
@ -291,13 +323,25 @@ typename T::Object Schema<T>::object_for_property(ContextType ctx, const Propert
Object::set_property(ctx, object, name_string, Value::from_string(ctx, property.name));
static const String type_string = "type";
const std::string type = is_array(property.type) ? "list" : string_for_property_type(property.type);
Object::set_property(ctx, object, type_string, Value::from_string(ctx, type));
if (is_array(property.type)) {
if (property.type == realm::PropertyType::LinkingObjects) {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "linkingObjects"));
}
else {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "list"));
}
}
else {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, string_for_property_type(property.type)));
}
static const String object_type_string = "objectType";
if (property.object_type.size()) {
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, property.object_type));
}
else if (is_array(property.type)) {
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, string_for_property_type(property.type & ~realm::PropertyType::Flags)));
}
static const String property_string = "property";
if (property.type == realm::PropertyType::LinkingObjects) {
@ -305,14 +349,10 @@ typename T::Object Schema<T>::object_for_property(ContextType ctx, const Propert
}
static const String indexed_string = "indexed";
if (property.is_indexed) {
Object::set_property(ctx, object, indexed_string, Value::from_boolean(ctx, true));
}
Object::set_property(ctx, object, indexed_string, Value::from_boolean(ctx, property.is_indexed));
static const String optional_string = "optional";
if (is_nullable(property.type)) {
Object::set_property(ctx, object, optional_string, Value::from_boolean(ctx, true));
}
Object::set_property(ctx, object, optional_string, Value::from_boolean(ctx, is_nullable(property.type)));
return object;
}

View File

@ -65,7 +65,7 @@ struct String {
String(const char *);
String(const StringType &);
String(StringType &&);
String(const std::string &);
String(StringData);
operator StringType() const;
operator std::string() const;
@ -82,16 +82,21 @@ struct Context {
class TypeErrorException : public std::invalid_argument {
public:
TypeErrorException(StringData object_type, StringData property,
std::string const& type, std::string const& value)
: std::invalid_argument(util::format("%1.%2 must be of type '%3', got (%4)",
object_type, property, type, value))
template<typename NativeAccessor, typename ValueType>
TypeErrorException(NativeAccessor& accessor, StringData object_type,
Property const& prop, ValueType value)
: std::invalid_argument(util::format("%1.%2 must be of type '%3', got '%4' (%5)",
object_type, prop.name, type_string(prop),
accessor.typeof(value),
accessor.print(value)))
{}
TypeErrorException(const char *name, std::string const& type, std::string const& value)
: std::invalid_argument(util::format("%1 must be of type '%2', got (%3)",
name ? name : "JS value", type, value))
{}
static std::string type_string(Property const& prop);
};
template<typename T>
@ -101,6 +106,8 @@ struct Value {
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
static const char *typeof(ContextType, const ValueType &);
static bool is_array(ContextType, const ValueType &);
static bool is_array_buffer(ContextType, const ValueType &);
static bool is_array_buffer_view(ContextType, const ValueType &);
@ -117,12 +124,17 @@ struct Value {
static bool is_valid(const ValueType &);
static bool is_valid_for_property(ContextType, const ValueType&, const Property&);
static bool is_valid_for_property_type(ContextType, const ValueType&, realm::PropertyType type, StringData object_type);
static ValueType from_boolean(ContextType, bool);
static ValueType from_null(ContextType);
static ValueType from_number(ContextType, double);
static ValueType from_string(ContextType, const String<T> &);
static ValueType from_binary(ContextType, BinaryData);
static ValueType from_string(ContextType ctx, const char *s) { return s ? from_nonnull_string(ctx, s) : from_null(ctx); }
static ValueType from_string(ContextType ctx, StringData s) { return s ? from_nonnull_string(ctx, s) : from_null(ctx); }
static ValueType from_string(ContextType ctx, const std::string& s) { return from_nonnull_string(ctx, s.c_str()); }
static ValueType from_binary(ContextType ctx, BinaryData b) { return b ? from_nonnull_binary(ctx, b) : from_null(ctx); }
static ValueType from_nonnull_string(ContextType, const String<T>&);
static ValueType from_nonnull_binary(ContextType, BinaryData);
static ValueType from_undefined(ContextType);
static ObjectType to_array(ContextType, const ValueType &);
@ -135,6 +147,7 @@ struct Value {
static String<T> to_string(ContextType, const ValueType &);
static OwnedBinaryData to_binary(ContextType, ValueType);
#define VALIDATED(return_t, type) \
static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \
if (!is_##type(ctx, value)) { \
@ -351,82 +364,76 @@ REALM_JS_INLINE void set_internal(const typename T::Object &object, typename Cla
template<typename T>
inline bool Value<T>::is_valid_for_property(ContextType context, const ValueType &value, const Property& prop)
{
if (is_nullable(prop.type) && (is_null(context, value) || is_undefined(context, value))) {
return true;
}
return is_valid_for_property_type(context, value, prop.type, prop.object_type);
}
template<typename T>
inline bool Value<T>::is_valid_for_property_type(ContextType context, const ValueType &value, realm::PropertyType type, StringData object_type) {
using realm::PropertyType;
if (realm::is_array(prop.type)) {
if (prop.type != PropertyType::Object) {
return false;
}
// FIXME: Do we need to validate the types of the contained objects?
if (is_array(context, value)) {
auto check_value = [&](auto&& value) {
if (is_nullable(type) && (is_null(context, value) || is_undefined(context, value))) {
return true;
}
if (is_object(context, value)) {
auto object = to_object(context, value);
return Object<T>::template is_instance<ResultsClass<T>>(context, object)
|| Object<T>::template is_instance<ListClass<T>>(context, object);
switch (type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
return is_number(context, value);
case PropertyType::Bool:
return is_boolean(context, value);
case PropertyType::String:
return is_string(context, value);
case PropertyType::Data:
return is_binary(context, value);
case PropertyType::Date:
return is_date(context, value);
case PropertyType::Object:
return true;
case PropertyType::Any:
return false;
default:
REALM_UNREACHABLE();
}
};
auto check_collection_type = [&](auto&& list) {
auto list_type = list->get_type();
return list_type == type
&& is_nullable(list_type) == is_nullable(type)
&& (type != PropertyType::Object || list->get_object_schema().name == object_type);
};
if (!realm::is_array(type)) {
return check_value(value);
}
if (is_object(context, value)) {
auto object = to_object(context, value);
if (Object<T>::template is_instance<ResultsClass<T>>(context, object)) {
return check_collection_type(get_internal<T, ResultsClass<T>>(object));
}
if (Object<T>::template is_instance<ListClass<T>>(context, object)) {
return check_collection_type(get_internal<T, ListClass<T>>(object));
}
}
if (type == PropertyType::Object) {
// FIXME: Do we need to validate the types of the contained objects?
return is_array(context, value);
}
if (!is_array(context, value)) {
return false;
}
switch (prop.type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
return is_number(context, value);
case PropertyType::Bool:
return is_boolean(context, value);
case PropertyType::String:
return is_string(context, value);
case PropertyType::Data:
return is_binary(context, value);
case PropertyType::Date:
return is_date(context, value);
case PropertyType::Object:
return true;
case PropertyType::Any:
auto array = to_array(context, value);
uint32_t size = Object<T>::validated_get_length(context, array);
for (uint32_t i = 0; i < size; ++i) {
if (!check_value(Object<T>::get_property(context, array, i))) {
return false;
default:
REALM_UNREACHABLE();
}
}
inline std::string js_type_name_for_property_type(realm::PropertyType type)
{
using realm::PropertyType;
if (realm::is_array(type)) {
if (type == PropertyType::LinkingObjects) {
throw std::runtime_error("LinkingObjects' type is not supported");
}
return "array";
}
switch (type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
return "number";
case PropertyType::Bool:
return "boolean";
case PropertyType::String:
return "string";
case PropertyType::Date:
return "date";
case PropertyType::Data:
return "binary";
case PropertyType::Object:
return "object";
case PropertyType::Any:
throw std::runtime_error("'Any' type is not supported");
default:
REALM_UNREACHABLE();
}
return true;
}
} // js

View File

@ -300,20 +300,43 @@ inline void ObjectWrap<ClassType>::get_property_names(JSContextRef ctx, JSObject
}
}
static inline bool try_get_int(JSStringRef property, int64_t& value) {
value = 0;
auto str = JSStringGetCharactersPtr(property);
auto end = str + JSStringGetLength(property);
while (str != end && iswspace(*str)) {
++str;
}
bool negative = false;
if (str != end && *str == '-') {
negative = true;
++str;
}
while (str != end && *str >= '0' && *str <= '9') {
if (int_multiply_with_overflow_detect(value, 10)) {
return false;
}
value += *str - '0';
++str;
}
if (negative) {
value *= -1;
}
return str == end;
}
template<typename ClassType>
inline JSValueRef ObjectWrap<ClassType>::get_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) {
if (auto index_getter = s_class.index_accessor.getter) {
try {
uint32_t index = validated_positive_index(jsc::String(property));
int64_t num;
if (try_get_int(property, num)) {
uint32_t index;
if (num < 0 || util::int_cast_with_overflow_detect(num, index)) {
// Out-of-bounds index getters should just return undefined in JS.
return Value::from_undefined(ctx);
}
return index_getter(ctx, object, index, exception);
}
catch (std::out_of_range &) {
// Out-of-bounds index getters should just return undefined in JS.
return Value::from_undefined(ctx);
}
catch (std::invalid_argument &) {
// Property is not a number.
}
}
if (auto string_getter = s_class.string_accessor.getter) {
return string_getter(ctx, object, property, exception);
@ -326,24 +349,24 @@ inline bool ObjectWrap<ClassType>::set_property(JSContextRef ctx, JSObjectRef ob
auto index_setter = s_class.index_accessor.setter;
if (index_setter || s_class.index_accessor.getter) {
try {
uint32_t index = validated_positive_index(jsc::String(property));
int64_t num;
if (try_get_int(property, num)) {
if (num < 0) {
*exception = Exception::value(ctx, util::format("Index %1 cannot be less than zero.", num));
return false;
}
int32_t index;
if (util::int_cast_with_overflow_detect(num, index)) {
*exception = Exception::value(ctx, util::format("Index %1 cannot be greater than %2.",
num, std::numeric_limits<uint32_t>::max()));
return false;
}
if (index_setter) {
return index_setter(ctx, object, index, value, exception);
}
else {
*exception = Exception::value(ctx, std::string("Cannot assign to read only index ") + util::to_string(index));
return false;
}
}
catch (std::out_of_range &e) {
*exception = Exception::value(ctx, e);
*exception = Exception::value(ctx, util::format("Cannot assign to read only index %1", index));
return false;
}
catch (std::invalid_argument &) {
// Property is not a number.
}
}
if (auto string_setter = s_class.string_accessor.setter) {
return string_setter(ctx, object, property, value, exception);
@ -372,10 +395,10 @@ JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object,
}
template<jsc::ArgumentsMethodType F>
JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
JSValueRef wrap(JSContextRef ctx, JSObjectRef, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
jsc::ReturnValue return_value(ctx);
try {
F(ctx, function, this_object, jsc::Arguments{ctx, argc, arguments}, return_value);
F(ctx, this_object, jsc::Arguments{ctx, argc, arguments}, return_value);
return return_value;
}
catch (std::exception &e) {

View File

@ -38,6 +38,9 @@ class ReturnValue<jsc::Types> {
void set(const std::string &string) {
m_value = JSValueMakeString(m_context, jsc::String(string));
}
void set(const char *string) {
m_value = JSValueMakeString(m_context, jsc::String(string));
}
void set(bool boolean) {
m_value = JSValueMakeBoolean(m_context, boolean);
}

View File

@ -32,7 +32,8 @@ class String<jsc::Types> {
public:
String(const char *s) : m_str(JSStringCreateWithUTF8CString(s)) {}
String(const JSStringRef &s) : m_str(JSStringRetain(s)) {}
String(const std::string &str) : String(str.c_str()) {}
String(StringData str) : String(str.data()) {}
String(const std::string& str) : String(str.c_str()) {}
String(const StringType &o) : String(o.m_str) {}
String(StringType &&o) : m_str(o.m_str) {
o.m_str = nullptr;

View File

@ -47,7 +47,7 @@ bool jsc::Value::is_binary(JSContextRef ctx, const JSValueRef &value)
}
template<>
JSValueRef jsc::Value::from_binary(JSContextRef ctx, BinaryData data)
JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data)
{
static jsc::String s_buffer = "buffer";
static jsc::String s_uint8_array = "Uint8Array";

View File

@ -41,6 +41,18 @@ static inline bool is_object_of_type(JSContextRef ctx, JSValueRef value, jsc::St
return result;
}
template<>
inline const char *jsc::Value::typeof(JSContextRef ctx, const JSValueRef &value) {
switch (JSValueGetType(ctx, value)) {
case kJSTypeNull: return "null";
case kJSTypeNumber: return "number";
case kJSTypeObject: return "object";
case kJSTypeString: return "string";
case kJSTypeBoolean: return "boolean";
case kJSTypeUndefined: return "undefined";
}
}
template<>
inline bool jsc::Value::is_array(JSContextRef ctx, const JSValueRef &value) {
// JSValueIsArray() is not available until iOS 9.
@ -124,7 +136,7 @@ inline JSValueRef jsc::Value::from_number(JSContextRef ctx, double number) {
}
template<>
inline JSValueRef jsc::Value::from_string(JSContextRef ctx, const jsc::String &string) {
inline JSValueRef jsc::Value::from_nonnull_string(JSContextRef ctx, const jsc::String &string) {
return JSValueMakeString(ctx, string);
}
@ -134,26 +146,12 @@ inline JSValueRef jsc::Value::from_undefined(JSContextRef ctx) {
}
template<>
JSValueRef jsc::Value::from_binary(JSContextRef ctx, BinaryData data);
JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data);
template<>
inline bool jsc::Value::to_boolean(JSContextRef ctx, const JSValueRef &value) {
return JSValueToBoolean(ctx, value);
}
template<>
inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;
double number = JSValueToNumber(ctx, value, &exception);
if (exception) {
throw jsc::Exception(ctx, exception);
}
if (isnan(number)) {
throw std::invalid_argument("Value not convertible to a number.");
}
return number;
}
template<>
inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;
@ -168,6 +166,21 @@ inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef &val
return string;
}
template<>
inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;
double number = JSValueToNumber(ctx, value, &exception);
if (exception) {
throw jsc::Exception(ctx, exception);
}
if (isnan(number)) {
throw std::invalid_argument(util::format("Value '%1' not convertible to a number.",
(std::string)to_string(ctx, value)));
}
return number;
}
template<>
inline JSObjectRef jsc::Value::to_object(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;

View File

@ -317,7 +317,7 @@ void wrap(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto arguments = node::get_arguments(info);
try {
F(isolate, info.Callee(), info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value);
F(isolate, info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value);
}
catch (std::exception &e) {
Nan::ThrowError(node::Exception::value(isolate, e));

View File

@ -41,6 +41,17 @@ class ReturnValue<node::Types> {
m_value.Set(Nan::New(string).ToLocalChecked());
}
}
void set(const char *str) {
if (!str) {
m_value.SetNull();
}
else if (!*str) {
m_value.SetEmptyString();
}
else {
m_value.Set(Nan::New(str).ToLocalChecked());
}
}
void set(bool boolean) {
m_value.Set(boolean);
}

View File

@ -23,6 +23,17 @@
namespace realm {
namespace js {
template<>
inline const char *node::Value::typeof(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
if (value->IsNull()) { return "null"; }
if (value->IsNumber()) { return "number"; }
if (value->IsString()) { return "string"; }
if (value->IsBoolean()) { return "boolean"; }
if (value->IsUndefined()) { return "undefined"; }
if (value->IsObject()) { return "object"; }
return "unknown";
}
template<>
inline bool node::Value::is_array(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
return value->IsArray();
@ -110,12 +121,12 @@ inline v8::Local<v8::Value> node::Value::from_number(v8::Isolate* isolate, doubl
}
template<>
inline v8::Local<v8::Value> node::Value::from_string(v8::Isolate* isolate, const node::String &string) {
inline v8::Local<v8::Value> node::Value::from_nonnull_string(v8::Isolate* isolate, const node::String &string) {
return v8::Local<v8::String>(string);
}
template<>
inline v8::Local<v8::Value> node::Value::from_binary(v8::Isolate* isolate, BinaryData data) {
inline v8::Local<v8::Value> node::Value::from_nonnull_binary(v8::Isolate* isolate, BinaryData data) {
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, data.size());
v8::ArrayBuffer::Contents contents = buffer->GetContents();
@ -137,17 +148,18 @@ inline bool node::Value::to_boolean(v8::Isolate* isolate, const v8::Local<v8::Va
}
template<>
inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
double number = Nan::To<double>(value).FromMaybe(NAN);
if (std::isnan(number)) {
throw std::invalid_argument("Value not convertible to a number.");
}
return number;
inline node::String node::Value::to_string(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
return value->ToString();
}
template<>
inline node::String node::Value::to_string(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
return value->ToString();
inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
double number = Nan::To<double>(value).FromMaybe(NAN);
if (std::isnan(number)) {
throw std::invalid_argument(util::format("Value '%1' not convertible to a number.",
(std::string)to_string(isolate, value)));
}
return number;
}
template<>

@ -1 +1 @@
Subproject commit 30b8a7853b17762719aa7671aac6ebea04473e33
Subproject commit 49705c0fbdbff8536cfa134fc17b837db4385d31

View File

@ -34,6 +34,7 @@ using namespace realm::rpc;
using Accessor = realm::js::NativeAccessor<jsc::Types>;
namespace {
static const char * const RealmObjectTypesData = "data";
static const char * const RealmObjectTypesDate = "date";
static const char * const RealmObjectTypesDictionary = "dict";
@ -46,10 +47,40 @@ static const char * const RealmObjectTypesUser = "user";
static const char * const RealmObjectTypesSession = "session";
static const char * const RealmObjectTypesUndefined = "undefined";
static RPCServer*& get_rpc_server(JSGlobalContextRef ctx) {
json serialize_object_schema(const realm::ObjectSchema &object_schema) {
std::vector<std::string> properties;
for (auto &prop : object_schema.persisted_properties) {
properties.push_back(prop.name);
}
for (auto &prop : object_schema.computed_properties) {
properties.push_back(prop.name);
}
return {
{"name", object_schema.name},
{"properties", properties},
};
}
template<typename Container>
json get_type(Container const& c) {
auto type = c.get_type();
if (type == realm::PropertyType::Object) {
return serialize_object_schema(c.get_object_schema());
}
return {
{"type", string_for_property_type(type)},
{"optional", is_nullable(type)}
};
}
RPCServer*& get_rpc_server(JSGlobalContextRef ctx) {
static std::map<JSGlobalContextRef, RPCServer*> s_map;
return s_map[ctx];
}
}
RPCWorker::RPCWorker() {
m_thread = std::thread([this]() {
@ -144,10 +175,31 @@ RPCServer::RPCServer() {
if (!realm_constructor) {
throw std::runtime_error("Realm constructor not found!");
}
JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync");
JSObjectRef user_constructor = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, "User");
JSObjectRef create_user_method = (JSObjectRef)jsc::Object::get_property(m_context, user_constructor, "createUser");
json::array_t args = dict["arguments"];
size_t arg_count = args.size();
JSValueRef arg_values[arg_count];
for (size_t i = 0; i < arg_count; i++) {
arg_values[i] = deserialize_json_value(args[i]);
}
JSObjectRef user_object = (JSObjectRef)jsc::Function::call(m_context, create_user_method, arg_count, arg_values);
return (json){{"result", serialize_json_value(user_object)}};
};
m_requests["/_adminUser"] = [this](const json dict) {
JSObjectRef realm_constructor = m_session_id ? JSObjectRef(m_objects[m_session_id]) : NULL;
if (!realm_constructor) {
throw std::runtime_error("Realm constructor not found!");
}
JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync");
JSObjectRef user_constructor = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, "User");
JSObjectRef create_user_method = (JSObjectRef)jsc::Object::get_property(m_context, user_constructor, "_adminUser");
json::array_t args = dict["arguments"];
size_t arg_count = args.size();
@ -213,11 +265,11 @@ RPCServer::RPCServer() {
if (!realm_constructor) {
throw std::runtime_error("Realm constructor not found!");
}
JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync");
JSObjectRef user_constructor = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, "User");
JSValueRef value = jsc::Object::get_property(m_context, user_constructor, "all");
return (json){{"result", serialize_json_value(value)}};
};
m_requests["/clear_test_state"] = [this](const json dict) {
@ -360,7 +412,7 @@ json RPCServer::serialize_json_value(JSValueRef js_value) {
{"type", RealmObjectTypesList},
{"id", store_object(js_object)},
{"size", list->size()},
{"schema", serialize_object_schema(list->get_object_schema())}
{"schema", get_type(*list)},
};
}
else if (jsc::Object::is_instance<js::ResultsClass<jsc::Types>>(m_context, js_object)) {
@ -369,7 +421,7 @@ json RPCServer::serialize_json_value(JSValueRef js_value) {
{"type", RealmObjectTypesResults},
{"id", store_object(js_object)},
{"size", results->size()},
{"schema", serialize_object_schema(results->get_object_schema())}
{"schema", get_type(*results)},
};
}
else if (jsc::Object::is_instance<js::RealmClass<jsc::Types>>(m_context, js_object)) {
@ -455,23 +507,6 @@ json RPCServer::serialize_json_value(JSValueRef js_value) {
assert(0);
}
json RPCServer::serialize_object_schema(const realm::ObjectSchema &object_schema) {
std::vector<std::string> properties;
for (auto &prop : object_schema.persisted_properties) {
properties.push_back(prop.name);
}
for (auto &prop : object_schema.computed_properties) {
properties.push_back(prop.name);
}
return {
{"name", object_schema.name},
{"properties", properties},
};
}
JSValueRef RPCServer::deserialize_json_value(const json dict) {
json oid = dict["id"];
if (oid.is_number()) {

View File

@ -81,8 +81,6 @@ class RPCServer {
json serialize_json_value(JSValueRef value);
JSValueRef deserialize_json_value(const json dict);
json serialize_object_schema(const ObjectSchema &objectSchema);
};
} // rpc

View File

@ -48,6 +48,8 @@
context[@"Promise"] = promiseModule[@"Promise"];
}
context[@"global"] = [JSValue valueWithNewObjectInContext:context];
// Create Realm constructor in the JS context.
RJSInitializeInContext(context.JSGlobalContextRef);

View File

@ -19,75 +19,122 @@
'use strict';
module.exports = {
assertEqual: function(val1, val2, errorMessage) {
assertSimilar: function(type, val1, val2, errorMessage, depth) {
depth = depth || 0;
this.assertDefined(type, depth + 1);
type = type.replace('?', '');
if (val2 === null) {
this.assertNull(val1, errorMessage, depth + 1);
}
else if (type === 'float' || type === 'double') {
this.assertEqualWithTolerance(val1, val2, 0.000001, errorMessage, depth + 1);
}
else if (type === 'data') {
this.assertArraysEqual(new Uint8Array(val1), val2, errorMessage, depth + 1);
}
else if (type === 'date') {
this.assertEqual(val1 && val1.getTime(), val2.getTime(), errorMessage, depth + 1);
}
else if (type === 'object') {
for (const key of Object.keys(val1)) {
this.assertEqual(val1[key], val2[key], errorMessage, depth + 1);
}
}
else if (type === 'list') {
this.assertArraysEqual(val1, val2, errorMessage, depth + 1);
}
else {
this.assertEqual(val1, val2, errorMessage, depth + 1);
}
},
assertEqual: function(val1, val2, errorMessage, depth) {
if (val1 !== val2) {
var message = "'" + val1 + "' does not equal expected value '" + val2 + "'";
let message = `'${val1}' does not equal expected value '${val2}'`;
if (errorMessage) {
message = errorMessage + ' - ' + message;
message = `${errorMessage} - ${message}`;
}
throw new TestFailureError(message);
throw new TestFailureError(message, depth);
}
},
assertNotEqual: function(val1, val2, errorMessage) {
assertNotEqual: function(val1, val2, errorMessage, depth) {
if (val1 === val2) {
var message = "'" + val1 + "' equals '" + val2 + "'";
let message = `'${val1}' equals '${val2}'`;
if (errorMessage) {
message = errorMessage + ' - ' + message;
message = `${errorMessage} - ${message}`;
}
throw new TestFailureError(message);
throw new TestFailureError(message, depth);
}
},
assertEqualWithTolerance: function(val1, val2, tolerance, errorMessage) {
assertEqualWithTolerance: function(val1, val2, tolerance, errorMessage, depth) {
if (val1 < val2 - tolerance || val1 > val2 + tolerance) {
var message = "'" + val1 + "' does not equal '" + val2 + "' with tolerance '" + tolerance + "'";
let message = `'${val1}' does not equal '${val2}' with tolerance '${tolerance}'`;
if (errorMessage) {
message = errorMessage + ' - ' + message;
message = `${errorMessage} - ${message}`;
}
throw new TestFailureError(message);
throw new TestFailureError(message, depth);
}
},
assertArray: function(value, length, errorMessage) {
assertArray: function(value, length, errorMessage, depth) {
if (!Array.isArray(value)) {
throw new TestFailureError(errorMessage || `Value ${value} is not an array`);
throw new TestFailureError(errorMessage || `Value ${value} is not an array`, depth);
}
},
assertArrayLength: function(value, length, errorMessage) {
this.assertArray(value);
assertArrayLength: function(value, length, errorMessage, depth) {
this.assertArray(value, 1 + depth || 0);
if (value.length !== length) {
throw new TestFailureError(errorMessage || `Value ${value} is not an array of length ${length}`);
throw new TestFailureError(errorMessage || `Value ${value} is not an array of length ${length}`, depth);
}
},
assertArraysEqual: function(val1, val2, errorMessage) {
var len1 = val1.length;
var len2 = val2.length;
var message;
assertArraysEqual: function(val1, val2, errorMessage, depth) {
this.assertDefined(val1, `val1 should be non-null but is ${val1}`, 1 + (depth || 0));
this.assertDefined(val2, `val2 should be non-null but is ${val2}`, 1 + (depth || 0));
const len1 = val1.length;
const len2 = val2.length;
if (len1 !== len2) {
message = 'Arrays have different lengths (' + len1 + ' != ' + len2 + ')';
let message = `Arrays (${val1}) and (${val2}) have different lengths (${len1} != ${len2})`;
if (errorMessage) {
message = errorMessage + ' - ' + message;
message = `${errorMessage} - ${message}`;
}
throw new TestFailureError(message);
throw new TestFailureError(message, depth);
}
for (var i = 0; i < len1; i++) {
if (val1[i] !== val2[i]) {
message = 'Array contents not equal at index ' + i + ' (' + val1[i] + ' != ' + val2[i] + ')';
let compare;
if (val1.type === "data") {
compare = (i, a, b) => a === b || this.assertArraysEqual(new Uint8Array(a), b, `Data elements at index ${i}`, 1) || true;
}
else if (val1.type === "date") {
compare = (i, a, b) => (a && a.getTime()) === (b && b.getTime());
}
else if (val1.type === "float" || val1.type === "double") {
compare = (i, a, b) => a >= b - 0.000001 && a <= b + 0.000001;
}
else if (val1.type === 'object') {
compare = (i, a, b) => Object.keys(a).every(key => a[key] === b[key]);
}
else {
compare = (i, a, b) => a === b;
}
for (let i = 0; i < len1; i++) {
if (!compare(i, val1[i], val2[i])) {
let message = `Array contents not equal at index ${i} (${val1[i]} != ${val2[i]})`;
if (errorMessage) {
message = errorMessage + ' - ' + message;
message = `${errorMessage} - ${message}`;
}
throw new TestFailureError(message);
throw new TestFailureError(message, depth);
}
}
},
assertThrows: function(func, errorMessage) {
var caught = false;
assertThrows: function(func, errorMessage, depth) {
let caught = false;
try {
func();
}
@ -96,22 +143,22 @@ module.exports = {
}
if (!caught) {
throw new TestFailureError(errorMessage || 'Expected exception not thrown');
throw new TestFailureError(errorMessage || 'Expected exception not thrown', depth);
}
},
assertThrowsException: function(func, expectedException) {
var caught = false;
let caught = false;
try {
func();
}
catch (e) {
caught = true;
if (e.name !== expectedException.name) {
throw new TestFailureError('Expected a ' + expectedException.name + ' exception but caught a ' + e.name + ' instead. Message was: ' + e.message);
throw new TestFailureError(`Expected a ${expectedException.name} exception but caught a ${e.name} instead. Message was: ${e.message}`);
}
if (e.message != expectedException.message) {
throw new TestFailureError('Expected exception "' + expectedException + '" not thrown - instead caught: "' + e + '"');
throw new TestFailureError(`Expected exception "${expectedException}" not thrown - instead caught: "${e}"`);
}
}
@ -120,48 +167,60 @@ module.exports = {
}
},
assertThrowsContaining: function(func, expectedMessage) {
var caught = false;
assertThrowsContaining: function(func, expectedMessage, depth) {
let caught = false;
try {
func();
}
catch (e) {
caught = true;
if (!e.message.includes(expectedMessage)) {
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`);
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`, depth);
}
}
if (!caught) {
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`);
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`, depth);
}
},
assertTrue: function(condition, errorMessage) {
assertTrue: function(condition, errorMessage, depth) {
if (!condition) {
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be true`);
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be true`, depth);
}
},
assertInstanceOf: function(object, type, errorMessage) {
assertFalse: function(condition, errorMessage, depth) {
if (condition) {
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be false`, depth);
}
},
assertInstanceOf: function(object, type, errorMessage, depth) {
if (!(object instanceof type)) {
throw new TestFailureError(errorMessage || `Object ${object} expected to be of type ${type}`);
throw new TestFailureError(errorMessage || `Object ${object} expected to be of type ${type}`, depth);
}
},
assertType: function(value, type) {
this.assertEqual(typeof value, type, `Value ${value} expected to be of type ${type}`);
assertType: function(value, type, depth) {
this.assertEqual(typeof value, type, `Value ${value} expected to be of type ${type}`, 1 + depth || 0);
},
assertUndefined: function(value, errorMessage) {
assertDefined: function(value, errorMessage, depth) {
if (value === undefined || value === null) {
throw new TestFailureError(errorMessage || `Value ${value} expected to be non-null`, depth);
}
},
assertUndefined: function(value, errorMessage, depth) {
if (value !== undefined) {
throw new TestFailureError(errorMessage || `Value ${value} expected to be undefined`);
throw new TestFailureError(errorMessage || `Value ${value} expected to be undefined`, depth);
}
},
assertNull: function(value, errorMessage) {
assertNull: function(value, errorMessage, depth) {
if (value !== null) {
throw new TestFailureError(errorMessage || `Value ${value} expected to be null`);
throw new TestFailureError(errorMessage || `Value ${value} expected to be null`, depth);
}
},
@ -176,26 +235,28 @@ module.exports = {
},
};
function TestFailureError(message) {
var error;
function TestFailureError(message, depth) {
let error;
try {
throw new Error(message);
} catch (e) {
error = e;
}
depth = 2 + (depth || 0);
// This regular expression will match stack trace lines provided by JavaScriptCore.
// Example: someMethod@file:///path/to/file.js:10:24
var regex = /^(?:.*?@)?([^\[\(].+?):(\d+)(?::(\d+))?\s*$/;
const regex = /^(?:.*?@)?([^\[\(].+?):(\d+)(?::(\d+))?\s*$/;
// Remove the top two stack frames and use information from the third, if possible.
var stack = error.stack && error.stack.split('\n');
var match = stack[2] && stack[2].match(regex);
const stack = error.stack && error.stack.split('\n');
const match = stack[depth] && stack[depth].match(regex);
if (match) {
this.sourceURL = match[1];
this.line = +match[2];
this.column = +match[3];
this.stack = stack.slice(2).join('\n');
this.stack = stack.slice(depth).join('\n');
}
this.__proto__ = error;

View File

@ -18,34 +18,78 @@
'use strict';
var Realm = require('realm');
var TestCase = require('./asserts');
var schemas = require('./schemas');
const Realm = require('realm');
let TestCase = require('./asserts');
let schemas = require('./schemas');
const DATA1 = new Uint8Array([0x01]);
const DATA2 = new Uint8Array([0x02]);
const DATA3 = new Uint8Array([0x03]);
const DATE1 = new Date(1);
const DATE2 = new Date(2);
const DATE3 = new Date(3);
module.exports = {
testListConstructor: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
realm.write(function() {
var obj = realm.create('PersonList', {list: []});
TestCase.assertTrue(obj.list instanceof Realm.List);
TestCase.assertTrue(obj.list instanceof Realm.Collection);
realm.write(() => {
let obj = realm.create('PersonList', {list: []});
TestCase.assertInstanceOf(obj.list, Realm.List);
TestCase.assertInstanceOf(obj.list, Realm.Collection);
});
TestCase.assertThrows(function() {
new Realm.List();
TestCase.assertThrowsContaining(() => new Realm.List(), 'constructor');
TestCase.assertType(Realm.List, 'function');
TestCase.assertInstanceOf(Realm.List, Function);
},
testListType: function() {
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject, schemas.PrimitiveArrays]});
let obj, prim;
realm.write(() => {
obj = realm.create('LinkTypesObject', {});
prim = realm.create('PrimitiveArrays', {});
});
TestCase.assertEqual(typeof Realm.List, 'function');
TestCase.assertTrue(Realm.List instanceof Function);
TestCase.assertEqual(obj.arrayCol.type, 'object');
TestCase.assertEqual(obj.arrayCol1.type, 'object');
TestCase.assertEqual(prim.bool.type, 'bool');
TestCase.assertEqual(prim.int.type, 'int');
TestCase.assertEqual(prim.float.type, 'float');
TestCase.assertEqual(prim.double.type, 'double');
TestCase.assertEqual(prim.string.type, 'string');
TestCase.assertEqual(prim.date.type, 'date');
TestCase.assertEqual(prim.optBool.type, 'bool');
TestCase.assertEqual(prim.optInt.type, 'int');
TestCase.assertEqual(prim.optFloat.type, 'float');
TestCase.assertEqual(prim.optDouble.type, 'double');
TestCase.assertEqual(prim.optString.type, 'string');
TestCase.assertEqual(prim.optDate.type, 'date');
TestCase.assertFalse(prim.bool.optional);
TestCase.assertFalse(prim.int.optional);
TestCase.assertFalse(prim.float.optional);
TestCase.assertFalse(prim.double.optional);
TestCase.assertFalse(prim.string.optional);
TestCase.assertFalse(prim.date.optional);
TestCase.assertTrue(prim.optBool.optional);
TestCase.assertTrue(prim.optInt.optional);
TestCase.assertTrue(prim.optFloat.optional);
TestCase.assertTrue(prim.optDouble.optional);
TestCase.assertTrue(prim.optString.optional);
TestCase.assertTrue(prim.optDate.optional);
},
testListLength: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}],
@ -60,94 +104,286 @@ module.exports = {
obj.arrayCol = [{doubleCol: 1}, {doubleCol: 2}];
TestCase.assertEqual(array.length, 2);
TestCase.assertThrows(function() {
array.length = 0;
}, 'cannot set length property on lists');
TestCase.assertThrowsContaining(() => array.length = 0,
"Cannot assign to read only property 'length'");
});
TestCase.assertEqual(array.length, 2);
},
testListSubscriptGetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject, schemas.PrimitiveArrays]});
let obj, prim;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
arrayCol1: [{doubleCol: 5}, {doubleCol: 6}],
});
prim = realm.create('PrimitiveArrays', {
bool: [true, false],
int: [1, 2],
float: [1.1, 2.2],
double: [1.11, 2.22],
string: ['a', 'b'],
date: [new Date(1), new Date(2)],
data: [DATA1, DATA2],
array = obj.arrayCol;
optBool: [true, null],
optInt: [1, null],
optFloat: [1.1, null],
optDouble: [1.11, null],
optString: ['a', null],
optDate: [new Date(1), null],
optData: [DATA1, null],
});
});
TestCase.assertEqual(array[0].doubleCol, 3);
TestCase.assertEqual(array[1].doubleCol, 4);
TestCase.assertEqual(array[2], undefined);
TestCase.assertEqual(array[-1], undefined);
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 3);
TestCase.assertEqual(obj.arrayCol[1].doubleCol, 4);
TestCase.assertEqual(obj.arrayCol[2], undefined);
TestCase.assertEqual(obj.arrayCol[-1], undefined);
TestCase.assertEqual(obj.arrayCol['foo'], undefined);
TestCase.assertEqual(obj.arrayCol1[0].doubleCol, 5);
TestCase.assertEqual(obj.arrayCol1[1].doubleCol, 6);
TestCase.assertEqual(obj.arrayCol1[2], undefined);
TestCase.assertEqual(obj.arrayCol1[-1], undefined);
TestCase.assertEqual(obj.arrayCol1['foo'], undefined);
for (let field of Object.keys(prim)) {
TestCase.assertEqual(prim[field][2], undefined);
TestCase.assertEqual(prim[field][-1], undefined);
TestCase.assertEqual(prim[field]['foo'], undefined);
if (field.includes('opt')) {
TestCase.assertEqual(prim[field][1], null);
}
}
TestCase.assertSimilar('bool', prim.bool[0], true);
TestCase.assertSimilar('bool', prim.bool[1], false);
TestCase.assertSimilar('int', prim.int[0], 1);
TestCase.assertSimilar('int', prim.int[1], 2);
TestCase.assertSimilar('float', prim.float[0], 1.1);
TestCase.assertSimilar('float', prim.float[1], 2.2);
TestCase.assertSimilar('double', prim.double[0], 1.11);
TestCase.assertSimilar('double', prim.double[1], 2.22);
TestCase.assertSimilar('string', prim.string[0], 'a');
TestCase.assertSimilar('string', prim.string[1], 'b');
TestCase.assertSimilar('data', prim.data[0], DATA1);
TestCase.assertSimilar('data', prim.data[1], DATA2);
TestCase.assertSimilar('date', prim.date[0], new Date(1));
TestCase.assertSimilar('date', prim.date[1], new Date(2));
TestCase.assertSimilar('bool', prim.optBool[0], true);
TestCase.assertSimilar('int', prim.optInt[0], 1);
TestCase.assertSimilar('float', prim.optFloat[0], 1.1);
TestCase.assertSimilar('double', prim.optDouble[0], 1.11);
TestCase.assertSimilar('string', prim.optString[0], 'a');
TestCase.assertSimilar('data', prim.optData[0], DATA1);
TestCase.assertSimilar('date', prim.optDate[0], new Date(1));
},
testListSubscriptSetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject,
schemas.PrimitiveArrays]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
});
let prim = realm.create('PrimitiveArrays', {});
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] = {doubleCol: 1};
}, 'cannot set list item beyond its bounds');
TestCase.assertThrowsContaining(() => array[0] = null,
"JS value must be of type 'object', got (null)");
TestCase.assertThrowsContaining(() => array[0] = {},
"Missing value for property 'TestObject.doubleCol'");
TestCase.assertThrowsContaining(() => array[0] = {foo: 'bar'},
"Missing value for property 'TestObject.doubleCol'");
TestCase.assertThrowsContaining(() => array[0] = prim,
"Object of type (PrimitiveArrays) does not match List type (TestObject)");
TestCase.assertThrowsContaining(() => array[0] = array,
"Missing value for property 'TestObject.doubleCol'");
TestCase.assertThrowsContaining(() => array[2] = {doubleCol: 1},
"Requested index 2 greater than max 1");
TestCase.assertThrowsContaining(() => array[-1] = {doubleCol: 1},
"Index -1 cannot be less than zero.");
TestCase.assertThrows(function() {
array[-1] = {doubleCol: 1};
}, 'cannot set list item with negative index');
array['foo'] = 'bar';
TestCase.assertEqual(array.foo, 'bar');
function testAssign(name, v1, v2) {
prim[name].push(v1);
TestCase.assertSimilar(prim[name].type, prim[name][0], v1, undefined, 1);
prim[name][0] = v2;
TestCase.assertSimilar(prim[name].type, prim[name][0], v2, undefined, 1);
}
testAssign('bool', true, false);
testAssign('int', 1, 2);
testAssign('float', 1.1, 2.2);
testAssign('double', 1.1, 2.2);
testAssign('string', 'a', 'b');
testAssign('data', DATA1, DATA2);
testAssign('date', DATE1, DATE2);
function testAssignNull(name, expected) {
TestCase.assertThrowsContaining(() => prim[name][0] = null,
`Property must be of type '${expected}', got (null)`,
undefined, 1);
}
testAssignNull('bool', 'bool');
testAssignNull('int', 'int');
testAssignNull('float', 'float');
testAssignNull('double', 'double');
testAssignNull('string', 'string');
testAssignNull('data', 'data');
testAssignNull('date', 'date');
testAssign('optBool', true, null);
testAssign('optInt', 1, null);
testAssign('optFloat', 1.1, null);
testAssign('optDouble', 1.1, null);
testAssign('optString', 'a', null);
testAssign('optData', DATA1, null);
testAssign('optDate', DATE1, null);
});
TestCase.assertThrows(function() {
array[0] = {doubleCol: 1};
}, 'cannot set list item outside write transaction');
TestCase.assertThrowsContaining(() => array[0] = {doubleCol: 1},
"Cannot modify managed objects outside of a write transaction.");
array['foo'] = 'baz';
TestCase.assertEqual(array.foo, 'baz');
},
testListInvalidProperty: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
testListAssignment: function() {
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject,
schemas.PersonList, schemas.PersonObject,
schemas.PrimitiveArrays]});
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
});
let obj, prim;
realm.write(() => {
obj = realm.create('LinkTypesObject', {});
prim = realm.create('PrimitiveArrays', {});
let person = realm.create('PersonObject', {name: 'a', age: 2.0});
let personList = realm.create('PersonList', {list: [person]}).list;
array = obj.arrayCol;
TestCase.assertThrowsContaining(() => obj.arrayCol = [0],
"JS value must be of type 'object', got (0)");
TestCase.assertThrowsContaining(() => obj.arrayCol = [null],
"JS value must be of type 'object', got (null)");
TestCase.assertThrowsContaining(() => obj.arrayCol = [person],
"Object of type (PersonObject) does not match List type (TestObject)");
TestCase.assertThrowsContaining(() => obj.arrayCol = personList,
"LinkTypesObject.arrayCol must be of type 'TestObject[]', got 'object' (");
obj.arrayCol = [realm.create('TestObject', {doubleCol: 1.0})]
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 1.0);
obj.arrayCol = obj.arrayCol;
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 1.0);
TestCase.assertThrowsContaining(() => prim.bool = [person],
"PrimitiveArrays.bool must be of type 'boolean[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.int = [person],
"PrimitiveArrays.int must be of type 'number[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.float = [person],
"PrimitiveArrays.float must be of type 'number[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.double = [person],
"PrimitiveArrays.double must be of type 'number[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.string = [person],
"PrimitiveArrays.string must be of type 'string[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.data = [person],
"PrimitiveArrays.data must be of type 'binary[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.date = [person],
"PrimitiveArrays.date must be of type 'date[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optBool = [person],
"PrimitiveArrays.optBool must be of type 'boolean?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optInt = [person],
"PrimitiveArrays.optInt must be of type 'number?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optFloat = [person],
"PrimitiveArrays.optFloat must be of type 'number?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optDouble = [person],
"PrimitiveArrays.optDouble must be of type 'number?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optString = [person],
"PrimitiveArrays.optString must be of type 'string?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optData = [person],
"PrimitiveArrays.optData must be of type 'binary?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optDate = [person],
"PrimitiveArrays.optDate must be of type 'date?[]', got 'object' ([PersonObject{");
function testAssign(name, value) {
prim[name] = [value];
TestCase.assertSimilar(prim[name].type, prim[name][0], value, undefined, 1);
}
testAssign('bool', true);
testAssign('int', 1);
testAssign('float', 1.1);
testAssign('double', 1.1);
testAssign('string', 'a');
testAssign('data', DATA1);
testAssign('date', DATE1);
function testAssignNull(name, expected) {
TestCase.assertThrowsContaining(() => prim[name] = [null],
`PrimitiveArrays.${name} must be of type '${expected}[]', got 'object' ([null])`,
undefined, 1);
TestCase.assertEqual(prim[name].length, 1,
"List should not have been cleared by invalid assignment", 1);
}
testAssignNull('bool', 'boolean');
testAssignNull('int', 'number');
testAssignNull('float', 'number');
testAssignNull('double', 'number');
testAssignNull('string', 'string');
testAssignNull('data', 'binary');
testAssignNull('date', 'date');
testAssign('optBool', true);
testAssign('optInt', 1);
testAssign('optFloat', 1.1);
testAssign('optDouble', 1.1);
testAssign('optString', 'a');
testAssign('optData', DATA1);
testAssign('optDate', DATE1);
testAssign('optBool', null);
testAssign('optInt', null);
testAssign('optFloat', null);
testAssign('optDouble', null);
testAssign('optString', null);
testAssign('optData', null);
testAssign('optDate', null);
});
TestCase.assertEqual(undefined, array.ablasdf);
TestCase.assertThrowsContaining(() => obj.arrayCol = [],
"Cannot modify managed objects outside of a write transaction.");
TestCase.assertThrowsContaining(() => prim.bool = [],
"Cannot modify managed objects outside of a write transaction.");
},
testListEnumerate: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var obj;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let obj;
realm.write(function() {
realm.write(() => {
obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
@ -155,19 +391,18 @@ module.exports = {
});
});
var index;
for (index in obj.arrayCol) {
for (const index in obj.arrayCol) {
TestCase.assertTrue(false, "No objects should have been enumerated: " + index);
}
realm.write(function() {
realm.write(() => {
obj.arrayCol = [{doubleCol: 0}, {doubleCol: 1}];
TestCase.assertEqual(obj.arrayCol.length, 2);
});
TestCase.assertEqual(obj.arrayCol.length, 2);
var count = 0;
var keys = Object.keys(obj.arrayCol);
for (index in obj.arrayCol) {
let count = 0;
let keys = Object.keys(obj.arrayCol);
for (const index in obj.arrayCol) {
TestCase.assertEqual(count++, +index);
TestCase.assertEqual(keys[index], index);
}
@ -177,11 +412,11 @@ module.exports = {
},
testListPush: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}],
@ -199,23 +434,22 @@ module.exports = {
TestCase.assertEqual(array[2].doubleCol, 1);
TestCase.assertEqual(array[3].doubleCol, 2);
TestCase.assertThrows(function() {
array.push();
});
TestCase.assertEqual(array.push(), 4);
TestCase.assertEqual(array.length, 4);
});
TestCase.assertEqual(array.length, 4);
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.push([1]);
}, 'can only push in a write transaction');
}, "Cannot modify managed objects outside of a write transaction.");
},
testListPop: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
@ -228,22 +462,19 @@ module.exports = {
TestCase.assertEqual(array.pop(), undefined);
TestCase.assertThrows(function() {
array.pop(1);
});
TestCase.assertThrowsContaining(() => array.pop(1), 'Invalid argument');
});
TestCase.assertThrows(function() {
array.pop();
}, 'can only pop in a write transaction');
TestCase.assertThrowsContaining(() => array.pop(),
"Cannot modify managed objects outside of a write transaction.");
},
testListUnshift: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}],
@ -260,20 +491,22 @@ module.exports = {
TestCase.assertEqual(array.length, 4);
TestCase.assertEqual(array[0].doubleCol, 1);
TestCase.assertEqual(array[1].doubleCol, 2);
TestCase.assertEqual(array.unshift(), 4);
TestCase.assertEqual(array.length, 4);
});
TestCase.assertEqual(array.length, 4);
TestCase.assertThrows(function() {
array.unshift({doubleCol: 1});
}, 'can only unshift in a write transaction');
TestCase.assertThrowsContaining(() => array.unshift({doubleCol: 1}),
'Cannot modify managed objects outside of a write transaction.');
},
testListShift: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
@ -286,29 +519,27 @@ module.exports = {
TestCase.assertEqual(array.shift(), undefined);
TestCase.assertThrows(function() {
array.shift(1);
});
TestCase.assertThrowsContaining(() => array.shift(1), 'Invalid argument');
});
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.shift();
}, 'can only shift in a write transaction');
}, "Cannot modify managed objects outside of a write transaction.");
},
testListSplice: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
});
array = obj.arrayCol;
var removed;
let removed;
removed = array.splice(0, 0, obj.objectCol, obj.objectCol1);
TestCase.assertEqual(removed.length, 0);
@ -355,26 +586,26 @@ module.exports = {
TestCase.assertEqual(removed.length, 1);
TestCase.assertEqual(array.length, 0);
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.splice('cat', 1);
});
}, "Value 'cat' not convertible to a number");
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.splice(0, 0, 0);
});
}, "JS value must be of type 'object', got (0)");
});
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.splice(0, 0, {doubleCol: 1});
}, 'can only splice in a write transaction');
}, "Cannot modify managed objects outside of a write transaction");
},
testListDeletions: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var object;
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let object;
let array;
realm.write(function() {
realm.write(() => {
object = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
@ -385,7 +616,7 @@ module.exports = {
});
try {
realm.write(function() {
realm.write(() => {
realm.delete(array[0]);
TestCase.assertEqual(array.length, 1);
TestCase.assertEqual(array[0].doubleCol, 4);
@ -398,22 +629,20 @@ module.exports = {
TestCase.assertEqual(array.length, 2);
TestCase.assertEqual(array[0].doubleCol, 3);
realm.write(function() {
realm.write(() => {
realm.delete(object);
});
TestCase.assertThrows(function() {
array[0];
});
TestCase.assertThrowsContaining(() => array[0], 'invalidated');
},
testLiveUpdatingResults: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let objects = realm.objects('TestObject');
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
@ -426,7 +655,7 @@ module.exports = {
TestCase.assertEqual(objects.length, 4);
try {
realm.write(function() {
realm.write(() => {
array.push({doubleCol: 5});
TestCase.assertEqual(objects.length, 5);
@ -449,50 +678,50 @@ module.exports = {
},
testListSnapshot: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let objects = realm.objects('TestObject');
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]);
realm.write(() => {
let obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]], [[5], [6]]]);
array = obj.arrayCol;
});
var objectsCopy = objects.snapshot();
var arrayCopy = array.snapshot();
let objectsCopy = objects.snapshot();
let arrayCopy = array.snapshot();
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
realm.write(function() {
realm.write(() => {
array.push([5]);
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
TestCase.assertEqual(objectsCopy.snapshot().length, 4);
TestCase.assertEqual(objectsCopy.snapshot().length, 6);
TestCase.assertEqual(arrayCopy.snapshot().length, 2);
TestCase.assertEqual(objects.snapshot().length, 5);
TestCase.assertEqual(objects.snapshot().length, 7);
TestCase.assertEqual(array.snapshot().length, 3);
realm.delete(array[0]);
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
TestCase.assertEqual(arrayCopy[0], null);
realm.deleteAll();
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
TestCase.assertEqual(arrayCopy[1], null);
});
},
testListFiltered: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
var list;
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
let list;
realm.write(function() {
var object = realm.create('PersonList', {list: [
realm.write(() => {
let object = realm.create('PersonList', {list: [
{name: 'Ari', age: 10},
{name: 'Tim', age: 11},
{name: 'Bjarne', age: 12},
@ -515,10 +744,11 @@ module.exports = {
{name: 'Target', properties: {value: 'int'}},
{name: 'Mid', properties: {value: 'int', link: 'Target'}},
{name: 'List', properties: {list: {type: 'list', objectType: 'Mid'}}},
schemas.PrimitiveArrays
];
const realm = new Realm({schema: schema});
let list;
let list, prim;
realm.write(() => {
list = realm.create('List', {list: [
{value: 3, link: {value: 1}},
@ -528,13 +758,31 @@ module.exports = {
realm.create('List', {list: [
{value: 4, link: {value: 4}},
]});
prim = realm.create('PrimitiveArrays', {
bool: [true, false],
int: [3, 1, 2],
float: [3, 1, 2],
double: [3, 1, 2],
string: ['c', 'a', 'b'],
data: [DATA3, DATA1, DATA2],
date: [DATE3, DATE1, DATE2],
optBool: [true, false, null],
optInt: [3, 1, 2, null],
optFloat: [3, 1, 2, null],
optDouble: [3, 1, 2, null],
optString: ['c', 'a', 'b', null],
optData: [DATA3, DATA1, DATA2, null],
optDate: [DATE3, DATE1, DATE2, null],
});
});
const values = (results) => results.map((o) => o.value);
TestCase.assertThrows(() => list.sorted());
TestCase.assertThrows(() => list.sorted('nonexistent property'));
TestCase.assertThrows(() => list.sorted('link'));
// TestCase.assertThrowsContaining(() => list.sorted());
TestCase.assertThrowsContaining(() => list.sorted('nonexistent property'),
"Cannot sort on key path 'nonexistent property': property 'Mid.nonexistent property' does not exist.");
TestCase.assertThrowsContaining(() => list.sorted('link'),
"Cannot sort on key path 'link': property 'Mid.link' of type 'object' cannot be the final property in the key path.");
TestCase.assertArraysEqual(values(list.sorted([])), [3, 1, 2]);
@ -551,24 +799,50 @@ module.exports = {
TestCase.assertArraysEqual(values(list.sorted(['link.value'])), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted([['link.value', false]])), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted([['link.value', true]])), [1, 2, 3]);
TestCase.assertThrowsContaining(() => prim.int.sorted('value', true),
"Cannot sort on key path 'value': arrays of 'int' can only be sorted on 'self'");
TestCase.assertThrowsContaining(() => prim.int.sorted('!ARRAY_VALUE', true),
"Cannot sort on key path '!ARRAY_VALUE': arrays of 'int' can only be sorted on 'self'");
TestCase.assertArraysEqual(prim.int.sorted([]), [3, 1, 2]);
TestCase.assertArraysEqual(prim.int.sorted(), [1, 2, 3]);
TestCase.assertArraysEqual(prim.int.sorted(false), [1, 2, 3]);
TestCase.assertArraysEqual(prim.int.sorted(true), [3, 2, 1]);
TestCase.assertArraysEqual(prim.optInt.sorted([]), [3, 1, 2, null]);
TestCase.assertArraysEqual(prim.optInt.sorted(), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optInt.sorted(false), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optInt.sorted(true), [3, 2, 1, null]);
TestCase.assertArraysEqual(prim.bool.sorted(), [false, true]);
TestCase.assertArraysEqual(prim.float.sorted(), [1, 2, 3]);
TestCase.assertArraysEqual(prim.double.sorted(), [1, 2, 3]);
TestCase.assertArraysEqual(prim.string.sorted(), ['a', 'b', 'c']);
TestCase.assertArraysEqual(prim.data.sorted(), [DATA1, DATA2, DATA3]);
TestCase.assertArraysEqual(prim.date.sorted(), [DATE1, DATE2, DATE3]);
TestCase.assertArraysEqual(prim.optBool.sorted(), [null, false, true]);
TestCase.assertArraysEqual(prim.optFloat.sorted(), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optDouble.sorted(), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optString.sorted(), [null, 'a', 'b', 'c']);
TestCase.assertArraysEqual(prim.optData.sorted(), [null, DATA1, DATA2, DATA3]);
TestCase.assertArraysEqual(prim.optDate.sorted(), [null, DATE1, DATE2, DATE3]);
},
testArrayMethods: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
var object;
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList, schemas.PrimitiveArrays]});
let object, prim;
realm.write(function() {
realm.write(() => {
object = realm.create('PersonList', {list: [
{name: 'Ari', age: 10},
{name: 'Tim', age: 11},
{name: 'Bjarne', age: 12},
]});
prim = realm.create('PrimitiveArrays', {int: [10, 11, 12]});
});
[
object.list,
realm.objects('PersonObject'),
].forEach(function(list) {
for (const list of [object.list, realm.objects('PersonObject')]) {
TestCase.assertEqual(list.slice().length, 3);
TestCase.assertEqual(list.slice(-1).length, 1);
TestCase.assertEqual(list.slice(-1)[0].age, 12);
@ -581,43 +855,43 @@ module.exports = {
TestCase.assertEqual(list.join(' '), 'Ari Tim Bjarne');
}
var count = 0;
list.forEach(function(p, i) {
let count = 0;
list.forEach((p, i) => {
TestCase.assertEqual(p.name, list[i].name);
count++;
});
TestCase.assertEqual(count, list.length);
TestCase.assertArraysEqual(list.map(function(p) {return p.age}), [10, 11, 12]);
TestCase.assertTrue(list.some(function(p) {return p.age > 10}));
TestCase.assertTrue(list.every(function(p) {return p.age > 0}));
TestCase.assertArraysEqual(list.map(p => p.age), [10, 11, 12]);
TestCase.assertTrue(list.some(p => p.age > 10));
TestCase.assertTrue(list.every(p => p.age > 0));
var person = list.find(function(p) {return p.name == 'Tim'});
let person = list.find(p => p.name == 'Tim');
TestCase.assertEqual(person.name, 'Tim');
var index = list.findIndex(function(p) {return p.name == 'Tim'});
let index = list.findIndex(p => p.name == 'Tim');
TestCase.assertEqual(index, 1);
TestCase.assertEqual(list.indexOf(list[index]), index);
TestCase.assertEqual(list.reduce(function(n, p) {return n + p.age}, 0), 33);
TestCase.assertEqual(list.reduceRight(function(n, p) {return n + p.age}, 0), 33);
TestCase.assertEqual(list.reduce((n, p) => n + p.age, 0), 33);
TestCase.assertEqual(list.reduceRight((n, p) => n + p.age, 0), 33);
// eslint-disable-next-line no-undef
var iteratorMethodNames = ['entries', 'keys', 'values'];
let iteratorMethodNames = ['entries', 'keys', 'values'];
iteratorMethodNames.push(Symbol.iterator);
iteratorMethodNames.forEach(function(methodName) {
var iterator = list[methodName]();
var count = 0;
var result;
iteratorMethodNames.forEach(methodName => {
let iterator = list[methodName]();
let count = 0;
let result;
// This iterator should itself be iterable.
// TestCase.assertEqual(iterator[iteratorSymbol](), iterator);
TestCase.assertEqual(iterator[Symbol.iterator](), iterator);
while ((result = iterator.next()) && !result.done) {
var value = result.value;
let value = result.value;
switch (methodName) {
case 'entries':
@ -640,14 +914,83 @@ module.exports = {
TestCase.assertEqual(result.value, undefined);
TestCase.assertEqual(count, list.length);
});
}
const list = prim.int;
TestCase.assertEqual(list.slice().length, 3);
TestCase.assertEqual(list.slice(-1).length, 1);
TestCase.assertEqual(list.slice(-1)[0], 12);
TestCase.assertEqual(list.slice(1, 3).length, 2);
TestCase.assertEqual(list.slice(1, 3)[1], 12);
TestCase.assertEqual(list.join(' '), '10 11 12');
let count = 0;
list.forEach((v, i) => {
TestCase.assertEqual(v, i + 10);
count++;
});
TestCase.assertEqual(count, list.length);
TestCase.assertArraysEqual(list.map(p => p + 1), [11, 12, 13]);
TestCase.assertTrue(list.some(p => p > 10));
TestCase.assertTrue(list.every(p => p > 0));
let value = list.find(p => p == 11);
TestCase.assertEqual(value, 11)
let index = list.findIndex(p => p == 11);
TestCase.assertEqual(index, 1);
TestCase.assertEqual(list.indexOf(list[index]), index);
TestCase.assertEqual(list.reduce((n, p) => n + p, 0), 33);
TestCase.assertEqual(list.reduceRight((n, p) => n + p, 0), 33);
// eslint-disable-next-line no-undef
let iteratorMethodNames = ['entries', 'keys', 'values'];
iteratorMethodNames.push(Symbol.iterator);
iteratorMethodNames.forEach(methodName => {
let iterator = list[methodName]();
let count = 0;
let result;
// This iterator should itself be iterable.
// TestCase.assertEqual(iterator[iteratorSymbol](), iterator);
TestCase.assertEqual(iterator[Symbol.iterator](), iterator);
while ((result = iterator.next()) && !result.done) {
let value = result.value;
switch (methodName) {
case 'entries':
TestCase.assertEqual(value.length, 2);
TestCase.assertEqual(value[0], count);
TestCase.assertEqual(value[1], list[count]);
break;
case 'keys':
TestCase.assertEqual(value, count);
break;
default:
TestCase.assertEqual(value.name, list[count].name);
break;
}
count++;
}
TestCase.assertEqual(result.done, true);
TestCase.assertEqual(result.value, undefined);
TestCase.assertEqual(count, list.length);
});
},
testIsValid: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
var object;
var list;
realm.write(function() {
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
let object;
let list;
realm.write(() => {
object = realm.create('PersonList', {list: [
{name: 'Ari', age: 10},
{name: 'Tim', age: 11},
@ -659,8 +1002,6 @@ module.exports = {
});
TestCase.assertEqual(list.isValid(), false);
TestCase.assertThrows(function() {
list.length;
});
TestCase.assertThrowsContaining(() => list.length, 'invalidated');
},
};

View File

@ -88,8 +88,8 @@ module.exports = {
renamed: 'string',
prop1: 'int',
}
}],
schemaVersion: 1,
}],
schemaVersion: 1,
migration: function(oldRealm, newRealm) {
var oldObjects = oldRealm.objects('TestObject');
var newObjects = newRealm.objects('TestObject');
@ -136,8 +136,8 @@ module.exports = {
renamed: 'string',
prop1: 'int',
}
}],
schemaVersion: 1,
}],
schemaVersion: 1,
migration: function(oldRealm, newRealm) {
var oldSchema = oldRealm.schema;
var newSchema = newRealm.schema;
@ -324,4 +324,44 @@ module.exports = {
realm.close();
},
testMigrateToListOfInts: function() {
let realm = new Realm({schema: [{name: 'TestObject', properties: {values: 'IntObject[]'}},
{name: 'IntObject', properties: {value: 'int'}}]});
realm.write(function() {
realm.create('TestObject', {values: [{value: 1}, {value: 2}, {value: 3}]});
realm.create('TestObject', {values: [{value: 1}, {value: 4}, {value: 5}]});
});
realm.close();
realm = new Realm({
schema: [{name: 'TestObject', properties: {values: 'int[]'}}],
schemaVersion: 1,
migration: function(oldRealm, newRealm) {
const oldObjects = oldRealm.objects('TestObject');
const newObjects = newRealm.objects('TestObject');
TestCase.assertEqual(oldObjects.length, 2);
TestCase.assertEqual(newObjects.length, 2);
for (let i = 0; i < oldObjects.length; ++i) {
TestCase.assertEqual(oldObjects[i].values.length, 3);
TestCase.assertEqual(newObjects[i].values.length, 0);
newObjects[i].values = oldObjects[i].values.map(o => o.value);
TestCase.assertEqual(newObjects[i].values.length, 3);
}
newRealm.deleteModel('IntObject');
}
});
const objects = realm.objects('TestObject');
TestCase.assertEqual(objects.length, 2);
TestCase.assertEqual(objects[0].values.length, 3);
TestCase.assertEqual(objects[1].values.length, 3);
TestCase.assertEqual(objects[0].values[0], 1);
TestCase.assertEqual(objects[0].values[1], 2);
TestCase.assertEqual(objects[0].values[2], 3);
TestCase.assertEqual(objects[1].values[0], 1);
TestCase.assertEqual(objects[1].values[1], 4);
TestCase.assertEqual(objects[1].values[2], 5);
},
};

View File

@ -18,238 +18,158 @@
'use strict';
var Realm = require('realm');
var TestCase = require('./asserts');
var schemas = require('./schemas');
const Realm = require('realm');
const TestCase = require('./asserts');
const schemas = require('./schemas');
var RANDOM_DATA = new Uint8Array([
const RANDOM_DATA = new Uint8Array([
0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9,
0x67, 0x1e, 0x40, 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff,
]);
module.exports = {
testBasicTypesPropertyGetters: function() {
var realm = new Realm({schema: [schemas.BasicTypes]});
var object;
const allTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
objectCol: {doubleCol: 2.2},
var basicTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
};
optBoolCol: true,
optIntCol: 1,
optFloatCol: 1.1,
optDoubleCol: 1.11,
optStringCol: 'string',
optDateCol: new Date(1),
optDataCol: RANDOM_DATA,
boolArrayCol: [true],
intArrayCol: [1],
floatArrayCol: [1.1],
doubleArrayCol: [1.11],
stringArrayCol: ['string'],
dateArrayCol: [new Date(1)],
dataArrayCol: [RANDOM_DATA],
objectArrayCol: [{doubleCol: 2.2}],
optBoolArrayCol: [true],
optIntArrayCol: [1],
optFloatArrayCol: [1.1],
optDoubleArrayCol: [1.11],
optStringArrayCol: ['string'],
optDateArrayCol: [new Date(1)],
optDataArrayCol: [RANDOM_DATA],
};
const nullPropertyValues = (() => {
let values = {}
for (let name in allTypesValues) {
if (name.includes('opt')) {
values[name] = name.includes('Array') ? [null] : null;
}
else {
values[name] = allTypesValues[name];
}
}
return values;
})();
module.exports = {
testAllPropertyGetters: function() {
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
let object, nullObject;
realm.write(function() {
object = realm.create('BasicTypesObject', basicTypesValues);
object = realm.create('AllTypesObject', allTypesValues);
nullObject = realm.create('AllTypesObject', nullPropertyValues);
});
for (var name in schemas.BasicTypes.properties) {
var prop = schemas.BasicTypes.properties[name];
var type = typeof prop == 'object' ? prop.type : prop;
const objectSchema = realm.schema[0];
for (const name of Object.keys(objectSchema.properties)) {
const type = objectSchema.properties[name].type;
if (type === 'linkingObjects') {
TestCase.assertEqual(object[name].length, 0);
TestCase.assertEqual(nullObject[name].length, 0);
continue;
}
if (type == 'float' || type == 'double') {
TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001);
}
else if (type == 'data') {
TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA);
}
else if (type == 'date') {
TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime());
}
else {
TestCase.assertEqual(object[name], basicTypesValues[name]);
}
TestCase.assertSimilar(type, object[name], allTypesValues[name]);
TestCase.assertSimilar(type, nullObject[name], nullPropertyValues[name]);
}
TestCase.assertEqual(object.nonexistent, undefined);
},
testNullableBasicTypesPropertyGetters: function() {
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
var object, nullObject;
var basicTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
};
testAllTypesPropertySetters: function() {
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
let obj;
realm.write(function() {
object = realm.create('NullableBasicTypesObject', basicTypesValues);
nullObject = realm.create('NullableBasicTypesObject', {
boolCol: null,
intCol: null,
floatCol: null,
doubleCol: null,
stringCol: null,
dateCol: null,
dataCol: null,
});
obj = realm.create('AllTypesObject', allTypesValues);
});
for (var name in schemas.BasicTypes.properties) {
var prop = schemas.BasicTypes.properties[name];
var type = typeof prop == 'object' ? prop.type : prop;
TestCase.assertEqual(nullObject[name], null);
if (type == 'float' || type == 'double') {
TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001);
}
else if (type == 'data') {
TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA);
}
else if (type == 'date') {
TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime());
}
else {
TestCase.assertEqual(object[name], basicTypesValues[name]);
}
}
},
testBasicTypesPropertySetters: function() {
var realm = new Realm({schema: [schemas.BasicTypes]});
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);
TestCase.assertThrows(function() {
obj.boolCol = false;
obj.intCol = 2;
obj.floatCol = 2.2;
obj.doubleCol = 2.22;
obj.stringCol = 'STRING';
obj.dateCol = new Date(2);
obj.dataCol = RANDOM_DATA;
});
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.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');
realm.write(function() {
TestCase.assertThrows(function() {
obj.boolCol = 'cat';
});
TestCase.assertThrows(function() {
obj.intCol = 'dog';
});
TestCase.assertThrows(function() {
obj.boolCol = null;
});
TestCase.assertThrows(function() {
obj.boolCol = undefined;
});
TestCase.assertThrows(function() {
obj.intCol = null;
});
TestCase.assertThrows(function() {
obj.intCol = undefined;
});
TestCase.assertThrows(function() {
obj.floatCol = null;
});
TestCase.assertThrows(function() {
obj.floatCol = undefined;
});
TestCase.assertThrows(function() {
obj.doubleCol = null;
});
TestCase.assertThrows(function() {
obj.doubleCol = undefined;
});
TestCase.assertThrows(function() {
obj.stringCol = null;
});
TestCase.assertThrows(function() {
obj.stringCol = undefined;
});
TestCase.assertThrows(function() {
obj.dateCol = null;
});
TestCase.assertThrows(function() {
obj.dateCol = undefined;
});
TestCase.assertThrows(function() {
obj.dataCol = null;
});
TestCase.assertThrows(function() {
obj.dataCol = undefined;
});
});
TestCase.assertThrows(function() {
obj.boolCol = true;
}, 'can only set property values in a write transaction');
TestCase.assertEqual(obj.boolCol, false, 'bool value changed outside transaction');
},
testNullableBasicTypesPropertySetters: function() {
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,
};
TestCase.assertEqual(obj.boolCol, true, 'bool value changed outside transaction');
realm.write(function() {
obj = realm.create('NullableBasicTypesObject', basicTypesValues);
obj1 = realm.create('NullableBasicTypesObject', basicTypesValues);
TestCase.assertThrows(() => obj.boolCol = 'cat');
TestCase.assertThrows(() => obj.intCol = 'dog');
for (var name in schemas.NullableBasicTypes.properties) {
obj[name] = null;
obj1[name] = undefined;
// Non-optional properties should complain about null
for (const name of ['boolCol', 'intCol', 'floatCol', 'doubleCol', 'stringCol', 'dataCol', 'dateCol']) {
TestCase.assertThrows(() => obj[name] = null, `Setting ${name} to null should throw`);
TestCase.assertThrows(() => obj[name] = undefined, `Setting ${name} to undefined should throw`);
}
// Optional properties should allow it
for (const name of ['optBoolCol', 'optIntCol', 'optFloatCol', 'optDoubleCol',
'optStringCol', 'optDataCol', 'optDateCol', 'objectCol']) {
obj[name] = null;
TestCase.assertEqual(obj[name], null);
obj[name] = undefined;
TestCase.assertEqual(obj[name], null);
}
function tryAssign(name, value) {
var prop = schemas.AllTypes.properties[name];
var type = typeof prop == 'object' ? prop.type : prop;
obj[name] = value;
TestCase.assertSimilar(type, obj[name], value, undefined, 1);
}
tryAssign('boolCol', false);
tryAssign('intCol', 10);
tryAssign('floatCol', 2.2);
tryAssign('doubleCol', 3.3);
tryAssign('stringCol', 'new str');
tryAssign('dateCol', new Date(2));
tryAssign('dataCol', RANDOM_DATA);
tryAssign('optBoolCol', null);
tryAssign('optIntCol', null);
tryAssign('optFloatCol', null);
tryAssign('optDoubleCol', null);
tryAssign('optStringCol', null);
tryAssign('optDateCol', null);
tryAssign('optDataCol', null);
tryAssign('optBoolCol', false);
tryAssign('optIntCol', 10);
tryAssign('optFloatCol', 2.2);
tryAssign('optDoubleCol', 3.3);
tryAssign('optStringCol', 'new str');
tryAssign('optDateCol', new Date(2));
tryAssign('optDataCol', RANDOM_DATA);
});
for (var name in schemas.NullableBasicTypes.properties) {
TestCase.assertEqual(obj[name], null);
TestCase.assertEqual(obj1[name], null);
}
realm.write(function() {
TestCase.assertThrows(function() {
obj.boolCol = 'cat';
});
TestCase.assertThrows(function() {
obj.intCol = 'dog';
});
});
TestCase.assertThrows(function() {
obj.boolCol = null;
}, 'can only set property values in a write transaction');
},
testLinkTypesPropertyGetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var obj = null;
realm.write(function() {
@ -273,8 +193,9 @@ module.exports = {
TestCase.assertEqual(arrayVal.length, 1);
TestCase.assertEqual(arrayVal[0].doubleCol, 3);
},
testLinkTypesPropertySetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var obj;
@ -340,33 +261,25 @@ module.exports = {
});
TestCase.assertEqual(obj.objectCol.doubleCol, 3);
},
testEnumerablePropertyNames: function() {
var realm = new Realm({schema: [schemas.BasicTypes]});
var object;
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
let object;
realm.write(function() {
object = realm.create('BasicTypesObject', {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
});
});
realm.write(() => object = realm.create('AllTypesObject', allTypesValues));
var propNames = Object.keys(schemas.BasicTypes.properties);
const propNames = Object.keys(schemas.AllTypes.properties);
TestCase.assertArraysEqual(Object.keys(object), propNames, 'Object.keys');
for (var key in object) {
for (let key in object) {
TestCase.assertEqual(key, propNames.shift());
}
TestCase.assertEqual(propNames.length, 0);
},
testDataProperties: function() {
var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]});
const realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]});
var object;
// Should be be able to set a data property with a typed array.
@ -444,7 +357,7 @@ module.exports = {
},
testObjectConstructor: function() {
var realm = new Realm({schema: [schemas.TestObject]});
const realm = new Realm({schema: [schemas.TestObject]});
realm.write(function() {
var obj = realm.create('TestObject', {doubleCol: 1});
@ -456,7 +369,7 @@ module.exports = {
},
testIsValid: function() {
var realm = new Realm({schema: [schemas.TestObject]});
const realm = new Realm({schema: [schemas.TestObject]});
var obj;
realm.write(function() {
obj = realm.create('TestObject', {doubleCol: 1});
@ -470,9 +383,9 @@ module.exports = {
obj.doubleCol;
});
},
testObjectSchema: function() {
var realm = new Realm({schema: [schemas.TestObject]});
const realm = new Realm({schema: [schemas.TestObject]});
var obj;
realm.write(function() {
obj = realm.create('TestObject', {doubleCol: 1});
@ -515,7 +428,7 @@ module.exports = {
TestCase.assertEqual(realm_v3.objects('Date')[0].nullDate.getTime(), 1462500087955);
TestCase.assertEqual(realm_v3.objects('Date')[1].currentDate.getTime(), -10000);
TestCase.assertEqual(realm_v3.objects('Date')[1].nullDate, null);
// test different dates
var realm = new Realm({schema: [schemas.DateObject]});
realm.write(function() {

View File

@ -95,7 +95,8 @@ module.exports = {
},
testRealmConstructorSchemaValidation: function() {
TestCase.assertThrowsContaining(() => new Realm({schema: schemas.AllTypes}), "schema must be of type 'array', got");
TestCase.assertThrowsContaining(() => new Realm({schema: schemas.AllTypes}),
"schema must be of type 'array', got");
TestCase.assertThrowsContaining(() => new Realm({schema: ['SomeType']}),
"Failed to read ObjectSchema: JS value must be of type 'object', got (SomeType)");
TestCase.assertThrowsContaining(() => new Realm({schema: [{}]}),
@ -105,26 +106,21 @@ module.exports = {
TestCase.assertThrowsContaining(() => new Realm({schema: [{properties: {intCol: 'int'}}]}),
"Failed to read ObjectSchema: name must be of type 'string', got (undefined)");
// linkingObjects property where the source property is missing
TestCase.assertThrowsContaining(() => {
new Realm({schema: [{
name: 'InvalidObject',
properties: {
linkingObjects: {type:'linkingObjects', objectType: 'InvalidObject', property: 'nosuchproperty'}
}
}]});
}, "Property 'InvalidObject.nosuchproperty' declared as origin of linking objects property 'InvalidObject.linkingObjects' does not exist");
function assertPropertyInvalid(prop, message) {
TestCase.assertThrowsContaining(() => {
new Realm({schema: [{name: 'InvalidObject', properties: { int: 'int', bad: prop }}]});
}, message, 1);
}
// linkingObjects property where the source property is not a link
TestCase.assertThrowsContaining(() => {
new Realm({schema: [{
name: 'InvalidObject',
properties: {
integer: 'int',
linkingObjects: {type:'linkingObjects', objectType: 'InvalidObject', property: 'integer'}
}
}]});
}, "Property 'InvalidObject.integer' declared as origin of linking objects property 'InvalidObject.linkingObjects' is not a link")
assertPropertyInvalid({type:'list[]', objectType: 'InvalidObject'},
"List property 'InvalidObject.bad' must have a non-list value type");
assertPropertyInvalid({type:'list?', objectType: 'InvalidObject'},
"List property 'InvalidObject.bad' cannot be optional");
assertPropertyInvalid('', "Property 'InvalidObject.bad' must have a non-empty type");
assertPropertyInvalid({type:'linkingObjects', objectType: 'InvalidObject', property: 'nosuchproperty'},
"Property 'InvalidObject.nosuchproperty' declared as origin of linking objects property 'InvalidObject.bad' does not exist");
assertPropertyInvalid({type:'linkingObjects', objectType: 'InvalidObject', property: 'int'},
"Property 'InvalidObject.int' declared as origin of linking objects property 'InvalidObject.bad' is not a link");
// linkingObjects property where the source property links elsewhere
TestCase.assertThrowsContaining(() => {
@ -141,8 +137,18 @@ module.exports = {
}
}]});
}, "Property 'InvalidObject.link' declared as origin of linking objects property 'InvalidObject.linkingObjects' links to type 'IntObject'")
{
new Realm({schema: [{
name: 'Object',
properties: {
// weird but valid
objectList: {type:'object[]', objectType: 'Object'}
}
}]});
}
},
testRealmConstructorInMemory: function() {
// open in-memory realm instance
const realm1 = new Realm({inMemory: true, schema: [schemas.TestObject]});
@ -166,7 +172,7 @@ module.exports = {
// Open the same in-memory realm again and verify that it is now empty
const realm3 = new Realm({inMemory: true});
TestCase.assertEqual(realm3.schema.length, 0);
// try to open the same realm in persistent mode (should fail as you cannot mix modes)
TestCase.assertThrowsContaining(() => new Realm({}), 'already opened with different inMemory settings.');
},
@ -296,23 +302,10 @@ module.exports = {
});
},
testRealmCreateOptionals: function() {
const realm = new Realm({schema: [schemas.NullableBasicTypes, schemas.LinkTypes, schemas.TestObject]});
let basic, links;
realm.write(() => {
basic = realm.create('NullableBasicTypesObject', {});
links = realm.create('LinkTypesObject', {});
});
for (const name in schemas.NullableBasicTypes.properties) {
TestCase.assertEqual(basic[name], null);
}
TestCase.assertEqual(links.objectCol, null);
TestCase.assertEqual(links.arrayCol.length, 0);
},
testRealmCreateUpsert: function() {
const realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
realm.write(() => {
const realm = new Realm({schema: [schemas.AllPrimaryTypes, schemas.TestObject,
schemas.StringPrimary]});
realm.write(function() {
const values = {
primaryCol: '0',
boolCol: true,
@ -326,12 +319,12 @@ module.exports = {
arrayCol: [],
};
const obj0 = realm.create('AllTypesObject', values);
const obj0 = realm.create('AllPrimaryTypesObject', values);
TestCase.assertThrowsContaining(() => realm.create('AllTypesObject', values),
"Attempting to create an object of type 'AllTypesObject' with an existing primary key value '0'.");
TestCase.assertThrowsContaining(() => realm.create('AllPrimaryTypesObject', values),
"Attempting to create an object of type 'AllPrimaryTypesObject' with an existing primary key value ''0''.");
const obj1 = realm.create('AllTypesObject', {
const obj1 = realm.create('AllPrimaryTypesObject', {
primaryCol: '1',
boolCol: false,
intCol: 2,
@ -344,10 +337,10 @@ module.exports = {
arrayCol: [{doubleCol: 2}],
}, true);
const objects = realm.objects('AllTypesObject');
const objects = realm.objects('AllPrimaryTypesObject');
TestCase.assertEqual(objects.length, 2);
realm.create('AllTypesObject', {
realm.create('AllPrimaryTypesObject', {
primaryCol: '0',
boolCol: false,
intCol: 2,
@ -371,13 +364,13 @@ module.exports = {
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('AllPrimaryTypesObject', {primaryCol: '0'}, true);
realm.create('AllPrimaryTypesObject', {primaryCol: '1'}, true);
TestCase.assertEqual(obj0.stringCol, '2');
TestCase.assertEqual(obj0.objectCol, null);
TestCase.assertEqual(obj1.objectCol.doubleCol, 0);
realm.create('AllTypesObject', {
realm.create('AllPrimaryTypesObject', {
primaryCol: '0',
stringCol: '3',
objectCol: {doubleCol: 0},
@ -393,13 +386,13 @@ module.exports = {
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('AllPrimaryTypesObject', {primaryCol: '0', objectCol: undefined}, true);
realm.create('AllPrimaryTypesObject', {primaryCol: '1', objectCol: null}, true);
TestCase.assertEqual(obj0.objectCol, null);
TestCase.assertEqual(obj1.objectCol, null);
// test with string primaries
const obj =realm.create('StringPrimaryObject', {
const obj = realm.create('StringPrimaryObject', {
primaryCol: '0',
valueCol: 0
});
@ -784,8 +777,9 @@ module.exports = {
},
testSchema: function() {
const originalSchema = [schemas.TestObject, schemas.BasicTypes, schemas.NullableBasicTypes, schemas.IndexedTypes, schemas.IntPrimary,
schemas.PersonObject, schemas.LinkTypes, schemas.LinkingObjectsObject];
const originalSchema = [schemas.TestObject, schemas.AllTypes, schemas.LinkToAllTypes,
schemas.IndexedTypes, schemas.IntPrimary, schemas.PersonObject,
schemas.LinkTypes, schemas.LinkingObjectsObject];
const schemaMap = {};
originalSchema.forEach(objectSchema => {
@ -801,45 +795,67 @@ module.exports = {
const schema = realm.schema;
TestCase.assertEqual(schema.length, originalSchema.length);
function isString(val) {
return typeof val === 'string' || val instanceof String;
}
const normalizeProperty = (val) => {
let prop;
if (typeof val !== 'string' && !(val instanceof String)) {
prop = val;
prop.optional = val.optional || false;
prop.indexed = val.indexed || false;
}
else {
prop = {type: val, indexed: false, optional: false};
}
if (prop.type.includes('?')) {
prop.optional = true;
prop.type = prop.type.replace('?', '');
}
if (prop.type.includes('[]')) {
prop.objectType = prop.type.replace('[]', '');
prop.type = 'list';
}
return prop;
};
function verifyObjectSchema(returned) {
let original = schemaMap[returned.name];
for (const objectSchema of schema) {
let original = schemaMap[objectSchema.name];
if (original.schema) {
original = original.schema;
}
TestCase.assertEqual(returned.primaryKey, original.primaryKey);
for (const propName in returned.properties) {
const prop1 = returned.properties[propName];
const prop2 = original.properties[propName];
if (prop1.type == 'object') {
TestCase.assertEqual(prop1.objectType, isString(prop2) ? prop2 : prop2.objectType);
TestCase.assertEqual(prop1.optional, true);
TestCase.assertEqual(objectSchema.primaryKey, original.primaryKey);
for (const propName in objectSchema.properties) {
TestCase.assertDefined(original.properties[propName], `schema has unexpected property ${propName}`);
const actual = objectSchema.properties[propName];
const expected = normalizeProperty(original.properties[propName]);
TestCase.assertEqual(actual.name, propName);
TestCase.assertEqual(actual.indexed, expected.indexed);
if (actual.type == 'object') {
TestCase.assertEqual(actual.objectType, expected.type === 'object' ? expected.objectType : expected.type);
TestCase.assertEqual(actual.optional, true);
TestCase.assertUndefined(actual.property);
}
else if (prop1.type == 'list') {
TestCase.assertEqual(prop1.objectType, prop2.objectType);
TestCase.assertEqual(prop1.optional, undefined);
else if (actual.type == 'list') {
TestCase.assertEqual(actual.type, expected.type);
TestCase.assertEqual(actual.objectType, expected.objectType);
TestCase.assertEqual(actual.optional, expected.optional);
TestCase.assertUndefined(actual.property);
}
else if (prop1.type == 'linking objects') {
TestCase.assertEqual(prop1.objectType, prop2.objectType);
TestCase.assertEqual(prop1.property, prop2.property);
TestCase.assertEqual(prop1.optional, undefined);
else if (actual.type == 'linkingObjects') {
TestCase.assertEqual(actual.type, expected.type);
TestCase.assertEqual(actual.objectType, expected.objectType);
TestCase.assertEqual(actual.property, expected.property);
TestCase.assertEqual(actual.optional, false);
}
else {
TestCase.assertEqual(prop1.type, isString(prop2) ? prop2 : prop2.type);
TestCase.assertEqual(prop1.optional, prop2.optional || undefined);
TestCase.assertEqual(actual.type, expected.type);
TestCase.assertEqual(actual.optional, expected.optional);
TestCase.assertUndefined(actual.property);
TestCase.assertUndefined(actual.objectType);
}
TestCase.assertEqual(prop1.indexed, prop2.indexed || undefined);
}
}
for (let i = 0; i < originalSchema.length; i++) {
verifyObjectSchema(schema[i]);
}
},
testCopyBundledRealmFiles: function() {
@ -869,7 +885,7 @@ module.exports = {
const p1 = realm.create('PersonObject', { name: 'Ari', age: 10 });
p1.age = "Ten";
});
}, new Error("PersonObject.age must be of type 'number', got (Ten)"));
}, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')"));
},
testErrorMessageFromInvalidCreate: function() {
@ -879,7 +895,7 @@ module.exports = {
realm.write(() => {
const p1 = realm.create('PersonObject', { name: 'Ari', age: 'Ten' });
});
}, new Error("PersonObject.age must be of type 'number', got (Ten)"));
}, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')"));
},
testValidTypesForListProperties: function() {
@ -934,7 +950,7 @@ module.exports = {
realm.cancelTransaction();
TestCase.assertTrue(!realm.isInTransaction);
},
testCompact: function() {
let wasCalled = false;
const count = 1000;

View File

@ -299,7 +299,7 @@ module.exports = {
},
testResultsInvalidation: function() {
var realm = new Realm({schema: [schemas.TestObject]});
let realm = new Realm({schema: [schemas.TestObject]});
realm.write(function() {
for (var i = 10; i > 0; i--) {
realm.create('TestObject', [i]);
@ -322,7 +322,7 @@ module.exports = {
realm.close();
realm = new Realm({
schemaVersion: 1,
schema: [schemas.TestObject, schemas.BasicTypes]
schema: [schemas.TestObject, schemas.DateObject]
});
resultsVariants.forEach(function(objects) {

View File

@ -33,7 +33,7 @@ PersonObject.schema = {
properties: {
name: 'string',
age: 'double',
married: {type: 'bool', default: false},
married: {type: 'bool', default: false},
children: {type: 'list', objectType: 'PersonObject'},
parents: {type: 'linkingObjects', objectType: 'PersonObject', property: 'children'},
}
@ -51,7 +51,7 @@ exports.PersonObject = PersonObject;
exports.PersonList = {
name: 'PersonList',
properties: {
list: {type: 'list', objectType: 'PersonObject'},
list: 'PersonObject[]',
}
};
@ -68,26 +68,82 @@ exports.BasicTypes = {
}
};
exports.NullableBasicTypes = {
name: 'NullableBasicTypesObject',
exports.AllTypes = {
name: 'AllTypesObject',
properties: {
boolCol: {type: 'bool', optional: true},
intCol: {type: 'int', optional: true},
floatCol: {type: 'float', optional: true},
doubleCol: {type: 'double', optional: true},
stringCol: {type: 'string', optional: true},
dateCol: {type: 'date', optional: true},
dataCol: {type: 'data', optional: true},
boolCol: 'bool',
intCol: 'int',
floatCol: 'float',
doubleCol: 'double',
stringCol: 'string',
dateCol: 'date',
dataCol: 'data',
objectCol: 'TestObject',
optBoolCol: 'bool?',
optIntCol: 'int?',
optFloatCol: 'float?',
optDoubleCol: 'double?',
optStringCol: 'string?',
optDateCol: 'date?',
optDataCol: 'data?',
boolArrayCol: 'bool[]',
intArrayCol: 'int[]',
floatArrayCol: 'float[]',
doubleArrayCol: 'double[]',
stringArrayCol: 'string[]',
dateArrayCol: 'date[]',
dataArrayCol: 'data[]',
objectArrayCol: 'TestObject[]',
optBoolArrayCol: 'bool?[]',
optIntArrayCol: 'int?[]',
optFloatArrayCol: 'float?[]',
optDoubleArrayCol: 'double?[]',
optStringArrayCol: 'string?[]',
optDateArrayCol: 'date?[]',
optDataArrayCol: 'data?[]',
linkingObjectsCol: {type: 'linkingObjects', objectType: 'LinkToAllTypesObject', property: 'allTypesCol'},
}
};
exports.AllPrimaryTypes = {
name: 'AllPrimaryTypesObject',
primaryKey: 'primaryCol',
properties: {
primaryCol: 'string',
boolCol: 'bool',
intCol: 'int',
floatCol: 'float',
doubleCol: 'double',
stringCol: 'string',
dateCol: 'date',
dataCol: 'data',
objectCol: 'TestObject',
arrayCol: {type: 'list', objectType: 'TestObject'},
}
};
exports.LinkToAllTypes = {
name: 'LinkToAllTypesObject',
properties: {
allTypesCol: 'AllTypesObject',
}
}
exports.IndexedTypes = {
name: 'IndexedTypesObject',
properties: {
boolCol: {type: 'bool', indexed: true},
intCol: {type: 'int', indexed: true},
stringCol: {type: 'string', indexed: true},
dateCol: {type: 'date', indexed: true},
boolCol: {type: 'bool', indexed: true},
intCol: {type: 'int', indexed: true},
stringCol: {type: 'string', indexed: true},
dateCol: {type: 'date', indexed: true},
optBoolCol: {type: 'bool?', indexed: true},
optIntCol: {type: 'int?', indexed: true},
optStringCol: {type: 'string?', indexed: true},
optDateCol: {type: 'date?', indexed: true},
}
};
@ -95,9 +151,31 @@ exports.IndexedTypes = {
exports.LinkTypes = {
name: 'LinkTypesObject',
properties: {
objectCol: 'TestObject',
objectCol: 'TestObject',
objectCol1: {type: 'object', objectType: 'TestObject'},
arrayCol: {type: 'list', objectType: 'TestObject'},
arrayCol: 'TestObject[]',
arrayCol1: {type: 'list', objectType: 'TestObject'},
}
};
exports.PrimitiveArrays = {
name: 'PrimitiveArrays',
properties: {
bool: 'bool[]',
int: 'int[]',
float: 'float[]',
double: 'double[]',
string: 'string[]',
date: 'date[]',
data: 'data[]',
optBool: 'bool?[]',
optInt: 'int?[]',
optFloat: 'float?[]',
optDouble: 'double?[]',
optString: 'string?[]',
optDate: 'date?[]',
optData: 'data?[]',
}
};
@ -126,44 +204,19 @@ exports.StringOnly = {
}
};
exports.AllTypes = {
name: 'AllTypesObject',
primaryKey: 'primaryCol',
properties: {
primaryCol: 'string',
boolCol: 'bool',
intCol: 'int',
floatCol: 'float',
doubleCol: 'double',
stringCol: 'string',
dateCol: 'date',
dataCol: 'data',
objectCol: 'TestObject',
arrayCol: {type: 'list', objectType: 'TestObject'},
linkingObjectsCol: {type: 'linkingObjects', objectType: 'LinkToAllTypesObject', property: 'allTypesCol'},
}
};
exports.LinkToAllTypes = {
name: 'LinkToAllTypesObject',
properties: {
allTypesCol: 'AllTypesObject',
}
}
exports.DefaultValues = {
name: 'DefaultValuesObject',
properties: {
boolCol: {type: 'bool', default: true},
intCol: {type: 'int', default: -1},
floatCol: {type: 'float', default: -1.1},
doubleCol: {type: 'double', default: -1.11},
stringCol: {type: 'string', default: 'defaultString'},
dateCol: {type: 'date', default: new Date(1.111)},
dataCol: {type: 'data', default: new ArrayBuffer(1)},
objectCol: {type: 'TestObject', default: {doubleCol: 1}},
nullObjectCol: {type: 'TestObject', default: null},
arrayCol: {type: 'list', objectType: 'TestObject', default: [{doubleCol: 2}]},
boolCol: {type: 'bool', default: true},
intCol: {type: 'int', default: -1},
floatCol: {type: 'float', default: -1.1},
doubleCol: {type: 'double', default: -1.11},
stringCol: {type: 'string', default: 'defaultString'},
dateCol: {type: 'date', default: new Date(1.111)},
dataCol: {type: 'data', default: new ArrayBuffer(1)},
objectCol: {type: 'TestObject', default: {doubleCol: 1}},
nullObjectCol: {type: 'TestObject', default: null},
arrayCol: {type: 'TestObject[]', default: [{doubleCol: 2}]},
}
};
@ -203,7 +256,7 @@ exports.DateObject = {
name: 'Date',
properties: {
currentDate: 'date',
nullDate: { type: 'date', optional: true }
nullDate: 'date?'
}
};
@ -211,7 +264,7 @@ exports.LinkingObjectsObject = {
name: 'LinkingObjectsObject',
properties: {
value: 'int',
links: {type: 'list', objectType: 'LinkingObjectsObject'},
links: 'LinkingObjectsObject[]',
linkingObjects: {type: 'linkingObjects', objectType: 'LinkingObjectsObject', property: 'links'}
}
}