@qooxdoo/framework
Version:
The JS Framework for Coders
852 lines (640 loc) • 20.6 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)
************************************************************************ */
qx.Class.define("qx.test.bom.request.Xhr",
{
extend : qx.dev.unit.TestCase,
include : [qx.dev.unit.MMock,
qx.dev.unit.MRequirements],
statics :
{
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4
},
members :
{
/**
* The faked XMLHttpRequest.
*/
fakedXhr: null,
/**
* Holds instances created by the faked XMLHttpRequest.
*/
fakeReqs: null,
/**
* The request to test.
*/
req: null,
setUp : function()
{
this.fakeNativeXhr();
this.req = new qx.bom.request.Xhr();
},
tearDown : function()
{
this.req = null;
this.getSandbox().restore();
},
//
// General
//
"test: create instance": function() {
this.assertObject(this.req);
},
"test: detect native XHR": function() {
var nativeXhr = this.req.getRequest();
this.assertObject(nativeXhr);
this.assertNotNull(nativeXhr.readyState);
},
//
// open()
//
"test: open request": function() {
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "open");
var url = "/foo";
var method = "GET";
this.req.open(method, url);
this.assertCalledWith(fakeReq.open, method, url);
},
"test: open request throws when missing arguments": function() {
var req = this.req;
var msg = /Not enough arguments/;
this.assertException(function() { req.open(); }, Error, msg);
this.assertException(function() { req.open("GET"); }, Error, msg);
},
"test: open async request on default": function() {
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "open");
this.req.open(null, null);
this.assertTrue(fakeReq.open.args[0][2], "async must be true");
},
"test: open sync request": function() {
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "open");
this.req.open(null, null, false);
this.assertFalse(fakeReq.open.args[0][2], "async must be false");
},
"test: open request with username and password": function() {
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "open");
this.req.open(null, null, null, "affe", "geheim");
this.assertEquals("affe", fakeReq.open.args[0][3], "Unexpected user");
this.assertEquals("geheim", fakeReq.open.args[0][4], "Unexpected password");
},
//
// setRequestHeader()
//
"test: set request header": function() {
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "setRequestHeader");
// Request must be opened before request headers can be set
this.req.open("GET", "/");
this.req.setRequestHeader("header", "value");
this.assertCalledWith(fakeReq.setRequestHeader, "header", "value");
},
//
// send()
//
"test: send() with data": function() {
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "send");
var data = "AFFE";
this.req.open("GET", "/affe");
this.req.send(data);
this.assertCalledWith(fakeReq.send, data);
},
// BUGFIXES
"test: send() without data": function() {
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "send");
this.req.open("GET", "/affe");
this.req.send();
this.assertCalledWith(fakeReq.send, null);
},
//
// abort()
//
"test: abort() aborts native Xhr": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "abort");
req.abort();
this.assertCalled(fakeReq.abort);
},
"test: abort() resets readyState": function() {
var req = this.req;
req.open("GET", "/");
req.abort();
this.assertEquals(this.constructor.UNSENT, req.readyState, "Must be UNSENT");
},
//
// Event helper
//
"test: call event handler": function() {
var req = this.req;
req.onevent = this.spy();
req._emit("event");
this.assertCalled(req.onevent);
},
"test: fire event": function(){
var req = this.req;
var event = this.spy();
req.onevent = this.spy();
req.on("event", event);
req._emit("event");
this.assertCalled(event);
},
//
//
// onreadystatechange()
//
"test: responseText set before onreadystatechange is called": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
var that = this;
req.onreadystatechange = function() {
that.assertEquals("Affe", req.responseText);
};
fakeReq.responseText = "Affe";
fakeReq.readyState = 4;
fakeReq.responseHeaders = {};
fakeReq.onreadystatechange();
},
"test: emit readystatechange when reopened": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
this.stub(req, "_emit");
// Send and respond
req.open("GET", "/");
req.send();
fakeReq.respond();
// Reopen
req.open("GET", "/");
this.assertCalledWith(req._emit, "readystatechange");
},
// BUGFIXES
"test: ignore onreadystatechange when readyState is unchanged": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
this.spy(req, "onreadystatechange");
req.readyState = this.constructor.OPENED;
fakeReq.onreadystatechange();
fakeReq.onreadystatechange();
this.assertCalledOnce(req.onreadystatechange);
},
"test: native onreadystatechange is disposed once DONE": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
req.onreadystatechange = function() { return "OP"; };
req.open("GET", "/");
req.send();
fakeReq.respond();
this.assertUndefined(req.getRequest().onreadystatechange());
},
//
// onload()
//
"test: emit load on successful request": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
this.stub(req._emitter, "emit");
req.open("GET", "/");
req.send();
// Status does not matter. Set a non-empty response for file:// workaround.
fakeReq.respond(200, {}, "RESPONSE");
this.assertCalledWith(req._emitter.emit, "load");
this.assertEquals(6, req._emitter.emit.callCount);
},
//
// onerror()
//
// See XhrWithBackend
//
//
// onabort()
//
"test: emit abort": function() {
var req = this.req;
this.spy(req, "_emit");
req.open("GET", "/");
req.send();
req.abort();
this.assertCalledWith(req._emit, "abort");
},
"test: emit abort before loadend": function() {
var req = this.req;
var emit = this.stub(req, "_emit");
var abort = emit.withArgs("abort");
var loadend = emit.withArgs("loadend");
req.open("GET", "/");
req.send();
req.abort();
this.assertCallOrder(abort, loadend);
},
//
// ontimeout()
//
"test: emit timeout": function() {
var req = this.req,
that = this;
var timeout = this.stub(req, "_emit").withArgs("timeout");
req.timeout = 10;
req.open("GET", "/");
req.send();
this.wait(20, function() {
this.assertCalledOnce(timeout);
}, this);
},
"test: not emit error when timeout": function() {
// Since Opera does not fire "error" on network error, fire additional
// "error" on timeout (may well be related to network error)
if (qx.core.Environment.get("engine.name") === "opera") {
this.skip();
}
var req = this.req;
var error = this.stub(req, "_emit").withArgs("error");
req.timeout = 10;
req.open("GET", "/");
req.send();
this.wait(20, function() {
this.assertNotCalled(error);
}, this);
},
"test: not emit error when aborted immediately": function() {
var req = this.req;
var error = this.stub(req, "_emit").withArgs("error");
req.open("GET", "/");
req.send();
req.abort();
this.assertNotCalled(error);
},
"test: cancel timeout when DONE": function() {
var fakeReq = this.getFakeReq(),
req = this.req;
this.spy(req, "ontimeout");
req.timeout = 10;
req.open("GET", "/");
req.send();
fakeReq.respond();
this.wait(20, function() {
this.assertNotCalled(req.ontimeout);
}, this);
},
"test: cancel timeout when handler throws": function() {
var fakeReq = this.getFakeReq(),
req = this.req;
this.spy(req, "ontimeout");
req.timeout = 10;
req.open("GET", "/");
req.send();
// Simulate error in handler for readyState DONE
req.onreadystatechange = function() {
if (req.readyState === 4) {
// Throw only once
req.onreadystatechange = function() {};
throw new Error();
}
};
try {
fakeReq.respond();
} catch(e) {
} finally {
this.wait(20, function() {
this.assertNotCalled(req.ontimeout);
}, this);
}
},
//
// onloadend()
//
"test: fire loadend when request complete": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
var loadend = this.stub(req, "_emit").withArgs("loadend");
req.open("GET", "/");
req.send();
// Status does not matter
fakeReq.respond();
this.assertCalled(loadend);
},
//
// Events
//
//
// readyState
//
"test: set readyState appropriate to native readyState": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
// Created
this.assertEquals(this.constructor.UNSENT, req.readyState);
// Open
req.open("GET", "/affe");
this.assertEquals(this.constructor.OPENED, req.readyState);
// Send (and receive)
req.send();
fakeReq.respond(this.constructor.DONE);
this.assertEquals(this.constructor.DONE, req.readyState);
},
//
// responseText
//
"test: responseText is empty string when OPEN": function() {
this.req.open("GET", "/affe");
this.assertIdentical("", this.req.responseText);
},
"test: responseText is empty string when reopened": function() {
var fakeReq = this.getFakeReq();
// Send and respond
var req = this.req;
req.open("GET", "/");
req.send();
fakeReq.respond(200, {"Content-Type": "text/html"}, "Affe");
// Reopen
req.open("GET", "/elefant");
this.assertIdentical("", req.responseText);
},
"test: responseText is set when DONE": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
req.open("GET", "/");
req.send();
fakeReq.respond(200, {"Content-Type": "text/html"}, "Affe");
this.assertEquals("Affe", req.responseText);
},
// BUGFIXES
"test: query responseText when available": function() {
var that = this;
var req = this.req;
var fakeReq = this.getFakeReq();
function success(state) {
// Stub and prepare success
fakeReq.readyState = state;
fakeReq.responseText = "YIPPIE";
fakeReq.responseHeaders = {};
// Trigger readystatechange handler
fakeReq.onreadystatechange();
that.assertEquals("YIPPIE", req.responseText,
"When readyState is " + state);
}
success(this.constructor.DONE);
// Assert responseText to be set when in progress
// in browsers other than IE < 9
if (!this.isIEBelow(9)) {
success(this.constructor.HEADERS_RECEIVED);
success(this.constructor.LOADING);
}
},
"test: not query responseText if unavailable": function() {
var that = this;
var req = this.req;
var fakeReq = this.getFakeReq();
function trap(state) {
// Stub and set trap
fakeReq.readyState = state;
fakeReq.responseText = "BOGUS";
// Trigger readystatechange handler
fakeReq.onreadystatechange();
that.assertNotEquals("BOGUS", req.responseText,
"When readyState is " + state);
}
if (this.isIEBelow(9)) {
trap(this.constructor.UNSENT);
trap(this.constructor.OPENED);
trap(this.constructor.HEADERS_RECEIVED);
trap(this.constructor.LOADING);
}
},
//
// responseXML
//
"test: responseXML is null when not DONE": function() {
this.assertNull(this.req.responseXML);
},
"test: responseXML is null when reopened": function() {
var fakeReq = this.getFakeReq();
// Send and respond
var req = this.req;
req.open("GET", "/");
req.send();
fakeReq.respond(200, { "Content-Type": "application/xml" }, "<affe></affe>");
// Reopen
req.open("GET", "/");
this.assertNull(req.responseXML);
},
"test: responseXML is parsed document with XML response": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
req.open("GET", "/");
req.send();
var headers = { "Content-Type": "application/xml" };
var body = "<animals><monkey/><mouse/></animals>";
fakeReq.respond(200, headers, body);
this.assertObject(req.responseXML);
},
//
// status and statusText
//
"test: http status is 0 when UNSENT": function() {
this.assertIdentical(0, this.req.status);
},
"test: http status is 0 when OPENED": function() {
var req = this.req;
req.open("GET", "/");
this.assertIdentical(0, req.status);
},
"test: http status is 0 when aborted immediately": function() {
this.require(["http"]);
var req = this.req;
req.open("GET", "/");
req.send();
req.abort();
this.assertIdentical(0, req.status);
},
"test: http status when DONE": function() {
var req = this.req;
var fakeReq = this.getFakeReq();
req.open("GET", "/");
fakeReq.respond(200);
this.assertIdentical(200, req.status);
},
"test: statusText is empty string when UNSENT": function() {
this.assertIdentical("", this.req.statusText);
},
"test: statusText is set when DONE": function() {
var fakeReq = this.getFakeReq();
var req = this.req;
req.open("GET", "/");
fakeReq.respond(200);
this.assertIdentical("OK", req.statusText);
},
"test: status is set when LOADING": function() {
var fakeReq = this.getFakeReq();
var req = this.req;
req.open("GET", "/");
fakeReq.readyState = this.constructor.LOADING;
fakeReq.status = 200;
fakeReq.responseHeaders = {};
fakeReq.onreadystatechange();
this.assertIdentical(200, req.status);
},
"test: reset status when reopened": function() {
var fakeReq = this.getFakeReq();
var req = this.req;
req.open("GET", "/");
fakeReq.respond(200);
req.open("GET", "/");
this.assertIdentical(0, req.status);
this.assertIdentical("", req.statusText);
},
// BUGFIXES
"test: normalize status 1223 to 204": function() {
var fakeReq = this.getFakeReq();
var req = this.req;
req.open("GET", "/");
req.send();
fakeReq.respond(1223);
this.assertIdentical(204, req.status);
},
"test: normalize status 0 to 200 when DONE and file protocol": function() {
var fakeReq = this.getFakeReq();
var req = this.req;
req.open("GET", "/");
req.send();
this.stub(req, "_getProtocol").returns("file:");
fakeReq.respond(0, {}, "Response");
this.assertEquals(200, req.status);
},
"test: keep status 0 when not yet DONE and file protocol": function() {
var fakeReq = this.getFakeReq();
var req = this.req;
this.stub(req, "_getProtocol").returns("file:");
req.open("GET", "/");
fakeReq.readyState = 3;
fakeReq.onreadystatechange();
this.assertEquals(0, req.status);
},
"test: keep status 0 when DONE with network error and file protocol": function() {
var fakeReq = this.getFakeReq();
var req = this.req;
req.open("GET", "/");
req.send();
this.stub(req, "_getProtocol").returns("file:");
// Indicate network error
fakeReq.readyState = 4;
fakeReq.responseText = "";
fakeReq.onreadystatechange();
this.assertEquals(0, req.status);
},
//
// _getProtocol()
//
"test: read protocol from requested URL when it contains protocol": function() {
var req = this.req;
req.open("GET", "http://example.org/index.html");
this.assertEquals("http:", req._getProtocol());
},
"test: read protocol from window if requested URL is without protocol": function() {
this.require(["http"]);
var req = this.req;
req.open("GET", "index.html");
this.assertMatch(req._getProtocol(), (/https?:/));
},
//
// getResponseHeader()
//
"test: getResponseHeader()": function() {
var fakeReq = this.getFakeReq();
fakeReq.open();
fakeReq.setResponseHeaders({
"key": "value"
});
var responseHeader = this.req.getResponseHeader("key");
this.assertEquals("value", responseHeader);
},
//
// getAllResponseHeaders()
//
"test: getAllResponseHeaders()": function() {
var fakeReq = this.getFakeReq();
fakeReq.open();
fakeReq.setResponseHeaders({
"key1": "value1",
"key2": "value2"
});
var responseHeaders = this.req.getAllResponseHeaders();
this.assertMatch(responseHeaders, /key1: value1/);
this.assertMatch(responseHeaders, /key2: value2/);
},
//
// dispose()
//
"test: dispose() deletes native Xhr": function() {
this.req.dispose();
this.assertNull(this.req.getRequest());
},
"test: dispose() aborts": function() {
var req = this.req;
this.spy(req, "abort");
this.req.dispose();
this.assertCalled(req.abort);
},
"test: isDisposed()": function() {
this.assertFalse(this.req.isDisposed());
this.req.dispose();
this.assertTrue(this.req.isDisposed());
},
"test: invoking public method throws when disposed": function() {
var req = this.req;
var assertDisposedException = qx.lang.Function.bind(function(callback) {
this.assertException(qx.lang.Function.bind(callback, this),
Error, /Already disposed/);
}, this);
this.req.dispose();
assertDisposedException(function() { req.open("GET", "/"); });
assertDisposedException(function() { req.setRequestHeader(); });
assertDisposedException(function() { req.send(); });
assertDisposedException(function() { req.abort(); });
assertDisposedException(function() { req.getResponseHeader(); });
assertDisposedException(function() { req.getAllResponseHeaders(); });
},
fakeNativeXhr: function() {
this.fakedXhr = this.useFakeXMLHttpRequest();
// Reset pre-existing request so that it uses the faked XHR
if (this.req) {
this.req = new qx.bom.request.Xhr();
}
},
getFakeReq: function() {
return this.getRequests().slice(-1)[0];
},
isIEBelow: function(targetVersion) {
var name = qx.core.Environment.get("engine.name");
var version = qx.core.Environment.get("engine.version");
return name == "mshtml" && version < targetVersion;
},
isFFBelow: function(targetVersion) {
var name = qx.core.Environment.get("engine.name");
var version = qx.core.Environment.get("browser.version");
return name == "gecko" && parseFloat(version) < targetVersion;
},
hasIEBelow9: function() {
return this.isIEBelow(9);
},
skip: function(msg) {
throw new qx.dev.unit.RequirementError(null, msg);
}
}
});