@qooxdoo/framework
Version:
The JS Framework for Coders
822 lines (638 loc) • 24.3 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2011 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Tristan Koch (tristankoch)
************************************************************************ */
/* ************************************************************************
************************************************************************ */
/**
* @ignore(Klass)
* @asset(qx/test/xmlhttp/*)
*/
/**
* Tests asserting behavior
*
* - special to io.request.Xhr and
* - common to io.request.* (see {@link qx.test.io.request.MRequest})
*
* Tests defined in MRequest run with appropriate context, i.e.
* a transport that is an instance of qx.bom.request.Xhr
* (see {@link #setUpFakeTransport}).
*
*/
qx.Class.define("qx.test.io.request.Xhr",
{
extend : qx.dev.unit.TestCase,
include : [qx.test.io.request.MRequest,
qx.dev.unit.MMock,
qx.dev.unit.MRequirements],
members :
{
setUp: function() {
this.setUpRequest();
this.setUpFakeTransport();
},
setUpRequest: function() {
this.req && this.req.dispose();
this.req = new qx.io.request.Xhr();
this.req.setUrl("url");
},
setUpFakeTransport: function() {
if (this.transport && this.transport.send.restore) { return; }
this.transport = this.injectStub(qx.io.request.Xhr.prototype, "_createTransport");
this.setUpRequest();
},
setUpFakeServer: function() {
// Not fake transport
this.getSandbox().restore();
this.useFakeServer();
this.setUpRequest();
this.server = this.getServer();
this.server.respondWith("GET", "/found",
[200, {"Content-Type": "text/html"}, "FOUND"]);
this.server.respondWith("GET", "/found.json",
[200, {"Content-Type": "application/json; charset=utf-8"}, "JSON"]);
this.server.respondWith("GET", "/found.other",
[200, {"Content-Type": "application/other"}, "OTHER"]);
},
setUpFakeXhr: function() {
// Not fake transport
this.getSandbox().restore();
this.useFakeXMLHttpRequest();
this.setUpRequest();
},
tearDown: function() {
this.getSandbox().restore();
this.req.dispose();
// May fail in IE
try { qx.Class.undefine("Klass"); } catch(e) {}
},
//
// General (cont.)
//
"test: set url property on construct": function() {
var req = new qx.io.request.Xhr("url");
this.assertEquals("url", req.getUrl());
req.dispose();
},
"test: set method property on construct": function() {
var req = new qx.io.request.Xhr("url", "POST");
this.assertEquals("POST", req.getMethod());
req.dispose();
},
//
// Send (cont.)
//
"test: send POST request": function() {
this.setUpFakeTransport();
this.req.setMethod("POST");
this.req.send();
this.assertCalledWith(this.transport.open, "POST");
},
"test: send sync request": function() {
this.require(["http"]);
this.setUpFakeTransport();
this.req.setAsync(false);
this.req.send();
this.assertCalledWith(this.transport.open, "GET", "url", false);
this.assertCalled(this.transport.send);
},
//
// Data (cont.)
//
"test: set content type urlencoded for POST request with body when no type given": function() {
this.setUpFakeTransport();
this.req.setMethod("POST");
this.req.setRequestData("Affe");
this.req.send();
this.assertCalledWith(this.transport.setRequestHeader,
"Content-Type", "application/x-www-form-urlencoded");
},
"test: not set content type urlencoded for POST request with body when type given": function() {
var msg;
this.setUpFakeTransport();
this.req.setMethod("POST");
this.req.setRequestData("Affe");
this.req.setRequestHeader("Content-Type", "application/json");
this.req.send();
msg = "Must not set content type urlencoded when other type given";
this.assert(!this.transport.setRequestHeader.calledWith("Content-Type",
"application/x-www-form-urlencoded"), msg);
},
"test: send string data with POST request": function() {
this.setUpFakeTransport();
this.req.setMethod("POST");
this.req.setRequestData("str");
this.req.send();
this.assertCalledWith(this.transport.send, "str");
},
"test: send obj data with POST request": function() {
this.setUpFakeTransport();
this.req.setMethod("POST");
this.req.setRequestData({"af fe": true});
this.req.send();
this.assertCalledWith(this.transport.send, "af+fe=true");
},
"test: send qooxdoo obj data with POST request": function() {
this.setUpFakeTransport();
this.setUpKlass();
var obj = new Klass();
this.req.setMethod("POST");
this.req.setRequestData(obj);
this.req.send();
this.assertCalledWith(this.transport.send, "affe=true");
obj.dispose();
},
"test: send blob data with POST request": function() {
if (typeof window.Blob == "undefined") {
this.skip("Blob constructor not available");
}
var blob = new window.Blob(['abc123'], {type: 'text/plain'});
this.setUpFakeTransport();
this.req.setMethod("POST");
this.req.setRequestData(blob);
this.req.send();
this.assertCalledWith(this.transport.send, blob);
},
"test: send array buffer data with POST request": function() {
if (typeof window.ArrayBuffer == "undefined") {
this.skip("ArrayBuffer constructor not available");
}
var array = new window.ArrayBuffer(512);
this.setUpFakeTransport();
this.req.setMethod("POST");
this.req.setRequestData(array);
this.req.send();
this.assertCalledWith(this.transport.send, array);
},
"test: serialize data": function() {
var req = this.req,
data = {"abc": "def", "uvw": "xyz"},
contentType = "application/json";
this.assertNull(req._serializeData(null));
this.assertEquals("leaveMeIntact", req._serializeData("leaveMeIntact"));
this.assertEquals("abc=def&uvw=xyz", req._serializeData(data));
req.setRequestHeader("Content-Type", "arbitrary/contentType");
this.assertEquals("abc=def&uvw=xyz", req._serializeData(data));
req.setRequestHeader("Content-Type", contentType);
this.assertEquals('{"abc":"def","uvw":"xyz"}', req._serializeData(data));
req.setRequestHeader("Content-Type", contentType);
this.assertEquals('[1,2,3]', req._serializeData([1,2,3]));
},
//
// Header and Params (cont.)
//
"test: set requested-with header": function() {
this.setUpFakeTransport();
this.req.send();
this.assertCalledWith(this.transport.setRequestHeader, "X-Requested-With", "XMLHttpRequest");
},
"test: not set requested-with header when cross-origin": function() {
this.setUpFakeTransport();
var spy = this.transport.setRequestHeader.withArgs("X-Requested-With", "XMLHttpRequest");
this.req.setUrl("http://example.com");
this.req.send();
this.assertNotCalled(spy);
},
"test: set cache control header": function() {
this.setUpFakeTransport();
this.req.setCache("no-cache");
this.req.send();
this.assertCalledWith(this.transport.setRequestHeader, "Cache-Control", "no-cache");
},
"test: set accept header": function() {
this.setUpFakeTransport();
this.req.setAccept("application/json");
this.req.send();
this.assertCalledWith(this.transport.setRequestHeader, "Accept", "application/json");
},
"test: override response content type": function() {
this.setUpFakeTransport();
this.req.overrideResponseContentType("text/plain;charset=Shift-JIS");
this.assertCalledWith(this.transport.overrideMimeType, "text/plain;charset=Shift-JIS");
},
"test: get response content type": function() {
this.stub(this.req, "getResponseHeader");
this.req.getResponseContentType();
this.assertCalledWith(this.req.getResponseHeader, "Content-Type");
},
//
// Handler
//
// Documentation only
"test: event handler receives request": function() {
this.setUpFakeTransport();
var req = this.req,
transport = this.transport,
that = this;
transport.readyState = 4;
transport.status = 200;
transport.responseText = "Affe";
req.addListener("success", function(e) {
that.assertEquals(e.getTarget(), req);
that.assertEquals("Affe", e.getTarget().getResponseText());
});
transport.onreadystatechange();
},
// Documentation only
"test: event handler's this is request": function() {
this.setUpFakeTransport();
var req = this.req,
transport = this.transport,
that = this;
transport.readyState = 4;
transport.status = 200;
transport.responseText = "Affe";
req.addListener("success", function() {
that.assertEquals(this, req);
that.assertEquals("Affe", this.getResponseText());
});
transport.onreadystatechange();
},
//
// Properties
//
"test: sync XHR properties for every readyState": function() {
this.require(["http"]);
this.setUpFakeServer();
var req = this.req,
server = this.server,
readyStates = [],
statuses = [];
req.setUrl("/found");
req.setMethod("GET");
readyStates.push(req.getReadyState());
req.addListener("readyStateChange", function() {
readyStates.push(req.getReadyState());
statuses.push(req.getStatus());
}, this);
req.send();
server.respond();
this.assertArrayEquals([0, 1, 2, 3, 4], readyStates);
this.assertArrayEquals([0, 200, 200, 200], statuses);
this.assertEquals("text/html", req.getResponseHeader("Content-Type"));
this.assertEquals("OK", req.getStatusText());
this.assertEquals("FOUND", req.getResponseText());
},
//
// Response
//
"test: get response": function() {
this.setUpFakeTransport();
var req = this.req,
transport = this.transport;
transport.readyState = 4;
transport.status = 200;
transport.responseText = "Affe";
transport.onreadystatechange();
this.assertEquals("Affe", req.getResponse());
},
"test: get response on 400 status": function() {
this.setUpFakeTransport();
var req = this.req,
transport = this.transport;
transport.readyState = 4;
transport.status = 400;
transport.responseText = "Affe";
transport.onreadystatechange();
this.assertEquals("Affe", req.getResponse());
},
"test: get response by change event": function() {
this.setUpFakeTransport();
var req = this.req,
transport = this.transport,
that = this;
transport.readyState = 4;
transport.status = 200;
transport.responseText = "Affe";
this.assertEventFired(req, "changeResponse", function() {
transport.onreadystatechange();
}, function(e) {
that.assertEquals("Affe", e.getData());
});
},
//
// Parsing
//
"test: _getParsedResponse": function() {
var req = this.req,
json = '{"animals": 3}',
contentType = "application/json",
stubbedParser = req._createResponseParser();
req._transport.responseText = json;
this.stub(req, "getResponseContentType").returns(contentType);
// replace real parser with stub
this.stub(stubbedParser, "parse");
req._parser = stubbedParser;
req._getParsedResponse();
this.assertCalledWith(stubbedParser.parse, json, contentType);
},
"test: setParser": function() {
var req = this.req,
customParser = function() {},
stubbedParser = req._createResponseParser();
// replace real parser with stub
this.stub(stubbedParser, "setParser");
req._parser = stubbedParser;
req.setParser(customParser);
this.assertCalledWith(stubbedParser.setParser, customParser);
},
//
// Auth
//
"test: basic auth": function() {
this.setUpFakeTransport();
var transport = this.transport,
auth, call, key, credentials;
auth = new qx.io.request.authentication.Basic("affe", "geheim");
this.req.setAuthentication(auth);
this.req.send();
call = transport.setRequestHeader.getCall(1);
key = "Authorization";
credentials = /Basic\s(.*)/.exec(call.args[1])[1];
this.assertEquals(key, call.args[0]);
this.assertEquals("affe:geheim", qx.util.Base64.decode(credentials));
},
//
// Promise
//
"test: send with promise sends the request": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.req.sendWithPromise();
this.assertCalledOnce(this.transport.send);
},
"test: send with promise succeeds": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
req.sendWithPromise(this)
.then(this.resumeHandler(function(result) {
this.assertEquals(req, result);
this.assertEquals(200, req.getStatus());
}, this));
var transport = this.transport;
transport.readyState = 4;
transport.status = 200;
transport.onreadystatechange();
this.wait(10000);
},
"test: send with promise fails with statusError": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
req.sendWithPromise(this)
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "Promise has been fulfilled but should have been rejected.");
}))
.catch(this.resumeHandler(function(result) {
this.assertInstance(result, qx.type.BaseError);
this.assertEquals("statusError: 404: Affe.", result.toString());
}, this));
var transport = this.transport;
transport.readyState = 4;
transport.status = 404;
transport.statusText = "Affe";
transport.onreadystatechange();
this.wait(1000);
},
"test: send with promise fails with error": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
req.sendWithPromise(this)
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "Promise has been fulfilled but should have been rejected.");
}))
.catch(this.resumeHandler(function(result) {
this.assertInstance(result, qx.type.BaseError);
this.assertEquals("error: Request failed.", result.toString());
}, this));
var transport = this.transport;
transport.readyState = 4;
transport.status = 0;
transport.onerror();
this.wait(1000);
},
"test: send with promise fails with timeout": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
req.sendWithPromise(this)
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "Promise has been fulfilled but should have been rejected.");
}))
.catch(this.resumeHandler(function(result) {
this.assertInstance(result, qx.type.BaseError);
this.assertEquals("timeout: Request failed with timeout after 1 ms.", result.toString());
}, this));
req.setTimeout(1);
this.transport.ontimeout();
this.wait(5000);
},
"test: setled promise has no extra listeners": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
var promise = req.sendWithPromise(this);
// cache the number of listeners before setling the promise
var listeners = qx.event.Registration.serializeListeners(req);
promise.catch(this.resumeHandler(function() {
var length = qx.event.Registration.serializeListeners(req).length;
this.assertNotEquals(length, listeners.length, "The number of listeners remains the same before and after setling the promise.");
}));
this.transport.ontimeout();
this.wait(5000);
},
"test: aborted request rejects the promise": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
req.sendWithPromise(this)
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "Promise has been fulfilled but should have been rejected.");
}))
.catch(this.resumeHandler(function(result) {
this.assertInstance(result, qx.type.BaseError);
this.assertEquals("abort: Request aborted.", result.toString());
}, this));
this.transport.onabort();
this.wait(5000);
},
"test: parseError rejects the promise": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
var stubbedParser = req._createResponseParser();
// replace real parser with stub
var stub = this.stub(stubbedParser, "parse");
stub.throws();
req._parser = stubbedParser;
req.sendWithPromise(this)
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "Promise has been fulfilled but should have been rejected.");
}))
.catch(this.resumeHandler(function(result) {
this.assertInstance(result, qx.type.BaseError);
this.assertEquals("parseError: Error parsing the response.", result.toString());
}, this));
var transport = this.transport;
transport.readyState = 4;
transport.status = 200;
transport.onreadystatechange();
this.wait(5000);
},
"test: canceled promise with abort() in finally does not reject other promises": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
var promise = req.sendWithPromise(this);
// this path is canceled. We don't expect anything from it
var promise1 = promise
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "This path should not be fulfilled.");
}, this))
.catch(this.resumeHandler(function(result) {
throw new qx.type.BaseError("Error in sendWithPromise()", "This path should not be rejected.");
}, this))
// except that we want to abort when is finished
.finally(function() {
req.abort();
});
// this path should keep going
promise
.then(this.resumeHandler(function(result) {
this.assertEquals(req, result);
}, this))
.catch(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "Promise has been rejected but should have been fulfilled.");
}, this));
promise1.cancel();
var transport = this.transport;
transport.readyState = 4;
transport.status = 200;
transport.onreadystatechange();
this.wait(5000);
},
"test: canceled promise path does not affect other listeners": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
var promise = req.sendWithPromise(this);
// this path is canceled. We don't expect anything from it
var promise1 = promise
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "promise1 should not be fulfilled.");
}))
.catch(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "promise1 should not be rejected.");
}, this));
// this path should keep going
promise
.then(this.resumeHandler(function(result) {
this.assertEquals(req, result);
}, this));
promise1.cancel();
var transport = this.transport;
transport.readyState = 4;
transport.status = 200;
transport.onreadystatechange();
this.wait(5000);
},
"test: canceled promise aborts pending request": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
var promise = req.sendWithPromise(this)
.then(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "promise should not be fulfilled.");
}))
.catch(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "promise should not be rejected.");
}))
.finally(this.resumeHandler(function() {
this.assertEquals("abort", req.getPhase());
}));
var transport = this.transport;
transport.readyState = 2;
transport.onreadystatechange();
promise.cancel();
this.wait(5000);
},
"test: settled promise does not set phase to abort": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
req.sendWithPromise(this)
.then(this.resumeHandler(function(_) {
this.assertEquals("success", req.getPhase());
}))
.catch(this.resumeHandler(function(_) {
throw new qx.type.BaseError("Error in sendWithPromise()", "promise should not be rejected.");
}))
.finally(function() {
this.assertEquals("success", req.getPhase());
});
var transport = this.transport;
transport.readyState = 4;
transport.status = 200;
transport.onreadystatechange();
this.wait(5000);
},
"test: returned promise is bound to request": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport();
var req = this.req;
req.sendWithPromise()
.catch(this.resumeHandler(function(_) {
this.assertIdentical(req, this);
}));
this.transport.onerror();
this.wait(5000);
},
"test: returned promise is bound to caller": function() {
if (!qx.core.Environment.get("qx.promise")) {
this.skip("Skipping because qx.promise==false");
}
this.setUpFakeTransport(this);
var self = this;
this.req.sendWithPromise(this)
.catch(this.resumeHandler(function(_) {
this.assertIdentical(self, this);
}));
this.transport.onerror();
this.wait(5000);
}
}
});