diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 95a8b062..17ab0c7a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -40,6 +40,6 @@ Full projects that we can compile and run ourselves are ideal! ## Version of Realm and Tooling - Realm JS SDK Version: ? -- Node or React Nattive: ? +- Node or React Native: ? - Client OS & Version: ? - Which debugger for React Native: ?/None diff --git a/CHANGELOG.md b/CHANGELOG.md index ff9119c8..8884342b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ -NEXT RELEASE +X.Y.Z Release notes ============================================================= +### Breaking changes +* None ### Enhancements * Add a callback function used to verify SSL certificates in the sync config. +### Bug fixes +* Fixed port conflict between RN >= 0.48 inspector proxy and RPC server used for Chrome debugging (#1294). + 2.0.0-rc10 Release notes (2017-9-19) ============================================================= diff --git a/docs/sync.js b/docs/sync.js index 76d20ef3..3b271d5a 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -30,9 +30,6 @@ class Sync { * _Currently only the 'change' event is supported_ * @param {function(change_event)} change_callback - called when changes are made to any Realm which * match the given regular expression - * @param {bool} validate_ssl=true - Validate the server's SSL chertificate. - * @param {string} ssl_trust_certificate_path=None - Path to a trust/anchor certificate used by the - * client to verify the server certificate. */ static addListener(server_url, admin_user, regex, name, change_callback) {} diff --git a/lib/browser/index.js b/lib/browser/index.js index 10bc7512..8ef9fc12 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -83,14 +83,14 @@ export default class Realm { if (typeof item == 'function') { let schema = item.schema; if (!schema || typeof schema != 'object') { - throw new Error("Realm object constructor must have 'schema' property"); + throw new Error("Realm object constructor must have a 'schema' property."); } let {name, properties} = schema; if (!name || typeof name != 'string') { - throw new Error("Realm object schema must have 'name' property"); + throw new Error(`Failed to read ObjectSchema: name must be of type 'string', got (${typeof name})`); } else if (!properties || typeof properties != 'object') { - throw new Error("Realm object schema must have 'properties' property"); + throw new Error(`Failed to read ObjectSchema: properties must be of type 'object', got (${typeof properties})`); } schemas.splice(i, 1, schema); diff --git a/lib/browser/objects.js b/lib/browser/objects.js index f0f5caf7..9c9ad87e 100644 --- a/lib/browser/objects.js +++ b/lib/browser/objects.js @@ -85,5 +85,5 @@ export function typeForConstructor(realmId, constructor) { } } - return null; + throw new Error("Constructor was not registered in the schema for this Realm") } diff --git a/package.json b/package.json index c14bf2e3..ba90b899 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,9 @@ "jsdoc": "npm install && npm run jsdoc:clean && jsdoc -u docs/tutorials -p package.json -c docs/conf.json", "prenode-tests": "npm install --build-from-source=realm && cd tests && npm install", "node-tests": "cd tests && npm run test && cd ..", - "test-runner:ava": "cd tests/test-runners/ava && npm install && npm test", - "test-runner:mocha": "cd tests/test-runners/mocha && npm install && npm test", - "test-runner:jest": "cd tests/test-runners/jest && npm install && npm test", + "test-runner:ava": "cd tests/test-runners/ava && npm install --build-from-source=realm && npm test", + "test-runner:mocha": "cd tests/test-runners/mocha && npm install --build-from-source=realm && npm test", + "test-runner:jest": "cd tests/test-runners/jest && npm install --build-from-source=realm && npm test", "test-runners": "npm run test-runner:ava && npm run test-runner:mocha && npm run test-runner:jest", "isMac": "node -p \"if (process.platform == 'darwin') { process.exit(0); } else { process.exit(-1); }\"", "testMac": "npm run isMac -s && echo this is mac || echo . ", diff --git a/react-native/android/publish_android_template b/react-native/android/publish_android_template index c4c0670b..7d1e6686 100644 --- a/react-native/android/publish_android_template +++ b/react-native/android/publish_android_template @@ -22,17 +22,17 @@ apply plugin: 'com.android.library' task forwardDebugPort(type: Exec) { def adb = android.getAdbExe()?.toString() ?: 'false' - commandLine adb, 'forward', 'tcp:8082', 'tcp:8082' + commandLine adb, 'forward', 'tcp:8083', 'tcp:8083' ignoreExitValue true doLast { if (execResult.getExitValue() != 0) { logger.error( '===========================================================================\n' + - 'WARNING: Failed to automatically forward port 8082.\n' + - 'In order to use Realm in Chrome debugging mode, port 8082 must be forwarded\n' + + 'WARNING: Failed to automatically forward port 8083.\n' + + 'In order to use Realm in Chrome debugging mode, port 8083 must be forwarded\n' + 'from localhost to the device or emulator being used to run the application.\n' + 'You may need to add the appropriate flags to the command that failed:\n' + - ' adb forward tcp:8082 tcp:8082\n' + + ' adb forward tcp:8083 tcp:8083\n' + '===========================================================================\n' ) } diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java index 696b978d..0ad49a2a 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java @@ -23,7 +23,7 @@ import java.util.Map; import fi.iki.elonen.NanoHTTPD; class RealmReactModule extends ReactContextBaseJavaModule { - private static final int DEFAULT_PORT = 8082; + private static final int DEFAULT_PORT = 8083; private static boolean sentAnalytics = false; private AndroidWebServer webServer; diff --git a/react-native/ios/RealmReact/RealmReact.mm b/react-native/ios/RealmReact/RealmReact.mm index 62be7893..26da98e2 100644 --- a/react-native/ios/RealmReact/RealmReact.mm +++ b/react-native/ios/RealmReact/RealmReact.mm @@ -38,7 +38,7 @@ #import "GCDWebServerErrorResponse.h" #import "rpc.hpp" -#define WEB_SERVER_PORT 8082 +#define WEB_SERVER_PORT 8083 using namespace realm::rpc; #endif diff --git a/scripts/test.sh b/scripts/test.sh index 6fb10a4a..0aac32ec 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,7 +9,7 @@ export NPM_CONFIG_PROGRESS=false TARGET=$1 CONFIGURATION=${2:-Release} -if echo $CONFIGURATION | grep -i "^Debug$" > /dev/null ; then +if echo "$CONFIGURATION" | grep -i "^Debug$" > /dev/null ; then CONFIGURATION="Debug" fi @@ -17,8 +17,6 @@ IOS_SIM_DEVICE=${IOS_SIM_DEVICE:-} # use preferentially, otherwise will be set a ios_sim_default_device_type=${IOS_SIM_DEVICE_TYPE:-iPhone 5s} ios_sim_default_ios_version=${IOS_SIM_OS:-iOS 10.1} -ACCEPTED_LICENSES='MIT, ISC, BSD, Apache-2.0, BSD-2-Clause, BSD-3-Clause, WTFPL, Unlicense, (MIT AND CC-BY-3.0)' - PATH="/opt/android-sdk-linux/platform-tools:$PATH" SRCROOT=$(cd "$(dirname "$0")/.." && pwd) XCPRETTY=$(which xcpretty || true) @@ -130,13 +128,18 @@ xctest() { echo " done" # - Run the build and test + xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" build || { + EXITCODE=$? + echo "*** Failure (exit code $EXITCODE). ***" + exit $EXITCODE + } if [ -n "$XCPRETTY" ]; then log_temp=$(mktemp build.log.XXXXXX) if [ -e "$log_temp" ]; then rm "$log_temp" fi - xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination name="iPhone 5s" build test 2>&1 | tee "$log_temp" | "$XCPRETTY" -c --no-utf --report junit --output build/reports/junit.xml || { - EXITCODE=$? + xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination name="iPhone 5s" test 2>&1 | tee "$log_temp" | "$XCPRETTY" -c --no-utf --report junit --output build/reports/junit.xml || { + EXITCODE=$? printf "*** Xcode Failure (exit code %s). The full xcode log follows: ***\n\n" "$EXITCODE" cat "$log_temp" printf "\n\n*** End Xcode Failure ***\n" @@ -144,11 +147,11 @@ xctest() { } rm "$log_temp" else - xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" build test || { - EXITCODE=$? + xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" test || { + EXITCODE=$? echo "*** Failure (exit code $EXITCODE). ***" exit $EXITCODE - } + } fi } @@ -240,14 +243,22 @@ cleanup trap cleanup EXIT # Use a consistent version of Node if possible. -if [ -f "$NVM_DIR/nvm.sh" ]; then - . "$NVM_DIR/nvm.sh" -elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then - # we must be on mac and nvm was installed with brew - # TODO: change the mac slaves to use manual nvm installation - . "$(brew --prefix nvm)/nvm.sh" +if [[ -z "$(command -v nvm)" ]]; then + set +e + if [ -f "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" '' || true + elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then + # we must be on mac and nvm was installed with brew + # TODO: change the mac slaves to use manual nvm installation + . "$(brew --prefix nvm)/nvm.sh" '' || true + elif [ -f "$HOME/.nvm/nvm.sh" ]; then + . ~/.nvm/nvm.sh '' + fi + set -e +fi +if [[ "$(command -v nvm)" ]]; then + nvm install 7.10.0 fi -[[ "$(command -v nvm)" ]] && nvm install 7.10.0 && nvm use 7.10.0 || true # Remove cached packages rm -rf ~/.yarn-cache/npm-realm-* @@ -406,8 +417,6 @@ case "$TARGET" in ;; "test-runners") npm run check-environment - # Create a fake realm module that points to the source root so that test-runner tests can require('realm') - npm install --build-from-source=realm npm run test-runners ;; "all") diff --git a/src/js_class.hpp b/src/js_class.hpp index 70b28e2e..052ec8f5 100644 --- a/src/js_class.hpp +++ b/src/js_class.hpp @@ -33,6 +33,29 @@ using ConstructorType = void(typename T::Context, typename T::Object, size_t, co template using MethodType = void(typename T::Context, typename T::Function, typename T::Object, size_t, const typename T::Value[], ReturnValue &); +template +struct Arguments { + const typename T::Context ctx; + const size_t count; + const typename T::Value* const value; + + typename T::Value operator[](size_t index) const noexcept { + if (index >= count) { + return Value::from_undefined(ctx); + } + return value[index]; + } + + void validate_maximum(size_t max) const { + if (max < count) { + throw std::invalid_argument(util::format("Invalid arguments: at most %1 expected, but %2 supplied.", max, count)); + } + } +}; + +template +using ArgumentsMethodType = void(typename T::Context, typename T::Function, typename T::Object, Arguments, ReturnValue &); + template struct PropertyType { using GetterType = void(typename T::Context, typename T::Object, ReturnValue &); diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 5f22c677..90981bb9 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -123,9 +123,7 @@ class RealmDelegate : public BindingContext { } ObjectType realm_object = create_object>(m_context, new SharedRealm(realm)); - ValueType arguments[2]; - arguments[0] = realm_object; - arguments[1] = Value::from_string(m_context, notification_name); + ValueType arguments[] = {realm_object, Value::from_string(m_context, notification_name)}; std::list> notifications_copy(m_notifications); for (auto &callback : notifications_copy) { @@ -148,6 +146,7 @@ class RealmClass : public ClassDefinition> { using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; + using Arguments = js::Arguments; using String = js::String; using Object = js::Object; using Value = js::Value; @@ -165,22 +164,22 @@ public: static FunctionType create_constructor(ContextType); // methods - static void objects(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void object_for_primary_key(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void create(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_one(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_all(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void write(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void begin_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); - static void commit_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); - static void cancel_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); - static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void wait_for_download_completion(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 close(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void compact(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_model(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + 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 &); // properties static void get_empty(ContextType, ObjectType, ReturnValue &); @@ -198,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, size_t, const ValueType[], ReturnValue &); - static void clear_test_state(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_file(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + 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 properties static void get_default_path(ContextType, ObjectType, ReturnValue &); @@ -398,7 +397,7 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t static const String schema_string = "schema"; ValueType schema_value = Object::get_property(ctx, object, schema_string); if (!Value::is_undefined(ctx, schema_value)) { - ObjectType schema_object = Value::validated_to_object(ctx, schema_value, "schema"); + ObjectType schema_object = Value::validated_to_array(ctx, schema_value, "schema"); config.schema.emplace(Schema::parse_schema(ctx, schema_object, defaults, constructors)); schema_updated = true; } @@ -504,13 +503,13 @@ SharedRealm RealmClass::create_shared_realm(ContextType ctx, realm::Realm::Co } template -void RealmClass::schema_version(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1, 2); +void RealmClass::schema_version(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); realm::Realm::Config config; - config.path = normalize_realm_path(Value::validated_to_string(ctx, arguments[0])); - if (argc == 2) { - auto encryption_key = Value::validated_to_binary(ctx, arguments[1], "encryptionKey"); + config.path = normalize_realm_path(Value::validated_to_string(ctx, args[0])); + if (args.count == 2) { + auto encryption_key = Value::validated_to_binary(ctx, args[1], "encryptionKey"); config.encryption_key.assign(encryption_key.data(), encryption_key.data() + encryption_key.size()); } @@ -525,23 +524,22 @@ void RealmClass::schema_version(ContextType ctx, FunctionType, ObjectType thi template -void RealmClass::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); - +void RealmClass::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); js::clear_test_state(); } template -void RealmClass::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); realm::copy_bundled_realm_files(); } template -void RealmClass::delete_file(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::delete_file(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); - ValueType value = arguments[0]; + ValueType value = args[0]; if (!Value::is_object(ctx, value)) { throw std::runtime_error("Invalid argument, expected a Realm configuration object"); } @@ -569,9 +567,9 @@ void RealmClass::delete_file(ContextType ctx, FunctionType, ObjectType this_o } template -void RealmClass::delete_model(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); - ValueType value = arguments[0]; +void RealmClass::delete_model(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + ValueType value = args[0]; SharedRealm& realm = *get_internal>(this_object); @@ -643,14 +641,14 @@ void RealmClass::get_sync_session(ContextType ctx, ObjectType object, ReturnV #endif template -void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2, 3); - auto config_object = Value::validated_to_object(ctx, arguments[0]); - auto callback_function = Value::validated_to_function(ctx, arguments[argc - 1]); +void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, 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)]); ValueType session_callback = Value::from_null(ctx); - if (argc == 3) { - session_callback = Value::validated_to_function(ctx, arguments[1]); + if (args.count == 3) { + session_callback = Value::validated_to_function(ctx, args[1]); } #if REALM_ENABLE_SYNC @@ -754,31 +752,33 @@ void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, return; } } +#else + static_cast(config_object); #endif Function::callback(ctx, callback_function, this_object, 0, nullptr); } template -void RealmClass::objects(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::objects(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); std::string object_type; - validated_object_schema_for_value(ctx, realm, arguments[0], object_type); + validated_object_schema_for_value(ctx, realm, args[0], object_type); return_value.set(ResultsClass::create_instance(ctx, realm, object_type)); } template -void RealmClass::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2); +void RealmClass::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); SharedRealm realm = *get_internal>(this_object); std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); NativeAccessor accessor(ctx, realm, object_schema); - auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, arguments[1]); + auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, args[1]); if (realm_object.is_valid()) { return_value.set(RealmObjectClass::create_instance(ctx, std::move(realm_object))); @@ -789,21 +789,22 @@ void RealmClass::object_for_primary_key(ContextType ctx, FunctionType, Object } template -void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2, 3); +void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(3); SharedRealm realm = *get_internal>(this_object); + realm->verify_open(); std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); - ObjectType object = Value::validated_to_object(ctx, arguments[1], "properties"); - if (Value::is_array(ctx, arguments[1])) { + ObjectType object = Value::validated_to_object(ctx, args[1], "properties"); + if (Value::is_array(ctx, args[1])) { object = Schema::dict_for_property_array(ctx, object_schema, object); } bool update = false; - if (argc == 3) { - update = Value::validated_to_boolean(ctx, arguments[2], "update"); + if (args.count == 3) { + update = Value::validated_to_boolean(ctx, args[2], "update"); } NativeAccessor accessor(ctx, realm, object_schema); @@ -812,15 +813,16 @@ void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object } template -void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); + realm->verify_open(); if (!realm->is_in_transaction()) { throw std::runtime_error("Can only delete objects within a transaction."); } - ObjectType arg = Value::validated_to_object(ctx, arguments[0]); + ObjectType arg = Value::validated_to_object(ctx, args[0], "object"); if (Object::template is_instance>(ctx, arg)) { auto object = get_internal>(arg); @@ -859,10 +861,11 @@ void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_ob } template -void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); + realm->verify_open(); if (!realm->is_in_transaction()) { throw std::runtime_error("Can only delete objects within a transaction."); @@ -874,18 +877,18 @@ void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_ob } template -void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); - FunctionType callback = Value::validated_to_function(ctx, arguments[0]); + FunctionType callback = Value::validated_to_function(ctx, args[0]); realm->begin_transaction(); try { Function::call(ctx, callback, this_object, 0, nullptr); } - catch (std::exception &e) { + catch (...) { realm->cancel_transaction(); throw; } @@ -894,82 +897,76 @@ void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, } template -void RealmClass::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->begin_transaction(); } template -void RealmClass::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->commit_transaction(); } template -void RealmClass::cancel_transaction(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::cancel_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->cancel_transaction(); } template -void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2); +void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); - validated_notification_name(ctx, arguments[0]); - auto callback = Value::validated_to_function(ctx, arguments[1]); + validated_notification_name(ctx, args[0]); + auto callback = Value::validated_to_function(ctx, args[1]); SharedRealm realm = *get_internal>(this_object); - if (realm->is_closed()) { - throw ClosedRealmException(); - } + realm->verify_open(); get_delegate(realm.get())->add_notification(callback); } template -void RealmClass::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2); +void RealmClass::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); - validated_notification_name(ctx, arguments[0]); - auto callback = Value::validated_to_function(ctx, arguments[1]); + validated_notification_name(ctx, args[0]); + auto callback = Value::validated_to_function(ctx, args[1]); SharedRealm realm = *get_internal>(this_object); - if (realm->is_closed()) { - throw ClosedRealmException(); - } + realm->verify_open(); get_delegate(realm.get())->remove_notification(callback); } template -void RealmClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0, 1); - if (argc) { - validated_notification_name(ctx, arguments[0]); +void RealmClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + if (args.count) { + validated_notification_name(ctx, args[0]); } SharedRealm realm = *get_internal>(this_object); - if (realm->is_closed()) { - throw ClosedRealmException(); - } + realm->verify_open(); get_delegate(realm.get())->remove_all_notifications(); } template -void RealmClass::close(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::close(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->close(); } template -void RealmClass::compact(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::compact(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); if (realm->is_in_transaction()) { diff --git a/src/js_schema.hpp b/src/js_schema.hpp index e96e0290..51ef3aa5 100644 --- a/src/js_schema.hpp +++ b/src/js_schema.hpp @@ -180,9 +180,9 @@ ObjectSchema Schema::parse_object_schema(ContextType ctx, ObjectType object_s ObjectDefaults object_defaults; ObjectSchema object_schema; - object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string); + object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string, "ObjectSchema"); - ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema must have a 'properties' object."); + ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema"); if (Value::is_array(ctx, properties_object)) { uint32_t length = Object::validated_get_length(ctx, properties_object); for (uint32_t i = 0; i < length; i++) { @@ -233,7 +233,8 @@ ObjectSchema Schema::parse_object_schema(ContextType ctx, ObjectType object_s } template -realm::Schema Schema::parse_schema(ContextType ctx, ObjectType schema_object, ObjectDefaultsMap &defaults, ConstructorMap &constructors) { +realm::Schema Schema::parse_schema(ContextType ctx, ObjectType schema_object, + ObjectDefaultsMap &defaults, ConstructorMap &constructors) { std::vector schema; uint32_t length = Object::validated_get_length(ctx, schema_object); diff --git a/src/js_sync.hpp b/src/js_sync.hpp index b364b81b..5d7c1d72 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -470,7 +470,7 @@ void SessionClass::add_progress_notification(ContextType ctx, FunctionType, O progressFunc = std::move(progress_handler); - auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, false); + auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, is_streaming); auto syncSession = create_object>(ctx, new WeakSession(session)); PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete; diff --git a/src/js_types.hpp b/src/js_types.hpp index e1cf6459..4f20b6cf 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -27,6 +27,7 @@ #include #include +#include #include #if defined(__GNUC__) && !(defined(DEBUG) && DEBUG) @@ -223,7 +224,7 @@ struct Object { return Value::validated_to_##type(ctx, get_property(ctx, object, key), std::string(key).c_str()); \ } \ catch (std::invalid_argument &e) { \ - throw message ? std::invalid_argument(message) : e; \ + throw message ? std::invalid_argument(util::format("Failed to read %1: %2", message, e.what())) : e; \ } \ } \ static return_t validated_get_##type(ContextType ctx, const ObjectType &object, uint32_t index, const char *message = nullptr) { \ @@ -231,7 +232,7 @@ struct Object { return Value::validated_to_##type(ctx, get_property(ctx, object, index)); \ } \ catch (std::invalid_argument &e) { \ - throw message ? std::invalid_argument(message) : e; \ + throw message ? std::invalid_argument(util::format("Failed to read %1: %2", message, e.what())) : e; \ } \ } diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp index 6e6f7618..8accc341 100644 --- a/src/jsc/jsc_class.hpp +++ b/src/jsc/jsc_class.hpp @@ -30,7 +30,9 @@ template using ClassDefinition = js::ClassDefinition; using ConstructorType = js::ConstructorType; +using ArgumentsMethodType = js::ArgumentsMethodType; using MethodType = js::MethodType; +using Arguments = js::Arguments; using PropertyType = js::PropertyType; using IndexPropertyType = js::IndexPropertyType; using StringPropertyType = js::StringPropertyType; @@ -369,6 +371,19 @@ JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, } } +template +JSValueRef wrap(JSContextRef ctx, JSObjectRef function, 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); + return return_value; + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return nullptr; + } +} + template JSValueRef wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) { jsc::ReturnValue return_value(ctx); diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp index 21cddad6..2b1c7557 100644 --- a/src/node/node_class.hpp +++ b/src/node/node_class.hpp @@ -31,6 +31,8 @@ using ClassDefinition = js::ClassDefinition; using ConstructorType = js::ConstructorType; using MethodType = js::MethodType; +using ArgumentsMethodType = js::ArgumentsMethodType; +using Arguments = js::Arguments; using PropertyType = js::PropertyType; using IndexPropertyType = js::IndexPropertyType; using StringPropertyType = js::StringPropertyType; @@ -308,6 +310,21 @@ void wrap(const v8::FunctionCallbackInfo& info) { } } +template +void wrap(const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + node::ReturnValue return_value(info.GetReturnValue()); + auto arguments = node::get_arguments(info); + + try { + F(isolate, info.Callee(), info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value); + } + catch (std::exception &e) { + Nan::ThrowError(node::Exception::value(isolate, e)); + } +} + + template void wrap(v8::Local property, const v8::PropertyCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); diff --git a/tests/js/asserts.js b/tests/js/asserts.js index a3a3026e..95f3378c 100644 --- a/tests/js/asserts.js +++ b/tests/js/asserts.js @@ -120,6 +120,23 @@ module.exports = { } }, + assertThrowsContaining: function(func, expectedMessage) { + var caught = false; + try { + func(); + } + catch (e) { + caught = true; + if (!e.message.includes(expectedMessage)) { + throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`); + } + } + + if (!caught) { + throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`); + } + }, + assertTrue: function(condition, errorMessage) { if (!condition) { throw new TestFailureError(errorMessage || `Condition ${condition} expected to be true`); diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index c1bf831e..5f33e95d 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -54,13 +54,25 @@ function wait(t) { return new Promise(resolve => setTimeout(resolve, t)); } +function repeatUntil(fn, predicate) { + let retries = 0 + function check() { + if (retries > 3) { + return Promise.reject(new Error("operation timed out")); + } + ++retries; + return fn().then(x => predicate(x) ? x : wait(100).then(check)); + } + return check; +} + module.exports = { testApplyAndGetGrantedPermissions() { return createUsersWithTestRealms(1) .then(([user]) => { return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read') - .then(wait(100)) - .then(() => user.getGrantedPermissions('any')) + .then(repeatUntil(() => user.getGrantedPermissions('any'), + permissions => permissions.length > 1)) .then(permissions => { TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`); TestCase.assertEqual(permissions[1].mayRead, true); @@ -77,17 +89,19 @@ module.exports = { .then(token => user2.acceptPermissionOffer(token)) .then(realmUrl => { TestCase.assertEqual(realmUrl, `/${user1.identity}/test`); - return user2.getGrantedPermissions('any') - .then(permissions => { - TestCase.assertEqual(permissions[1].path, `/${user1.identity}/test`); - TestCase.assertEqual(permissions[1].mayRead, true); - TestCase.assertEqual(permissions[1].mayWrite, false); - TestCase.assertEqual(permissions[1].mayManage, false); - }); + return realmUrl; + }) + .then(repeatUntil(() => user2.getGrantedPermissions('any'), + permissions => permissions.length > 1)) + .then(permissions => { + TestCase.assertEqual(permissions[1].path, `/${user1.identity}/test`); + TestCase.assertEqual(permissions[1].mayRead, true); + TestCase.assertEqual(permissions[1].mayWrite, false); + TestCase.assertEqual(permissions[1].mayManage, false); }); }); }, - + testInvalidatePermissionOffer() { return createUsersWithTestRealms(2) .then(([user1, user2]) => { diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 791d130f..251e8e8d 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -18,9 +18,9 @@ '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'); let pathSeparator = '/'; if (typeof process === 'object' && process.platform === 'win32') { @@ -29,7 +29,7 @@ if (typeof process === 'object' && process.platform === 'win32') { module.exports = { testRealmConstructor: function() { - var realm = new Realm({schema: []}); + const realm = new Realm({schema: []}); TestCase.assertTrue(realm instanceof Realm); TestCase.assertEqual(typeof Realm, 'function'); @@ -37,52 +37,43 @@ module.exports = { }, testRealmConstructorPath: function() { - TestCase.assertThrows(function() { - new Realm(''); - }, 'Realm cannot be created with an invalid path'); - TestCase.assertThrows(function() { - new Realm('test1.realm', 'invalidArgument'); - }, 'Realm constructor can only have 0 or 1 argument(s)'); + TestCase.assertThrows(() => new Realm('')); // the message for this error is platform-specific + TestCase.assertThrowsContaining(() => new Realm('test1.realm', 'invalidArgument'), + "Invalid arguments when constructing 'Realm'"); - var defaultRealm = new Realm({schema: []}); + const defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); - var defaultRealm2 = new Realm(); + const defaultRealm2 = new Realm(); TestCase.assertEqual(defaultRealm2.path, Realm.defaultPath); - var defaultDir = Realm.defaultPath.substring(0, Realm.defaultPath.lastIndexOf(pathSeparator) + 1) - var testPath = 'test1.realm'; - var realm = new Realm({schema: [], path: testPath}); + const defaultDir = Realm.defaultPath.substring(0, Realm.defaultPath.lastIndexOf(pathSeparator) + 1); + const testPath = 'test1.realm'; + const realm = new Realm({schema: [], path: testPath}); TestCase.assertEqual(realm.path, defaultDir + testPath); - var testPath2 = 'test2.realm'; - var realm2 = new Realm({schema: [], path: testPath2}); + const testPath2 = 'test2.realm'; + const realm2 = new Realm({schema: [], path: testPath2}); TestCase.assertEqual(realm2.path, defaultDir + testPath2); }, testRealmConstructorSchemaVersion: function() { - var defaultRealm = new Realm({schema: []}); + const defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.schemaVersion, 0); - TestCase.assertThrows(function() { - new Realm({schemaVersion: 1, schema: []}); - }, "Realm already opened at a different schema version"); + TestCase.assertThrowsContaining(() => new Realm({schemaVersion: 1, schema: []}), + "already opened with different schema version."); TestCase.assertEqual(new Realm().schemaVersion, 0); TestCase.assertEqual(new Realm({schemaVersion: 0}).schemaVersion, 0); - var realm = new Realm({path: 'test1.realm', schema: [], schemaVersion: 1}); + let realm = new Realm({path: 'test1.realm', schema: [], schemaVersion: 1}); TestCase.assertEqual(realm.schemaVersion, 1); TestCase.assertEqual(realm.schema.length, 0); realm.close(); - // FIXME - enable once realm initialization supports schema comparison - // TestCase.assertThrows(function() { - // realm = new Realm({path: testPath, schema: [schemas.TestObject], schemaVersion: 1}); - // }, "schema changes require updating the schema version"); - realm = new Realm({path: 'test1.realm', schema: [schemas.TestObject], schemaVersion: 2}); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertEqual(realm.objects('TestObject')[0].doubleCol, 1); @@ -91,41 +82,31 @@ module.exports = { }, testRealmConstructorDynamicSchema: function() { - var realm = new Realm({schema: [schemas.TestObject]}); - realm.write(function() { + let realm = new Realm({schema: [schemas.TestObject]}); + realm.write(() => { realm.create('TestObject', [1]) }); realm.close(); realm = new Realm(); - var objects = realm.objects('TestObject'); + const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.length, 1); TestCase.assertEqual(objects[0].doubleCol, 1.0); }, testRealmConstructorSchemaValidation: function() { - TestCase.assertThrows(function() { - new Realm({schema: schemas.AllTypes}); - }, 'The schema should be an array'); - - TestCase.assertThrows(function() { - new Realm({schema: ['SomeType']}); - }, 'The schema should be an array of objects'); - - TestCase.assertThrows(function() { - new Realm({schema: [{}]}); - }, 'The schema should be an array of ObjectSchema objects'); - - TestCase.assertThrows(function() { - new Realm({schema: [{name: 'SomeObject'}]}); - }, 'The schema should be an array of ObjectSchema objects'); - - TestCase.assertThrows(function() { - new Realm({schema: [{properties: {intCol: 'int'}}]}); - }, 'The schema should be an array of ObjectSchema objects'); + 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: [{}]}), + "Failed to read ObjectSchema: name must be of type 'string', got (undefined)"); + TestCase.assertThrowsContaining(() => new Realm({schema: [{name: 'SomeObject'}]}), + "Failed to read ObjectSchema: properties must be of type 'object', got (undefined)"); + 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.assertThrows(function() { + TestCase.assertThrowsContaining(() => { new Realm({schema: [{ name: 'InvalidObject', properties: { @@ -135,7 +116,7 @@ module.exports = { }, "Property 'InvalidObject.nosuchproperty' declared as origin of linking objects property 'InvalidObject.linkingObjects' does not exist"); // linkingObjects property where the source property is not a link - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { new Realm({schema: [{ name: 'InvalidObject', properties: { @@ -146,7 +127,7 @@ module.exports = { }, "Property 'InvalidObject.integer' declared as origin of linking objects property 'InvalidObject.linkingObjects' is not a link") // linkingObjects property where the source property links elsewhere - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { new Realm({schema: [{ name: 'InvalidObject', properties: { @@ -165,7 +146,7 @@ module.exports = { testRealmConstructorInMemory: function() { // open in-memory realm instance const realm1 = new Realm({inMemory: true, schema: [schemas.TestObject]}); - realm1.write(function() { + realm1.write(() => { realm1.create('TestObject', [1]) }); TestCase.assertEqual(realm1.inMemory, true); @@ -187,28 +168,25 @@ module.exports = { TestCase.assertEqual(realm3.schema.length, 0); // try to open the same realm in persistent mode (should fail as you cannot mix modes) - TestCase.assertThrows(function() { - const realm4 = new Realm({}); - }); + TestCase.assertThrowsContaining(() => new Realm({}), 'already opened with different inMemory settings.'); }, testRealmConstructorReadOnly: function() { - var realm = new Realm({schema: [schemas.TestObject]}); - realm.write(function() { + let realm = new Realm({schema: [schemas.TestObject]}); + realm.write(() => { realm.create('TestObject', [1]) }); TestCase.assertEqual(realm.readOnly, false); realm.close(); realm = new Realm({readOnly: true, schema: [schemas.TestObject]}); - var objects = realm.objects('TestObject'); + const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.length, 1); TestCase.assertEqual(objects[0].doubleCol, 1.0); TestCase.assertEqual(realm.readOnly, true); - TestCase.assertThrows(function() { - realm.write(function() {}); - }); + TestCase.assertThrowsContaining(() => realm.write(() => {}), + "Can't perform transactions on read-only Realms."); realm.close(); realm = new Realm({readOnly: true}); @@ -217,12 +195,12 @@ module.exports = { }, testDefaultPath: function() { - var defaultPath = Realm.defaultPath; - var defaultRealm = new Realm({schema: []}); + const defaultPath = Realm.defaultPath; + let defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); try { - var newPath = Realm.defaultPath.substring(0, defaultPath.lastIndexOf(pathSeparator) + 1) + 'default2.realm'; + const newPath = `${Realm.defaultPath.substring(0, defaultPath.lastIndexOf(pathSeparator) + 1)}default2.realm`; Realm.defaultPath = newPath; defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, newPath, "should use updated default realm path"); @@ -235,7 +213,7 @@ module.exports = { testRealmSchemaVersion: function() { TestCase.assertEqual(Realm.schemaVersion(Realm.defaultPath), -1); - var realm = new Realm({schema: []}); + let realm = new Realm({schema: []}); TestCase.assertEqual(realm.schemaVersion, 0); TestCase.assertEqual(Realm.schemaVersion(Realm.defaultPath), 0); @@ -245,69 +223,64 @@ module.exports = { }, testRealmWrite: function() { - var realm = new Realm({schema: [schemas.IntPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); + const realm = new Realm({schema: [schemas.IntPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); // exceptions should be propogated - TestCase.assertThrows(function() { - realm.write(function() { - realm.invalid(); - }); - }); + TestCase.assertThrowsContaining(() => realm.write(() => { throw new Error('Inner exception message'); }), + 'Inner exception message'); // writes should be possible after caught exception - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertEqual(1, realm.objects('TestObject').length); - realm.write(function() { + realm.write(() => { // nested transactions not supported - TestCase.assertThrows(function() { - realm.write(function() {}); - }); + TestCase.assertThrowsContaining(() => realm.write(() => {}), + 'The Realm is already in a write transaction'); }); }, testRealmCreate: function() { - var realm = new Realm({schema: [schemas.TestObject]}); + const realm = new Realm({schema: [schemas.TestObject]}); - TestCase.assertThrows(function() { - realm.create('TestObject', {doubleCol: 1}); - }, 'can only create inside a write transaction'); + TestCase.assertThrowsContaining(() => realm.create('TestObject', {doubleCol: 1}), + "Cannot modify managed objects outside of a write transaction."); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); realm.create('TestObject', {doubleCol: 2}); }); - var objects = realm.objects('TestObject'); + const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.length, 2, 'wrong object count'); TestCase.assertEqual(objects[0].doubleCol, 1, 'wrong object property value'); TestCase.assertEqual(objects[1].doubleCol, 2, 'wrong object property value'); }, testRealmCreatePrimaryKey: function() { - var realm = new Realm({schema: [schemas.IntPrimary]}); + const realm = new Realm({schema: [schemas.IntPrimary]}); - realm.write(function() { - var obj0 = realm.create('IntPrimaryObject', { + realm.write(() => { + const obj0 = realm.create('IntPrimaryObject', { primaryCol: 0, valueCol: 'val0', }); - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { realm.create('IntPrimaryObject', { primaryCol: 0, valueCol: 'val0', }); - }, 'cannot create object with conflicting primary key'); + }, "Attempting to create an object of type 'IntPrimaryObject' with an existing primary key value '0'."); realm.create('IntPrimaryObject', { primaryCol: 1, valueCol: 'val1', }, true); - var objects = realm.objects('IntPrimaryObject'); + const objects = realm.objects('IntPrimaryObject'); TestCase.assertEqual(objects.length, 2); realm.create('IntPrimaryObject', { @@ -324,13 +297,13 @@ module.exports = { }, testRealmCreateOptionals: function() { - var realm = new Realm({schema: [schemas.NullableBasicTypes, schemas.LinkTypes, schemas.TestObject]}); - var basic, links; - realm.write(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 (var name in schemas.NullableBasicTypes.properties) { + for (const name in schemas.NullableBasicTypes.properties) { TestCase.assertEqual(basic[name], null); } TestCase.assertEqual(links.objectCol, null); @@ -338,9 +311,9 @@ module.exports = { }, testRealmCreateUpsert: function() { - var realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); - realm.write(function() { - var values = { + const realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); + realm.write(() => { + const values = { primaryCol: '0', boolCol: true, intCol: 1, @@ -353,13 +326,12 @@ module.exports = { arrayCol: [], }; - var obj0 = realm.create('AllTypesObject', values); + const obj0 = realm.create('AllTypesObject', values); - TestCase.assertThrows(function() { - realm.create('AllTypesObject', values); - }, 'cannot create object with conflicting primary key'); + TestCase.assertThrowsContaining(() => realm.create('AllTypesObject', values), + "Attempting to create an object of type 'AllTypesObject' with an existing primary key value '0'."); - var obj1 = realm.create('AllTypesObject', { + const obj1 = realm.create('AllTypesObject', { primaryCol: '1', boolCol: false, intCol: 2, @@ -372,7 +344,7 @@ module.exports = { arrayCol: [{doubleCol: 2}], }, true); - var objects = realm.objects('AllTypesObject'); + const objects = realm.objects('AllTypesObject'); TestCase.assertEqual(objects.length, 2); realm.create('AllTypesObject', { @@ -427,7 +399,7 @@ module.exports = { TestCase.assertEqual(obj1.objectCol, null); // test with string primaries - var obj =realm.create('StringPrimaryObject', { + const obj =realm.create('StringPrimaryObject', { primaryCol: '0', valueCol: 0 }); @@ -442,12 +414,12 @@ module.exports = { }, testRealmWithIndexedProperties: function() { - var realm = new Realm({schema: [schemas.IndexedTypes]}); - realm.write(function() { + const realm = new Realm({schema: [schemas.IndexedTypes]}); + realm.write(() => { realm.create('IndexedTypesObject', {boolCol: true, intCol: 1, stringCol: '1', dateCol: new Date(1)}); }); - var NotIndexed = { + const NotIndexed = { name: 'NotIndexedObject', properties: { floatCol: {type: 'float', indexed: false} @@ -456,23 +428,23 @@ module.exports = { new Realm({schema: [NotIndexed], path: '1.realm'}); - var IndexedSchema = { + const IndexedSchema = { name: 'IndexedSchema', }; - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { IndexedSchema.properties = { floatCol: {type: 'float', indexed: true} }; new Realm({schema: [IndexedSchema], path: '2.realm'}); - }); + }, "Property 'IndexedSchema.floatCol' of type 'float' cannot be indexed."); - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { IndexedSchema.properties = { doubleCol: {type: 'double', indexed: true} } new Realm({schema: [IndexedSchema], path: '3.realm'}); - }); + }, "Property 'IndexedSchema.doubleCol' of type 'double' cannot be indexed."); - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { IndexedSchema.properties = { dataCol: {type: 'data', indexed: true} } new Realm({schema: [IndexedSchema], path: '4.realm'}); - }); + }, "Property 'IndexedSchema.dataCol' of type 'data' cannot be indexed."); // primary key IndexedSchema.properties = { intCol: {type: 'int', indexed: true} }; @@ -483,11 +455,11 @@ module.exports = { }, testRealmCreateWithDefaults: function() { - var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); + let realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); - var createAndTestObject = function() { - var obj = realm.create('DefaultValuesObject', {}); - var properties = schemas.DefaultValues.properties; + const createAndTestObject = () => { + const obj = realm.create('DefaultValuesObject', {}); + const properties = schemas.DefaultValues.properties; TestCase.assertEqual(obj.boolCol, properties.boolCol.default); TestCase.assertEqual(obj.intCol, properties.intCol.default); @@ -510,17 +482,17 @@ module.exports = { }, testRealmCreateWithChangingDefaults: function() { - var objectSchema = { + const objectSchema = { name: 'IntObject', properties: { intCol: {type: 'int', default: 1}, } }; - var realm = new Realm({schema: [objectSchema]}); + let realm = new Realm({schema: [objectSchema]}); - var createAndTestObject = function() { - var object = realm.create('IntObject', {}); + const createAndTestObject = () => { + const object = realm.create('IntObject', {}); TestCase.assertEqual(object.intCol, objectSchema.properties.intCol.default); }; @@ -533,7 +505,7 @@ module.exports = { }, testRealmCreateWithConstructor: function() { - var customCreated = 0; + let customCreated = 0; function CustomObject() { customCreated++; @@ -548,9 +520,8 @@ module.exports = { function InvalidObject() { return {}; } - TestCase.assertThrows(function() { - new Realm({schema: [InvalidObject]}); - }); + TestCase.assertThrowsContaining(() => new Realm({schema: [InvalidObject]}), + "Realm object constructor must have a 'schema' property."); InvalidObject.schema = { name: 'InvalidObject', @@ -559,10 +530,10 @@ module.exports = { } }; - var realm = new Realm({schema: [CustomObject, InvalidObject]}); + let realm = new Realm({schema: [CustomObject, InvalidObject]}); - realm.write(function() { - var object = realm.create('CustomObject', {intCol: 1}); + realm.write(() => { + let object = realm.create('CustomObject', {intCol: 1}); TestCase.assertTrue(object instanceof CustomObject); TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); TestCase.assertEqual(customCreated, 1); @@ -574,21 +545,21 @@ module.exports = { TestCase.assertEqual(customCreated, 2); }); - TestCase.assertThrows(function() { - realm.write(function() { + TestCase.assertThrowsContaining(() => { + realm.write(() => { realm.create('InvalidObject', {intCol: 1}); }); - }); + }, 'Realm object constructor must not return another value'); // Only the original constructor should be valid. function InvalidCustomObject() {} InvalidCustomObject.schema = CustomObject.schema; - TestCase.assertThrows(function() { - realm.write(function() { + TestCase.assertThrowsContaining(() => { + realm.write(() => { realm.create(InvalidCustomObject, {intCol: 1}); }); - }); + }, 'Constructor was not registered in the schema for this Realm'); // The constructor should still work when creating another Realm instance. realm = new Realm(); @@ -605,9 +576,9 @@ module.exports = { } }; - var realm = new Realm({schema: [CustomObject]}); - realm.write(function() { - var object = realm.create('CustomObject', {intCol: 1}); + let realm = new Realm({schema: [CustomObject]}); + realm.write(() => { + const object = realm.create('CustomObject', {intCol: 1}); TestCase.assertTrue(object instanceof CustomObject); }); @@ -615,30 +586,28 @@ module.exports = { NewCustomObject.schema = CustomObject.schema; realm = new Realm({schema: [NewCustomObject]}); - realm.write(function() { - var object = realm.create('CustomObject', {intCol: 1}); + realm.write(() => { + const object = realm.create('CustomObject', {intCol: 1}); TestCase.assertTrue(object instanceof NewCustomObject); }); }, testRealmDelete: function() { - var realm = new Realm({schema: [schemas.TestObject]}); + const realm = new Realm({schema: [schemas.TestObject]}); - realm.write(function() { - for (var i = 0; i < 10; i++) { + realm.write(() => { + for (let i = 0; i < 10; i++) { realm.create('TestObject', {doubleCol: i}); } }); - var objects = realm.objects('TestObject'); - TestCase.assertThrows(function() { - realm.delete(objects[0]); - }, 'can only delete in a write transaction'); + const objects = realm.objects('TestObject'); + TestCase.assertThrowsContaining(() => realm.delete(objects[0]), + "Can only delete objects within a transaction."); - realm.write(function() { - TestCase.assertThrows(function() { - realm.delete(); - }); + realm.write(() => { + TestCase.assertThrowsContaining(() => realm.delete(), + "object must be of type 'object', got (undefined)"); realm.delete(objects[0]); TestCase.assertEqual(objects.length, 9, 'wrong object count'); @@ -650,24 +619,23 @@ module.exports = { TestCase.assertEqual(objects[0].doubleCol, 7, "wrong property value"); TestCase.assertEqual(objects[1].doubleCol, 8, "wrong property value"); - var threeObjects = realm.objects('TestObject').filtered("doubleCol < 5"); + const threeObjects = realm.objects('TestObject').filtered("doubleCol < 5"); TestCase.assertEqual(threeObjects.length, 3, "wrong results count"); realm.delete(threeObjects); TestCase.assertEqual(objects.length, 4, 'wrong object count'); TestCase.assertEqual(threeObjects.length, 0, 'threeObject should have been deleted'); - var o = objects[0]; + const o = objects[0]; realm.delete(o); - TestCase.assertThrows(function() { - realm.delete(o); - }); + TestCase.assertThrowsContaining(() => realm.delete(o), + 'Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.'); }); }, testDeleteAll: function() { - var realm = new Realm({schema: [schemas.TestObject, schemas.IntPrimary]}); + const realm = new Realm({schema: [schemas.TestObject, schemas.IntPrimary]}); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); realm.create('TestObject', {doubleCol: 2}); realm.create('IntPrimaryObject', {primaryCol: 2, valueCol: 'value'}); @@ -676,11 +644,10 @@ module.exports = { TestCase.assertEqual(realm.objects('TestObject').length, 2); TestCase.assertEqual(realm.objects('IntPrimaryObject').length, 1); - TestCase.assertThrows(function() { - realm.deleteAll(); - }, 'can only deleteAll in a write transaction'); + TestCase.assertThrowsContaining(() => realm.deleteAll(), + "Can only delete objects within a transaction."); - realm.write(function() { + realm.write(() => { realm.deleteAll(); }); @@ -689,9 +656,9 @@ module.exports = { }, testRealmObjects: function() { - var realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); + const realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); - realm.write(function() { + realm.write(() => { realm.create('PersonObject', {name: 'Ari', age: 10}); realm.create('PersonObject', {name: 'Tim', age: 11}); realm.create('PersonObject', {name: 'Bjarne', age: 12}); @@ -699,77 +666,45 @@ module.exports = { }); // Should be able to pass constructor for getting objects. - var objects = realm.objects(schemas.PersonObject); + const objects = realm.objects(schemas.PersonObject); TestCase.assertTrue(objects[0] instanceof schemas.PersonObject); function InvalidPerson() {} InvalidPerson.schema = schemas.PersonObject.schema; - TestCase.assertThrows(function() { - realm.objects(); - }); - TestCase.assertThrows(function() { - realm.objects([]); - }); - TestCase.assertThrows(function() { - realm.objects('InvalidClass'); - }); - TestCase.assertThrows(function() { - realm.objects('PersonObject', 'truepredicate'); - }); - TestCase.assertThrows(function() { - realm.objects(InvalidPerson); - }); + TestCase.assertThrowsContaining(() => realm.objects(), "objectType must be of type 'string', got (undefined)"); + TestCase.assertThrowsContaining(() => realm.objects([]), "objectType must be of type 'string', got ()"); + TestCase.assertThrowsContaining(() => realm.objects('InvalidClass'), "Object type 'InvalidClass' not found in schema."); + TestCase.assertThrowsContaining(() => realm.objects('PersonObject', 'truepredicate'), + "Invalid arguments: at most 1 expected, but 2 supplied."); + TestCase.assertThrowsContaining(() => realm.objects(InvalidPerson), + 'Constructor was not registered in the schema for this Realm'); - var person = realm.objects('PersonObject')[0]; - var listenerCallback = () => {}; + const person = realm.objects('PersonObject')[0]; + const listenerCallback = () => {}; realm.addListener('change', listenerCallback); // The tests below assert that everthing throws when // operating on a closed realm realm.close(); - TestCase.assertThrows(function() { - console.log("Name: ", person.name); - }); + TestCase.assertThrowsContaining(() => console.log("Name: ", person.name), + 'Accessing object of type PersonObject which has been invalidated or deleted'); - TestCase.assertThrows(function() { - realm.objects('PersonObject'); - }); - - TestCase.assertThrows(function() { - realm.addListener('change', () => {}); - }); - - TestCase.assertThrows(function() { - realm.create('PersonObject', {name: 'Ari', age: 10}); - }); - - TestCase.assertThrows(function() { - realm.delete(person); - }); - - TestCase.assertThrows(function() { - realm.deleteAll(); - }); - - TestCase.assertThrows(function() { - realm.write(() => {}); - }); - - TestCase.assertThrows(function() { - realm.removeListener('change', listenerCallback); - }); - - TestCase.assertThrows(function() { - realm.removeAllListeners(); - }); + TestCase.assertThrowsContaining(() => realm.objects('PersonObject'), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.addListener('change', () => {}), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.create('PersonObject', {name: 'Ari', age: 10}), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.delete(person), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.deleteAll(), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.write(() => {}), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.removeListener('change', listenerCallback), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.removeAllListeners(), 'Cannot access realm that has been closed'); }, testRealmObjectForPrimaryKey: function() { - var realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.TestObject]}); + const realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.TestObject]}); - realm.write(function() { + realm.write(() => { realm.create('IntPrimaryObject', {primaryCol: 0, valueCol: 'val0'}); realm.create('IntPrimaryObject', {primaryCol: 1, valueCol: 'val1'}); @@ -789,36 +724,32 @@ module.exports = { TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', 'val0').valueCol, 0); TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', 'val1').valueCol, 1); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey('TestObject', 0); - }); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey(); - }); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey('IntPrimary'); - }); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey('InvalidClass', 0); - }); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('TestObject', 0), + "'TestObject' does not have a primary key defined"); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey(), + "objectType must be of type 'string', got (undefined)"); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('IntPrimaryObject'), + "Invalid null value for non-nullable primary key."); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('InvalidClass', 0), + "Object type 'InvalidClass' not found in schema."); }, testNotifications: function() { - var realm = new Realm({schema: []}); - var notificationCount = 0; - var notificationName; + const realm = new Realm({schema: []}); + let notificationCount = 0; + let notificationName; - realm.addListener('change', function(realm, name) { + realm.addListener('change', (realm, name) => { notificationCount++; notificationName = name; }); TestCase.assertEqual(notificationCount, 0); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 1); TestCase.assertEqual(notificationName, 'change'); - var secondNotificationCount = 0; + let secondNotificationCount = 0; function secondNotification() { secondNotificationCount++; } @@ -827,39 +758,37 @@ module.exports = { realm.addListener('change', secondNotification); realm.addListener('change', secondNotification); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 2); TestCase.assertEqual(secondNotificationCount, 1); realm.removeListener('change', secondNotification); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 3); TestCase.assertEqual(secondNotificationCount, 1); realm.removeAllListeners(); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 3); TestCase.assertEqual(secondNotificationCount, 1); - TestCase.assertThrows(function() { - realm.addListener('invalid', function() {}); + TestCase.assertThrowsContaining(() => realm.addListener('invalid', () => {}), + "Only the 'change' notification name is supported."); + + realm.addListener('change', () => { + throw new Error('expected error message'); }); - realm.addListener('change', function() { - throw new Error('error'); - }); - - TestCase.assertThrows(function() { - realm.write(function() {}); - }); + TestCase.assertThrowsContaining(() => realm.write(() => {}), + 'expected error message'); }, testSchema: function() { - var originalSchema = [schemas.TestObject, schemas.BasicTypes, schemas.NullableBasicTypes, schemas.IndexedTypes, schemas.IntPrimary, + const originalSchema = [schemas.TestObject, schemas.BasicTypes, schemas.NullableBasicTypes, schemas.IndexedTypes, schemas.IntPrimary, schemas.PersonObject, schemas.LinkTypes, schemas.LinkingObjectsObject]; - var schemaMap = {}; - originalSchema.forEach(function(objectSchema) { + const schemaMap = {}; + originalSchema.forEach(objectSchema => { if (objectSchema.schema) { // for PersonObject schemaMap[objectSchema.schema.name] = objectSchema; } else { @@ -867,9 +796,9 @@ module.exports = { } }); - var realm = new Realm({schema: originalSchema}); + const realm = new Realm({schema: originalSchema}); - var schema = realm.schema; + const schema = realm.schema; TestCase.assertEqual(schema.length, originalSchema.length); function isString(val) { @@ -877,15 +806,15 @@ module.exports = { } function verifyObjectSchema(returned) { - var original = schemaMap[returned.name]; + let original = schemaMap[returned.name]; if (original.schema) { original = original.schema; } TestCase.assertEqual(returned.primaryKey, original.primaryKey); - for (var propName in returned.properties) { - var prop1 = returned.properties[propName]; - var prop2 = original.properties[propName]; + 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); @@ -908,7 +837,7 @@ module.exports = { } } - for (var i = 0; i < originalSchema.length; i++) { + for (let i = 0; i < originalSchema.length; i++) { verifyObjectSchema(schema[i]); } }, @@ -916,12 +845,12 @@ module.exports = { testCopyBundledRealmFiles: function() { Realm.copyBundledRealmFiles(); - var realm = new Realm({path: 'dates-v5.realm', schema: [schemas.DateObject]}); + let realm = new Realm({path: 'dates-v5.realm', schema: [schemas.DateObject]}); TestCase.assertEqual(realm.objects('Date').length, 2); TestCase.assertEqual(realm.objects('Date')[0].currentDate.getTime(), 1462500087955); - var newDate = new Date(1); - realm.write(function() { + const newDate = new Date(1); + realm.write(() => { realm.objects('Date')[0].currentDate = newDate; }); realm.close(); @@ -933,33 +862,33 @@ module.exports = { }, testErrorMessageFromInvalidWrite: function() { - var realm = new Realm({schema: [schemas.PersonObject]}); + const realm = new Realm({schema: [schemas.PersonObject]}); - TestCase.assertThrowsException(function() { - realm.write(function () { - var p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); + TestCase.assertThrowsException(() => { + realm.write(() => { + const p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); p1.age = "Ten"; }); }, new Error("PersonObject.age must be of type 'number', got (Ten)")); }, testErrorMessageFromInvalidCreate: function() { - var realm = new Realm({schema: [schemas.PersonObject]}); + const realm = new Realm({schema: [schemas.PersonObject]}); - TestCase.assertThrowsException(function() { - realm.write(function () { - var p1 = realm.create('PersonObject', { name: 'Ari', age: 'Ten' }); + TestCase.assertThrowsException(() => { + realm.write(() => { + const p1 = realm.create('PersonObject', { name: 'Ari', age: 'Ten' }); }); }, new Error("PersonObject.age must be of type 'number', got (Ten)")); }, testValidTypesForListProperties: function() { - var realm = new Realm({schema: [schemas.PersonObject]}); - realm.write(function () { - var p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); - var p2 = realm.create('PersonObject', { name: 'Harold', age: 55, children: realm.objects('PersonObject').filtered('age < 15') }); + const realm = new Realm({schema: [schemas.PersonObject]}); + realm.write(() => { + const p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); + const p2 = realm.create('PersonObject', { name: 'Harold', age: 55, children: realm.objects('PersonObject').filtered('age < 15') }); TestCase.assertEqual(p2.children.length, 1); - var p3 = realm.create('PersonObject', { name: 'Wendy', age: 52, children: p2.children }); + const p3 = realm.create('PersonObject', { name: 'Wendy', age: 52, children: p2.children }); TestCase.assertEqual(p3.children.length, 1); }); }, @@ -1007,13 +936,13 @@ module.exports = { }, testCompact: function() { - var wasCalled = false; + let wasCalled = false; const count = 1000; // create compactable Realm const realm1 = new Realm({schema: [schemas.StringOnly]}); realm1.write(() => { realm1.create('StringOnlyObject', { stringCol: 'A' }); - for (var i = 0; i < count; i++) { + for (let i = 0; i < count; i++) { realm1.create('StringOnlyObject', { stringCol: 'ABCDEFG' }); } realm1.create('StringOnlyObject', { stringCol: 'B' }); @@ -1021,7 +950,7 @@ module.exports = { realm1.close(); // open Realm and see if it is compacted - var shouldCompact = function(totalBytes, usedBytes) { + const shouldCompact = (totalBytes, usedBytes) => { wasCalled = true; const fiveHundredKB = 500*1024; return (totalBytes > fiveHundredKB) && (usedBytes / totalBytes) < 0.2; @@ -1049,9 +978,9 @@ module.exports = { testManualCompactInWrite: function() { const realm = new Realm({schema: [schemas.StringOnly]}); realm.write(() => { - TestCase.assertThrows(() => { + TestCase.assertThrowsContaining(() => { realm.compact(); - }); + }, 'Cannot compact a Realm within a transaction.'); }); TestCase.assertTrue(realm.empty); }, @@ -1059,14 +988,14 @@ module.exports = { testManualCompactMultipleInstances: function() { const realm1 = new Realm({schema: [schemas.StringOnly]}); const realm2 = new Realm({schema: [schemas.StringOnly]}); - TestCase.assertThrows(realm1.compact()); + TestCase.assertTrue(realm1.compact()); }, testRealmDeleteFileDefaultConfigPath: function() { const config = {schema: [schemas.TestObject]}; const realm = new Realm(config); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); @@ -1084,7 +1013,7 @@ module.exports = { const config = {schema: [schemas.TestObject], path: 'test-realm-delete-file.realm'}; const realm = new Realm(config); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index b7b83159..ad7395e2 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -383,23 +383,23 @@ module.exports = { TestCase.assertEqual(snapshot.length, 0); }); }, - + testResultsFindIndexOfObject: function() { var realm = new Realm({schema: [schemas.TestObject]}); - + var object1, object2, object3; realm.write(function() { object1 = realm.create('TestObject', {doubleCol: 1}); object2 = realm.create('TestObject', {doubleCol: 2}); object3 = realm.create('TestObject', {doubleCol: 2}); }); - + // Search in base table const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.indexOf(object1), 0); TestCase.assertEqual(objects.indexOf(object2), 1); TestCase.assertEqual(objects.indexOf(object3), 2); - + // Search in filtered query const results = objects.filtered("doubleCol == 2"); TestCase.assertEqual(results.indexOf(object1), -1); @@ -408,7 +408,7 @@ module.exports = { const nonRealmObject = {test: "this is an object"}; TestCase.assertEqual(objects.indexOf(nonRealmObject), -1); - + // Searching for object from the wrong realm var realm2 = new Realm({path: '2.realm', schema: realm.schema}); var object4; @@ -421,29 +421,40 @@ module.exports = { }, testAddListener: function() { - return new Promise((resolve, _reject) => { - var realm = new Realm({ schema: [schemas.TestObject] }); + if (typeof navigator !== 'undefined' && /Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef + // FIXME: async callbacks do not work correctly in Chrome debugging mode + return; + } - realm.write(() => { - realm.create('TestObject', { doubleCol: 1 }); - realm.create('TestObject', { doubleCol: 2 }); - realm.create('TestObject', { doubleCol: 3 }); - }); + const realm = new Realm({ schema: [schemas.TestObject] }); + realm.write(() => { + realm.create('TestObject', { doubleCol: 1 }); + realm.create('TestObject', { doubleCol: 2 }); + realm.create('TestObject', { doubleCol: 3 }); + }); + let resolve, first = true; + return new Promise((r, _reject) => { + resolve = r; realm.objects('TestObject').addListener((testObjects, changes) => { - // TODO: First notification is empty, so perform these - // assertions on the second call. However, there is a race condition - // in React Native, so find a way to do this in a robust way. - //TestCase.assertEqual(testObjects.length, 4); - //TestCase.assertEqual(changes.insertions.length, 1); + if (first) { + TestCase.assertEqual(testObjects.length, 3); + TestCase.assertEqual(changes.insertions.length, 0); + } + else { + TestCase.assertEqual(testObjects.length, 4); + TestCase.assertEqual(changes.insertions.length, 1); + } + first = false; resolve(); }); - - realm.write(() => { - realm.create('TestObject', { doubleCol: 1 }); + }).then(() => { + return new Promise((r, _reject) => { + realm.write(() => { + realm.create('TestObject', { doubleCol: 1 }); + }); + resolve = r; }); - }) + }); } - - }; diff --git a/tests/react-test-app/ios/ReactTests/RealmReactTests.m b/tests/react-test-app/ios/ReactTests/RealmReactTests.m index 746e31a6..27bacbe8 100644 --- a/tests/react-test-app/ios/ReactTests/RealmReactTests.m +++ b/tests/react-test-app/ios/ReactTests/RealmReactTests.m @@ -168,6 +168,7 @@ extern NSMutableArray *RCTGetModuleClasses(void); + (void)waitForCondition:(BOOL *)condition description:(NSString *)description { NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:30.0]; + RCTBridge *bridge = [self currentBridge]; while (!*condition) { if ([timeout timeIntervalSinceNow] < 0) { @@ -180,6 +181,7 @@ extern NSMutableArray *RCTGetModuleClasses(void); [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [NSThread sleepForTimeInterval:0.01]; // Bad things may happen without some sleep. + [bridge.eventDispatcher sendAppEventWithName:@"realm-dummy" body:nil]; // Ensure RN has an event loop running } } }