aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/nodeserver/server.js34
-rw-r--r--src/scenario/Application.js66
-rw-r--r--src/scenario/Scenario.js1
-rw-r--r--src/scenario/dsl.js4
-rw-r--r--test/scenario/ApplicationSpec.js106
5 files changed, 181 insertions, 30 deletions
diff --git a/lib/nodeserver/server.js b/lib/nodeserver/server.js
index 08f13a99..f91f6afa 100644
--- a/lib/nodeserver/server.js
+++ b/lib/nodeserver/server.js
@@ -8,10 +8,8 @@ var DEFAULT_PORT = 8000;
function main(argv) {
new HttpServer({
- 'GET': (function() {
- var servlet = new StaticServlet();
- return servlet.handleRequest.bind(servlet)
- })()
+ 'GET': createServlet(StaticServlet),
+ 'HEAD': createServlet(StaticServlet)
}).start(Number(argv[2]) || DEFAULT_PORT);
}
@@ -22,6 +20,11 @@ function escapeHtml(value) {
replace('"', '"');
}
+function createServlet(Class) {
+ var servlet = new Class();
+ return servlet.handleRequest.bind(servlet);
+}
+
/**
* An Http server implementation that uses a map of methods to decide
* action routing.
@@ -61,11 +64,10 @@ HttpServer.prototype.handleRequest_ = function(req, res) {
}
};
-
/**
* Handles static content.
*/
-function StaticServlet() {}
+function StaticServlet() {}
StaticServlet.MimeMap = {
'txt': 'text/plain',
@@ -164,13 +166,17 @@ StaticServlet.prototype.sendFile_ = function(req, res, path) {
'Content-Type': StaticServlet.
MimeMap[path.split('.').pop()] || 'text/plain'
});
- file.on('data', res.write.bind(res));
- file.on('close', function() {
+ if (req.method === 'HEAD') {
res.end();
- });
- file.on('error', function(error) {
- self.sendError_(req, res, error);
- });
+ } else {
+ file.on('data', res.write.bind(res));
+ file.on('close', function() {
+ res.end();
+ });
+ file.on('error', function(error) {
+ self.sendError_(req, res, error);
+ });
+ }
};
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
@@ -207,6 +213,10 @@ StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
res.writeHead(200, {
'Content-Type': 'text/html'
});
+ if (req.method === 'HEAD') {
+ res.end();
+ return;
+ }
res.write('<!doctype html>\n');
res.write('<title>' + escapeHtml(path) + '</title>\n');
res.write('<style>\n');
diff --git a/src/scenario/Application.js b/src/scenario/Application.js
index e2d34551..eacf3c7b 100644
--- a/src/scenario/Application.js
+++ b/src/scenario/Application.js
@@ -38,27 +38,70 @@ angular.scenario.Application.prototype.getWindow_ = function() {
};
/**
+ * Checks that a URL would return a 2xx success status code. Callback is called
+ * with no arguments on success, or with an error on failure.
+ *
+ * Warning: This requires the server to be able to respond to HEAD requests
+ * and not modify the state of your application.
+ *
+ * @param {string} url Url to check
+ * @param {Function} callback function(error) that is called with result.
+ */
+angular.scenario.Application.prototype.checkUrlStatus_ = function(url, callback) {
+ var self = this;
+ _jQuery.ajax({
+ url: url,
+ type: 'HEAD',
+ complete: function(request) {
+ if (request.status < 200 || request.status >= 300) {
+ if (!request.status) {
+ callback.call(self, 'Sandbox Error: Cannot access ' + url);
+ } else {
+ callback.call(self, request.status + ' ' + request.statusText);
+ }
+ } else {
+ callback.call(self);
+ }
+ }
+ });
+};
+
+/**
* Changes the location of the frame.
*
* @param {string} url The URL. If it begins with a # then only the
* hash of the page is changed.
- * @param {Function} onloadFn function($window, $document)
+ * @param {Function} loadFn function($window, $document) Called when frame loads.
+ * @param {Function} errorFn function(error) Called if any error when loading.
*/
-angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) {
+angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorFn) {
var self = this;
var frame = this.getFrame_();
- if (url.charAt(0) === '#') {
+ //TODO(esprehn): Refactor to use rethrow()
+ errorFn = errorFn || function(e) { throw e; };
+ if (/^file:\/\//.test(url)) {
+ errorFn('Sandbox Error: Cannot load file:// URL.');
+ } else if (url.charAt(0) === '#') {
url = frame.attr('src').split('#')[0] + url;
frame.attr('src', url);
- this.executeAction(onloadFn);
+ this.executeAction(loadFn);
} else {
frame.css('display', 'none').attr('src', 'about:blank');
- this.context.find('#test-frames').append('<iframe>');
- frame = this.getFrame_();
- frame.load(function() {
- self.executeAction(onloadFn);
- frame.unbind();
- }).attr('src', url);
+ this.checkUrlStatus_(url, function(error) {
+ if (error) {
+ return errorFn(error);
+ }
+ self.context.find('#test-frames').append('<iframe>');
+ frame = this.getFrame_();
+ frame.load(function() {
+ frame.unbind();
+ try {
+ self.executeAction(loadFn);
+ } catch (e) {
+ errorFn(e);
+ }
+ }).attr('src', url);
+ });
}
this.context.find('> h2 a').attr('href', url).text(url);
};
@@ -73,6 +116,9 @@ angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) {
angular.scenario.Application.prototype.executeAction = function(action) {
var self = this;
var $window = this.getWindow_();
+ if (!$window.document) {
+ throw 'Sandbox Error: Application document not accessible.';
+ }
if (!$window.angular) {
return action.call(this, $window, _jQuery($window.document));
}
diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js
index 14d530ac..d141c42b 100644
--- a/src/scenario/Scenario.js
+++ b/src/scenario/Scenario.js
@@ -1,3 +1,4 @@
+
/**
* Setup file for the Scenario.
* Must be first in the compilation/bootstrap list.
diff --git a/src/scenario/dsl.js b/src/scenario/dsl.js
index fcc2e5b2..358f1fce 100644
--- a/src/scenario/dsl.js
+++ b/src/scenario/dsl.js
@@ -62,7 +62,7 @@ angular.scenario.dsl('navigateTo', function() {
}
application.navigateTo(url, function() {
done(null, url);
- });
+ }, done);
});
};
});
@@ -271,7 +271,7 @@ angular.scenario.dsl('element', function() {
if (href && elements[0].nodeName.toUpperCase() === 'A') {
this.application.navigateTo(href, function() {
done();
- });
+ }, done);
} else {
done();
}
diff --git a/test/scenario/ApplicationSpec.js b/test/scenario/ApplicationSpec.js
index 3e1c862d..75a7e9c9 100644
--- a/test/scenario/ApplicationSpec.js
+++ b/test/scenario/ApplicationSpec.js
@@ -1,15 +1,24 @@
describe('angular.scenario.Application', function() {
var app, frames;
+ function callLoadHandlers(app) {
+ var handlers = app.getFrame_().data('events').load;
+ expect(handlers).toBeDefined();
+ expect(handlers.length).toEqual(1);
+ handlers[0].handler();
+ }
+
beforeEach(function() {
frames = _jQuery("<div></div>");
app = new angular.scenario.Application(frames);
+ app.checkUrlStatus_ = function(url, callback) {
+ callback.call(this);
+ };
});
it('should return new $window and $document after navigate', function() {
var called;
var testWindow, testDocument, counter = 0;
- app.navigateTo = noop;
app.getWindow_ = function() {
return {x:counter++, document:{x:counter++}};
};
@@ -50,6 +59,31 @@ describe('angular.scenario.Application', function() {
expect(app.getFrame_().attr('test')).toBeFalsy();
});
+ it('should call error handler if document not accessible', function() {
+ app.getWindow_ = function() {
+ return {};
+ };
+ app.navigateTo('about:blank', angular.noop, function(error) {
+ expect(error).toMatch(/Sandbox Error/);
+ });
+ callLoadHandlers(app);
+ });
+
+ it('should call error handler if using file:// URL', function() {
+ app.navigateTo('file://foo/bar.txt', angular.noop, function(error) {
+ expect(error).toMatch(/Sandbox Error/);
+ });
+ });
+
+ it('should call error handler if status check fails', function() {
+ app.checkUrlStatus_ = function(url, callback) {
+ callback.call(this, 'Example Error');
+ };
+ app.navigateTo('about:blank', angular.noop, function(error) {
+ expect(error).toEqual('Example Error');
+ });
+ });
+
it('should hide old iframes and navigate to about:blank', function() {
app.navigateTo('about:blank#foo');
app.navigateTo('about:blank#bar');
@@ -70,15 +104,12 @@ describe('angular.scenario.Application', function() {
it('should call onload handler when frame loads', function() {
var called;
app.getWindow_ = function() {
- return {};
+ return {document: {}};
};
app.navigateTo('about:blank', function($window, $document) {
called = true;
});
- var handlers = app.getFrame_().data('events').load;
- expect(handlers).toBeDefined();
- expect(handlers.length).toEqual(1);
- handlers[0].handler();
+ callLoadHandlers(app);
expect(called).toBeTruthy();
});
@@ -113,4 +144,67 @@ describe('angular.scenario.Application', function() {
expect(handlers.length).toEqual(1);
handlers[0]();
});
+
+ describe('jQuery ajax', function() {
+ var options;
+ var response;
+ var jQueryAjax;
+
+ beforeEach(function() {
+ response = {
+ status: 200,
+ statusText: 'OK'
+ };
+ jQueryAjax = _jQuery.ajax;
+ _jQuery.ajax = function(opts) {
+ options = opts;
+ opts.complete.call(this, response);
+ };
+ app.checkUrlStatus_ = angular.scenario.Application.
+ prototype.checkUrlStatus_;
+ });
+
+ afterEach(function() {
+ _jQuery.ajax = jQueryAjax;
+ });
+
+ it('should perform a HEAD request to verify file existence', function() {
+ app.navigateTo('http://www.google.com/', angular.noop, angular.noop);
+ expect(options.type).toEqual('HEAD');
+ expect(options.url).toEqual('http://www.google.com/');
+ });
+
+ it('should call error handler if status code is less than 200', function() {
+ var finished;
+ response.status = 199;
+ response.statusText = 'Error Message';
+ app.navigateTo('about:blank', angular.noop, function(error) {
+ expect(error).toEqual('199 Error Message');
+ finished = true;
+ });
+ expect(finished).toBeTruthy();
+ });
+
+ it('should call error handler if status code is greater than 299', function() {
+ var finished;
+ response.status = 300;
+ response.statusText = 'Error';
+ app.navigateTo('about:blank', angular.noop, function(error) {
+ expect(error).toEqual('300 Error');
+ finished = true;
+ });
+ expect(finished).toBeTruthy();
+ });
+
+ it('should call error handler if status code is 0 for sandbox error', function() {
+ var finished;
+ response.status = 0;
+ response.statusText = '';
+ app.navigateTo('about:blank', angular.noop, function(error) {
+ expect(error).toEqual('Sandbox Error: Cannot access about:blank');
+ finished = true;
+ });
+ expect(finished).toBeTruthy();
+ });
+ });
});