diff --git a/ReactWindows/ReactNative.Shared/Bridge/Promise.cs b/ReactWindows/ReactNative.Shared/Bridge/Promise.cs
new file mode 100644
index 00000000000..b10eecf98cb
--- /dev/null
+++ b/ReactWindows/ReactNative.Shared/Bridge/Promise.cs
@@ -0,0 +1,69 @@
+using Newtonsoft.Json.Linq;
+using System;
+
+namespace ReactNative.Bridge
+{
+ class Promise : IPromise
+ {
+ private const string DefaultError = "EUNSPECIFIED";
+
+ private readonly ICallback _resolve;
+ private readonly ICallback _reject;
+
+ public Promise(ICallback resolve, ICallback reject)
+ {
+ _resolve = resolve;
+ _reject = reject;
+ }
+
+ public void Resolve(object value)
+ {
+ if (_resolve != null)
+ {
+ _resolve.Invoke(value);
+ }
+ }
+
+ public void Reject(string code, string message)
+ {
+ Reject(code, message, default(Exception));
+ }
+
+ public void Reject(string message)
+ {
+ Reject(DefaultError, message, default(Exception));
+ }
+
+ public void Reject(string code, Exception e)
+ {
+ Reject(code, e.Message, e);
+ }
+
+ public void Reject(Exception e)
+ {
+ if (e == null)
+ throw new ArgumentNullException(nameof(e));
+
+ Reject(DefaultError, e.Message, e);
+ }
+
+ public void Reject(string code, string message, Exception e)
+ {
+ if (_reject != null)
+ {
+ var errorData = e?.Data;
+ var userInfo = errorData != null
+ ? JToken.FromObject(errorData)
+ : null;
+ _reject.Invoke(new JObject
+ {
+ { "code", code ?? DefaultError },
+ { "message", message },
+ { "stack", e?.StackTrace },
+ { "userInfo", userInfo },
+ });
+ }
+ }
+ }
+
+}
diff --git a/ReactWindows/ReactNative.Shared/Bridge/ReactDelegateFactoryBase.cs b/ReactWindows/ReactNative.Shared/Bridge/ReactDelegateFactoryBase.cs
index 99fa33ee5c6..4db8ca63c21 100644
--- a/ReactWindows/ReactNative.Shared/Bridge/ReactDelegateFactoryBase.cs
+++ b/ReactWindows/ReactNative.Shared/Bridge/ReactDelegateFactoryBase.cs
@@ -1,6 +1,7 @@
using Newtonsoft.Json.Linq;
using ReactNative.Bridge;
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -141,63 +142,5 @@ public void Invoke(params object[] arguments)
_instance.InvokeCallback(_id, JArray.FromObject(arguments ?? s_empty));
}
}
-
- class Promise : IPromise
- {
- private const string DefaultError = "EUNSPECIFIED";
-
- private readonly ICallback _resolve;
- private readonly ICallback _reject;
-
- public Promise(ICallback resolve, ICallback reject)
- {
- _resolve = resolve;
- _reject = reject;
- }
-
- public void Resolve(object value)
- {
- if (_resolve != null)
- {
- _resolve.Invoke(value);
- }
- }
-
- public void Reject(string code, string message)
- {
- Reject(code, message, default(Exception));
- }
-
- public void Reject(string message)
- {
- Reject(DefaultError, message, default(Exception));
- }
-
- public void Reject(string code, Exception e)
- {
- Reject(code, e.Message, e);
- }
-
- public void Reject(Exception e)
- {
- if (e == null)
- throw new ArgumentNullException(nameof(e));
-
- Reject(DefaultError, e.Message, e);
- }
-
- public void Reject(string code, string message, Exception e)
- {
- if (_reject != null)
- {
- _reject.Invoke(new JObject
- {
- { "code", code ?? DefaultError },
- { "message", message },
- { "stack", e?.StackTrace },
- });
- }
- }
- }
}
}
diff --git a/ReactWindows/ReactNative.Shared/ReactNative.Shared.projitems b/ReactWindows/ReactNative.Shared/ReactNative.Shared.projitems
index 1875c871059..cad6e5edb8a 100644
--- a/ReactWindows/ReactNative.Shared/ReactNative.Shared.projitems
+++ b/ReactWindows/ReactNative.Shared/ReactNative.Shared.projitems
@@ -30,6 +30,7 @@
+
diff --git a/ReactWindows/ReactNative.Tests/Bridge/PromiseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/PromiseTests.cs
new file mode 100644
index 00000000000..fecc493869b
--- /dev/null
+++ b/ReactWindows/ReactNative.Tests/Bridge/PromiseTests.cs
@@ -0,0 +1,89 @@
+using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
+using Newtonsoft.Json.Linq;
+using ReactNative.Bridge;
+using System;
+using System.Threading;
+
+namespace ReactNative.Tests.Bridge
+{
+ [TestClass]
+ public class PromiseTests
+ {
+ [TestMethod]
+ public void Promise_Resolve()
+ {
+ var args = default(object[]);
+ var are = new AutoResetEvent(false);
+ var resolve = new MockCallback(a =>
+ {
+ args = a;
+ are.Set();
+ });
+ var reject = new MockCallback(_ => { });
+ var promise = new Promise(resolve, reject);
+
+ var o = new object();
+ promise.Resolve(o);
+ are.WaitOne();
+
+ Assert.IsNotNull(args);
+ Assert.AreEqual(1, args.Length);
+ Assert.AreSame(o, args[0]);
+ }
+
+ [TestMethod]
+ public void Promise_Reject()
+ {
+ var args = default(object[]);
+ var are = new AutoResetEvent(false);
+ var resolve = new MockCallback(_ => { });
+ var reject = new MockCallback(a =>
+ {
+ args = a;
+ are.Set();
+ });
+ var promise = new Promise(resolve, reject);
+
+ var code = "42";
+ var message = "foo";
+ promise.Reject(code, message);
+ are.WaitOne();
+ Assert.IsNotNull(args);
+ Assert.AreEqual(1, args.Length);
+
+ var json = args[0] as JObject;
+ Assert.IsNotNull(json);
+ Assert.AreEqual(code, json["code"]);
+ Assert.AreEqual(message, json["message"]);
+ }
+
+ [TestMethod]
+ public void Promise_Reject_UserInfo()
+ {
+ var args = default(object[]);
+ var are = new AutoResetEvent(false);
+ var resolve = new MockCallback(_ => { });
+ var reject = new MockCallback(a =>
+ {
+ args = a;
+ are.Set();
+ });
+ var promise = new Promise(resolve, reject);
+
+ var code = "42";
+ var message = "foo";
+ var e = new Exception();
+ e.Data.Add("qux", "baz");
+ promise.Reject(code, message, e);
+ are.WaitOne();
+
+ Assert.IsNotNull(args);
+ Assert.AreEqual(1, args.Length);
+ var json = args[0] as JObject;
+ Assert.IsNotNull(json);
+ var userInfo = json["userInfo"] as JObject;
+ Assert.IsNotNull(userInfo);
+ Assert.AreEqual("baz", userInfo["qux"]);
+ }
+ }
+}
diff --git a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj
index 380019b4579..af1105eda22 100644
--- a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj
+++ b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj
@@ -95,6 +95,7 @@
+