@qooxdoo/framework
Version:
The JS Framework for Coders
807 lines (748 loc) • 23.3 kB
JavaScript
qx.Class.define("qx.test.Promise", {
extend: qx.dev.unit.TestCase,
members: {
/**
* Tests a new promise that resolves with no errors
*/
testNewPromise: function() {
var self = this;
var p = new qx.Promise(function(resolve, reject) {
setTimeout(function() {
resolve("ok");
});
}, this);
p.then(function(value) {
this.assertIdentical(this, self);
this.assertEquals(value, "ok");
this.resume();
}, function(err) {
this.assertTrue(false);
this.resume();
});
this.wait(1000);
},
/**
* Tests a new promise that is rejected
*/
testReject: function() {
var self = this;
var p = new qx.Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error("oops"));
});
}, this);
p.then(function(value) {
this.assertTrue(false);
this.resume();
}, function(err) {
this.assertIdentical(this, self);
this.assertEquals(err.message, "oops");
this.resume();
});
this.wait(1000);
},
testCatchFinally: function() {
var caughtException = null;
qx.Promise.resolve()
.then(function() {
throw new Error("oops");
}).catch(function(ex) {
caughtException = ex;
}).finally(function() {
this.assertNotNull(caughtException);
this.resume();
}, this);
this.wait(1000);
},
/**
* Tests the qx.Promise.allOf method
*/
testAllOf: function() {
var t = this;
var dt = new Date();
var obj = {
a: new qx.Promise(),
b: new qx.Promise(),
c: new qx.Promise(),
d: "four",
e: dt
}
qx.Promise.allOf(obj)
.then(function(obj2) {
t.assertTrue(obj === obj2);
t.assertEquals("one", obj.a);
t.assertEquals("two", obj.b);
t.assertEquals("three", obj.c);
t.assertEquals("four", obj.d);
t.assertTrue(obj.e === dt);
t.resume();
});
obj.a.then(function() {
obj.b.resolve("two");
});
obj.b.then(function() {
obj.c.resolve("three");
});
obj.a.resolve("one");
t.wait(1000);
},
/**
* Tests that setting a property value with a promise will delay setting the
* value until the promise is resolved. In this case, the property is *not*
* marked as async and the setXxx method is used
*/
testPropertySetValueAsPromise1: function() {
var t = this;
var Clazz = qx.Class.define("testPropertySetValueAsPromise1.Clazz", {
extend: qx.core.Object,
properties: {
alpha: {
init: null,
nullable: true
}
}
});
this.assertTrue(!!Clazz.prototype.setAlpha);
this.assertFalse(!!Clazz.prototype.setAlphaAsync);
var obj = new Clazz();
var p = new qx.Promise(function(resolve) {
resolve(123);
});
obj.setAlpha(p);
p.then(function() {
t.assertEquals(123, obj.getAlpha());
qx.Class.undefine("testPropertySetValueAsPromise1.Clazz");
t.resume();
});
this.wait(1000);
},
/**
* Tests that setting a property value with a promise will delay setting the
* value until the promise is resolved. In this case, the property *is*
* marked as async and the setXxxAsync method is used to test chaining
*/
testPropertySetValueAsPromise2: function() {
var t = this;
var Clazz = qx.Class.define("testPropertySetValueAsPromise2.Clazz", {
extend: qx.core.Object,
properties: {
alpha: {
init: null,
nullable: true,
async: true
}
}
});
this.assertTrue(!!Clazz.prototype.setAlpha);
this.assertTrue(!!Clazz.prototype.setAlphaAsync);
var obj = new Clazz();
var p = new qx.Promise(function(resolve) {
resolve(123);
});
obj.setAlphaAsync(p).then(function() {
t.assertEquals(123, obj.getAlpha());
qx.Class.undefine("testPropertySetValueAsPromise2.Clazz");
t.resume();
});
this.wait(1000);
},
/**
* Tests that a property apply method can return a promise; in this case, the
* property is not marked as async so the apply method is only able to delay
* the event handler
*/
testPropertySetValueAsyncApply1: function() {
var t = this;
var p;
var Clazz = qx.Class.define("testPropertySetValueAsyncApply1.Clazz", {
extend: qx.core.Object,
properties: {
alpha: {
init: null,
nullable: true,
apply: "_applyAlpha",
event: "changeAlpha"
}
},
members: {
_applyAlpha: function(value, oldValue) {
return p = new qx.Promise(function(resolve) {
setTimeout(function() {
resolve("xyz");
}, 250);
});
}
}
});
var obj = new Clazz();
var eventFired = 0;
obj.addListener("changeAlpha", function(evt) {
eventFired++;
});
obj.setAlpha("abc");
this.assertTrue(!!p);
this.assertEquals(0, eventFired);
this.assertEquals("abc", obj.getAlpha());
p.then(function(value) {
this.assertEquals("xyz", value); // "xyz" because this is the internal promise
this.assertEquals("abc", obj.getAlpha());
this.assertEquals(1, eventFired);
qx.Class.undefine("testPropertySetValueAsyncApply1.Clazz");
t.resume();
}, this);
this.wait(1000);
},
/**
* Tests that a property apply method can return a promise; in this case, the
* property *is* marked as async, and we use the setAlphaAsync to test chaining
*/
testPropertySetValueAsyncApply2: function() {
var t = this;
var Clazz = qx.Class.define("testPropertySetValueAsyncApply2.Clazz", {
extend: qx.core.Object,
properties: {
alpha: {
init: null,
nullable: true,
async: true,
apply: "_applyAlpha",
event: "changeAlpha"
}
},
members: {
_applyAlpha: function(value, oldValue) {
return new qx.Promise(function(resolve) {
setTimeout(function() {
resolve("xyz");
}, 250);
});
}
}
});
var obj = new Clazz();
var eventFired = 0;
obj.addListener("changeAlpha", function(evt) {
eventFired++;
});
var p = obj.setAlphaAsync("abc");
this.assertEquals(0, eventFired);
p.then(function(value) {
this.assertEquals("abc", value);
this.assertEquals("abc", obj.getAlpha());
this.assertEquals(1, eventFired);
// Set the same value, should return a new promise but not fire an event
p = obj.setAlphaAsync("abc");
p.then(function(value) {
this.assertEquals("abc", value);
this.assertEquals("abc", obj.getAlpha());
this.assertEquals(1, eventFired);
qx.Class.undefine("testPropertySetValueAsyncApply2.Clazz");
t.resume();
}, this);
}, this);
this.wait(1000);
},
/**
* Tests that a property apply method can take a promise
*/
testPropertySetValueAsyncApply3: function() {
var t = this;
var Clazz = qx.Class.define("testPropertySetValueAsyncApply3.Clazz", {
extend: qx.core.Object,
properties: {
alpha: {
init: null,
nullable: true,
check: "qx.Promise"
}
}
});
var obj = new Clazz();
var p = qx.Promise.resolve("hello");
obj.setAlpha(p);
this.assertEquals(p, obj.getAlpha());
qx.Class.undefine("testPropertySetValueAsyncApply3.Clazz");
},
testBinding: function() {
var t = this;
var AsyncClazz = qx.Class.define("testBinding.AsyncClazz", {
extend: qx.core.Object,
properties: {
alpha: {
init: null,
nullable: true,
async: true,
event: "changeAlpha"
}
},
members: {
_applyAlpha: function(value, oldValue) {
return new qx.Promise(function(resolve) {
setTimeout(resolve, 250);
});
}
}
});
var SyncClazz = qx.Class.define("testBinding.SyncClazz", {
extend: qx.core.Object,
properties: {
bravo: {
init: null,
nullable: true,
event: "changeBravo"
}
}
});
/*
* Test binding an async property to a "normal" sync property
*/
var asyncToSync = new qx.Promise(function(resolve) {
var asyncObj = new AsyncClazz();
var syncObj = new SyncClazz();
var p1 = new qx.Promise();
asyncObj.addListenerOnce("changeAlphaAsync", function(evt) {
var data = evt.getData();
this.assertTrue(data instanceof qx.Promise);
p1.resolve();
}, this);
var p2 = new qx.Promise();
var bravoEvents = 0;
var id = syncObj.addListener("changeBravo", function(evt) {
bravoEvents++;
this.assertTrue(bravoEvents <= 2);
var data = evt.getData();
// First event is .bind() setting the initial value
if (bravoEvents == 1) {
this.assertNull(data);
// Second event was caused by asyncObj.setAlphaAsync()
} else if (bravoEvents == 2) {
this.assertEquals("zyx", data);
syncObj.removeListenerById(id);
p2.resolve();
}
}, this);
asyncObj.getAlphaAsync();
asyncObj.bind("alphaAsync", syncObj, "bravo");
asyncObj.setAlphaAsync("zyx");
qx.Promise.all([p1, p2]).then(function() {
var p3 = new qx.Promise();
syncObj.addListenerOnce("changeBravo", function(evt) {
var data = evt.getData();
this.assertEquals("wvu", data);
p3.resolve();
}, this);
asyncObj.setAlphaAsync("wvu");
p3.then(function() {
this.resume();
}, this);
}, this);
}, this);
/*
* Test binding a "normal" sync property to an async property
*/
asyncToSync.then(function() {
var asyncObj = new AsyncClazz();
var syncObj = new SyncClazz();
var p1 = new qx.Promise();
asyncObj.addListenerOnce("changeAlphaAsync", function(evt) {
var data = evt.getData();
this.assertEquals("def", data);
p1.resolve();
}, this);
syncObj.bind("bravo", asyncObj, "alphaAsync");
syncObj.setBravo("def");
p1.then(function() {
var p2 = new qx.Promise();
asyncObj.addListenerOnce("changeAlphaAsync", function(evt) {
var data = evt.getData();
this.assertEquals("ghi", data);
p2.resolve();
}, this);
syncObj.setBravo("ghi");
return p2.then(function() {
qx.Class.undefine("testBinding.AsyncClazz");
qx.Class.undefine("testBinding.SyncClazz");
this.resume();
}, this);
}, this);
}, this);
this.wait(1000);
},
/**
* Tests event handlers bound to the "changeXxxAsync" events, and which return
* a promise. Event handlers must be triggered in sequence and by returning
* a promise will defer subsequent event handlers from firing
*/
testAsyncEventHandlers: function() {
var Clazz = qx.Class.define("testAsyncEventHandlers.Clazz", {
extend: qx.core.Object,
properties: {
value: {
},
alpha: {
init: null,
nullable: true,
async: true,
apply: "_applyAlpha",
event: "changeAlpha"
},
bravo: {
init: null,
nullable: true,
async: true,
apply: "_applyBravo",
event: "changeBravo"
}
},
members: {
_applyAlpha: function(value, oldValue) {
var p = new qx.Promise(function(resolve) {
console.log("in _applyAlpha qx.Promise, value=" + value);
setTimeout(function() {
console.log("in _applyAlpha resolving qx.Promise, value=" + value);
resolve("xyz");
}, 50);
});
console.log("in _applyAlpha, value=" + value + ", p=" + p);
return p;
},
_applyBravo: function(value, oldValue) {
return new qx.Promise(function(resolve) {
setTimeout(function() {
resolve("uvw");
}, 50);
});
}
}
});
function createObj(name) {
var obj = new Clazz().set({ value: name });
obj.addListener("changeAlphaAsync", function(evt) {
var value = evt.getData();
var p = new qx.Promise(function(resolve) {
console.log(name + ": changeAlphaAsync 1 in qx.Promise, value=" + value);
setTimeout(function() {
if (str.length)
str += ",";
str += name;
console.log(name + ": changeAlphaAsync 1 resolving qx.Promise, value=" + value);
resolve();
}, 200);
}).then(function() {
console.log(name + ": changeAlphaAsync 1 resolved qx.Promise, value=" + value);
});
console.log(name + ": changeAlphaAsync 1, value=" + value + ", p=" + p);
return p;
});
return obj;
}
var objOne = createObj("one");
var objTwo = createObj("two");
var str = "";
objOne.addListener("changeAlphaAsync", function(evt) {
var value = evt.getData();
console.log("objOne.alphaAsync setting, value=" + value);
return objTwo.setAlphaAsync("def").then(function() {
str += "xxx";
console.log("objOne.alphaAsync done, value=" + value);
});
});
console.log("objOne.alphaAsync going to set value=abc");
objOne.setAlphaAsync("abc").then(function() {
console.log("objOne.alphaAsync completed set value=abc");
this.assertEquals("one,twoxxx", str);
qx.Class.undefine("testAsyncEventHandlers.Clazz");
this.resume();
}, this);
this.wait(2500);
},
/**
* Tests using bind() on async properties (using the "changeXxxAsync" events) between
* a series of objects. The test must show that the property values are fired in
* order, and that if an async event handler returns a promise it defers bind from
* propagating onto other objects.
*/
testWaterfallBinding: function() {
var t = this;
var Clazz = qx.Class.define("testWaterfallBinding.Clazz", {
extend: qx.core.Object,
properties: {
value: {
},
alpha: {
init: null,
nullable: true,
async: true,
apply: "_applyAlpha",
event: "changeAlpha"
}
},
members: {
_applyAlpha: function(value, oldValue) {
var t = this;
console.log("pre applyAlpha[" + t.getValue() + "] = " + value);
return new qx.Promise(function(resolve) {
setTimeout(function() {
console.log("applyAlpha[" + t.getValue() + "] = " + value);
resolve("xyz");
}, 50);
});
}
}
});
var objs = [];
var str = "";
function trap(i) {
var obj = new Clazz().set({value: i});
var bindPromise;
if (i > 0) {
bindPromise = objs[i - 1].bindAsync("alphaAsync", obj, "alphaAsync");
} else {
bindPromise = qx.Promise.resolve(true);
}
return bindPromise.then(function() {
obj.addListener("changeAlpha", function(evt) {
var obj = evt.getTarget();
var data = evt.getData();
var delay = (5-i+1) * 100;
console.log("pre changeAlpha " + obj.getValue() + " = " + data + " after " + delay);
return new qx.Promise(function(resolve) {
setTimeout(function() {
if (str.length)
str += ",";
str += obj.getValue() + ":" + data;
console.log("changeAlpha " + obj.getValue() + " = " + data + " after " + delay);
resolve();
}, delay);
});
}, this);
objs[i] = obj;
});
}
qx.Promise.mapSeries([0, 1, 2, 3, 4], trap)
.then(function() {
var p = objs[0].setAlphaAsync("abc");
p.then(function() {
t.assertEquals("0:abc,1:abc,2:abc,3:abc,4:abc", str);
qx.Class.undefine("testWaterfallBinding.Clazz");
t.resume();
}, t);
});
this.wait(10000);
},
/**
* Tests the each method of promise, using qx.data.Array which the Bluebird implementation
* does not understand. The values are scalar values
*/
testEach1: function() {
var t = this;
var arr = new qx.data.Array();
arr.push("a");
arr.push("b");
arr.push("c");
var str = "";
var promise = qx.Promise.resolve(arr);
promise.forEach(function(item) {
str += item;
}).then(function() {
t.assertEquals("abc", str);
t.resume();
});
t.wait(1000);
},
/**
* Tests the each method of promise, using qx.data.Array which the Bluebird implementation
* does not understand. The values are a mixture of promises and scalar values
*/
testEach2: function() {
var t = this;
var arr = new qx.data.Array();
arr.push(new qx.Promise(function(resolve) {
setTimeout(function() { resolve("a"); }, 500);
}));
arr.push(new qx.Promise(function(resolve) {
setTimeout(function() { resolve("b"); }, 300);
}));
arr.push(new qx.Promise(function(resolve) {
setTimeout(function() { resolve("c"); }, 100);
}));
arr.push("d");
arr.push("e");
var str = "";
var promise = qx.Promise.resolve(arr);
this.assertInstance(promise, qx.Promise);
var pEach = promise.forEach(function(item) {
str += item;
});
this.assertInstance(pEach, qx.Promise);
var pThen = pEach.then(function() {
t.assertEquals("abcde", str);
t.resume();
});
this.assertInstance(pThen, qx.Promise);
t.wait(1000);
},
/**
* Tests unhandled rejections being passed to the global error handler
*/
testGlobalError: function() {
var t = this;
qx.event.GlobalError.setErrorHandler(function(ex) {
t.assertEquals(ex.message, "oops");
t.resume();
});
var self = this;
var p = new qx.Promise(function(resolve, reject) {
setTimeout(function() {
resolve("ok");
});
}, this);
p.then(function(value) {
throw new Error("oops");
});
this.wait(1000);
},
/**
* Tests promisification of methods
*/
testMethod: function() {
var t = this;
var fn = qx.Promise.method(function(value) {
return value;
});
var promise = fn("yes");
this.assertInstance(promise, qx.Promise);
promise.then(function(value) {
t.assertEquals(value, "yes");
t.resume();
});
this.wait(1000);
},
/**
* Tests binding of all callbacks via .bind()
*/
testBinding1: function() {
var t = this;
var p = qx.Promise.resolve("hello").bind(this);
p.then(function(value) {
qx.core.Assert.assertIdentical(t, this);
t.resume();
});
this.wait(1000);
},
/**
* Tests binding on a per-method basis
*/
testBinding2: function() {
var t = this;
var p = qx.Promise.forEach(
["a", "b", "c"],
function(item) {
qx.core.Assert.assertIdentical(t, this);
},
this)
.then(function(value) {
qx.core.Assert.assertIdentical(t, this);
this.resume();
}, this);
this.wait(1000);
},
testMarshal: function() {
var marshal = new qx.data.marshal.Json();
marshal.toClass(qx.test.Promise.TEST_MODEL.children[0], true);
var model = marshal.toModel(qx.test.Promise.TEST_MODEL.children[0]);
},
/**
* Tests binding where the context is static class
*/
testBindingToStatic: function() {
var t = this;
qx.Promise.resolve(true).then(function() {
qx.core.Assert.assertIdentical(qx.Promise, this);
t.resume();
}, qx.Promise);
this.wait(1000);
},
/**
* Tests the context parameter for qx.Promise.resolve
*/
testBindingResolve: function() {
var t = this;
qx.Promise.resolve(true, this).then(function() {
qx.core.Assert.assertIdentical(t, this);
t.resume();
});
this.wait(1000);
},
/**
* Tests the context parameter for qx.Promise.reject
*/
testBindingReject: function() {
var t = this;
qx.Promise.reject(new Error("Dummy Error"), this).catch(function() {
qx.core.Assert.assertIdentical(t, this);
t.resume();
});
this.wait(1000);
},
/**
* Tests wrapping of parameters preserves the original values
*/
testWrapping: function() {
var t = this;
new qx.Promise(function(resolve) {
resolve();
})
.then(function() {
return qx.Promise.all(["foo", new qx.data.Array(["a", "b", "c"])]);
})
.spread(function(str, arr) {
t.assertEquals(str, "foo");
t.assertInstance(arr, qx.data.Array);
t.assertEquals(arr.join(""), "abc");
t.resume();
});
this.wait(1000);
}
},
statics: {
TEST_MODEL: {
"name": "qx",
"children": [
{
"name": "test",
"children": [
{
"name": "Class",
"children": [
{
"name": "test: instantiate class in defer and access property"
},
{
"name": "testAbstract"
},
{
"name": "testAnonymous"
}
]
},
{
"name": "Bootstrap",
"children": [
{
"name": "test: define bootstrap class, which extends 'Error'"
},
{
"name": "test: define class with constructor"
},
{
"name": "test: extend from Bootstrap class"
}
]
}
]
}
]
}
}
});