@qooxdoo/framework
Version:
The JS Framework for Coders
899 lines (697 loc) • 20.4 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() {
this.fakeNativeXhr();
this.req = new qx.bom.request.Xhr();
},
tearDown() {
this.req = null;
this.getSandbox().restore();
},
//
// General
//
"test: create instance"() {
this.assertObject(this.req);
},
"test: detect native XHR"() {
var nativeXhr = this.req.getRequest();
this.assertObject(nativeXhr);
this.assertNotNull(nativeXhr.readyState);
},
//
// open()
//
"test: open request"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
var req = this.req;
var fakeReq = this.getFakeReq();
this.spy(fakeReq, "abort");
req.abort();
this.assertCalled(fakeReq.abort);
},
"test: abort() resets readyState"() {
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"() {
var req = this.req;
req.onevent = this.spy();
req._emit("event");
this.assertCalled(req.onevent);
},
"test: fire event"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
// 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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
this.req.open("GET", "/affe");
this.assertIdentical("", this.req.responseText);
},
"test: responseText is empty string when reopened"() {
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"() {
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"() {
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"() {
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"() {
this.assertNull(this.req.responseXML);
},
"test: responseXML is null when reopened"() {
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"() {
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"() {
this.assertIdentical(0, this.req.status);
},
"test: http status is 0 when OPENED"() {
var req = this.req;
req.open("GET", "/");
this.assertIdentical(0, req.status);
},
"test: http status is 0 when aborted immediately"() {
this.require(["http"]);
var req = this.req;
req.open("GET", "/");
req.send();
req.abort();
this.assertIdentical(0, req.status);
},
"test: http status when DONE"() {
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"() {
this.assertIdentical("", this.req.statusText);
},
"test: statusText is set when DONE"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
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"() {
this.require(["http"]);
var req = this.req;
req.open("GET", "index.html");
this.assertMatch(req._getProtocol(), /https?:/);
},
//
// getResponseHeader()
//
"test: getResponseHeader()"() {
var fakeReq = this.getFakeReq();
fakeReq.open();
fakeReq.setResponseHeaders({
key: "value"
});
var responseHeader = this.req.getResponseHeader("key");
this.assertEquals("value", responseHeader);
},
//
// getAllResponseHeaders()
//
"test: getAllResponseHeaders()"() {
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"() {
this.req.dispose();
this.assertNull(this.req.getRequest());
},
"test: dispose() aborts"() {
var req = this.req;
this.spy(req, "abort");
this.req.dispose();
this.assertCalled(req.abort);
},
"test: isDisposed()"() {
this.assertFalse(this.req.isDisposed());
this.req.dispose();
this.assertTrue(this.req.isDisposed());
},
"test: invoking public method throws when disposed"() {
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() {
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() {
return this.getRequests().slice(-1)[0];
},
isIEBelow(targetVersion) {
var name = qx.core.Environment.get("engine.name");
var version = qx.core.Environment.get("engine.version");
return name == "mshtml" && version < targetVersion;
},
isFFBelow(targetVersion) {
var name = qx.core.Environment.get("engine.name");
var version = qx.core.Environment.get("browser.version");
return name == "gecko" && parseFloat(version) < targetVersion;
},
hasIEBelow9() {
return this.isIEBelow(9);
},
skip(msg) {
throw new qx.dev.unit.RequirementError(null, msg);
}
}
});