From 2b31999f288f62b250d9658224b7c5d9f836b795 Mon Sep 17 00:00:00 2001 From: Dan Hansen Date: Wed, 31 Aug 2011 00:00:40 -0500 Subject: [PATCH] pull in jasmine-ajax, start testing our ajax callbacks with meaningful responses --- spec/controllers/comments_controller_spec.rb | 8 + spec/controllers/likes_controller_spec.rb | 7 + spec/javascripts/helpers/mock-ajax.js | 207 ++++++++++++++++++ .../widgets/comment-stream-spec.js | 49 ++++- spec/javascripts/widgets/likes-spec.js | 14 +- 5 files changed, 271 insertions(+), 14 deletions(-) create mode 100644 spec/javascripts/helpers/mock-ajax.js diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index 126aaf963..cf7a67189 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -128,6 +128,14 @@ describe CommentsController do @message = bob.post(:status_message, :text => "hey", :to => aspect_to_post.id) @comments = [alice, bob, eve].map{ |u| u.comment("hey", :post => @message) } end + + it 'generates a jasmine fixture', :fixture => true do + 2.times { alice.comment("hey", :post => @message) } + get :index, :post_id => @message.id + + save_fixture(response.body, "ajax_comments_on_post") + end + it 'works for mobile' do get :index, :post_id => @message.id, :format => 'mobile' response.should be_success diff --git a/spec/controllers/likes_controller_spec.rb b/spec/controllers/likes_controller_spec.rb index 156b64692..4d7b7e19a 100644 --- a/spec/controllers/likes_controller_spec.rb +++ b/spec/controllers/likes_controller_spec.rb @@ -83,6 +83,13 @@ describe LikesController do @message = alice.post(:status_message, :text => "hey", :to => @alices_aspect.id) @message = alice.comment( "hey", :post => @message) if class_const == Comment end + + it 'generates a jasmine fixture', :fixture => true do + get :index, id_field => @message.id + + save_fixture(response.body, "ajax_likes_on_#{class_const.to_s.underscore}") + end + it 'returns a 404 for a post not visible to the user' do sign_in eve get :index, id_field => @message.id diff --git a/spec/javascripts/helpers/mock-ajax.js b/spec/javascripts/helpers/mock-ajax.js new file mode 100644 index 000000000..249efc907 --- /dev/null +++ b/spec/javascripts/helpers/mock-ajax.js @@ -0,0 +1,207 @@ +/* + Jasmine-Ajax : a set of helpers for testing AJAX requests under the Jasmine + BDD framework for JavaScript. + + Supports both Prototype.js and jQuery. + + http://github.com/pivotal/jasmine-ajax + + Jasmine Home page: http://pivotal.github.com/jasmine + + Copyright (c) 2008-2010 Pivotal Labs + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +// Jasmine-Ajax interface +var ajaxRequests = []; + +function mostRecentAjaxRequest() { + if (ajaxRequests.length > 0) { + return ajaxRequests[ajaxRequests.length - 1]; + } else { + return null; + } +} + +function clearAjaxRequests() { + ajaxRequests = []; +} + +// Fake XHR for mocking Ajax Requests & Responses +function FakeXMLHttpRequest() { + var extend = Object.extend || $.extend; + extend(this, { + requestHeaders: {}, + + open: function() { + this.method = arguments[0]; + this.url = arguments[1]; + this.readyState = 1; + }, + + setRequestHeader: function(header, value) { + this.requestHeaders[header] = value; + }, + + abort: function() { + this.readyState = 0; + }, + + readyState: 0, + + onreadystatechange: function(isTimeout) { + }, + + status: null, + + send: function(data) { + this.params = data; + this.readyState = 2; + }, + + getResponseHeader: function(name) { + return this.responseHeaders[name]; + }, + + getAllResponseHeaders: function() { + var responseHeaders = []; + for (var i in this.responseHeaders) { + if (this.responseHeaders.hasOwnProperty(i)) { + responseHeaders.push(i + ': ' + this.responseHeaders[i]); + } + } + return responseHeaders.join('\r\n'); + }, + + responseText: null, + + response: function(response) { + this.status = response.status; + this.responseText = response.responseText || ""; + this.readyState = 4; + this.responseHeaders = response.responseHeaders || + {"Content-type": response.contentType || "application/json" }; + // uncomment for jquery 1.3.x support + // jasmine.Clock.tick(20); + + this.onreadystatechange(); + }, + responseTimeout: function() { + this.readyState = 4; + jasmine.Clock.tick(jQuery.ajaxSettings.timeout || 30000); + this.onreadystatechange('timeout'); + } + }); + + return this; +} + + +jasmine.Ajax = { + + isInstalled: function() { + return jasmine.Ajax.installed == true; + }, + + assertInstalled: function() { + if (!jasmine.Ajax.isInstalled()) { + throw new Error("Mock ajax is not installed, use jasmine.Ajax.useMock()") + } + }, + + useMock: function() { + if (!jasmine.Ajax.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Ajax.uninstallMock); + + jasmine.Ajax.installMock(); + } + }, + + installMock: function() { + if (typeof jQuery != 'undefined') { + jasmine.Ajax.installJquery(); + } else if (typeof Prototype != 'undefined') { + jasmine.Ajax.installPrototype(); + } else { + throw new Error("jasmine.Ajax currently only supports jQuery and Prototype"); + } + jasmine.Ajax.installed = true; + }, + + installJquery: function() { + jasmine.Ajax.mode = 'jQuery'; + jasmine.Ajax.real = jQuery.ajaxSettings.xhr; + jQuery.ajaxSettings.xhr = jasmine.Ajax.jQueryMock; + + }, + + installPrototype: function() { + jasmine.Ajax.mode = 'Prototype'; + jasmine.Ajax.real = Ajax.getTransport; + + Ajax.getTransport = jasmine.Ajax.prototypeMock; + }, + + uninstallMock: function() { + jasmine.Ajax.assertInstalled(); + if (jasmine.Ajax.mode == 'jQuery') { + jQuery.ajaxSettings.xhr = jasmine.Ajax.real; + } else if (jasmine.Ajax.mode == 'Prototype') { + Ajax.getTransport = jasmine.Ajax.real; + } + jasmine.Ajax.reset(); + }, + + reset: function() { + jasmine.Ajax.installed = false; + jasmine.Ajax.mode = null; + jasmine.Ajax.real = null; + }, + + jQueryMock: function() { + var newXhr = new FakeXMLHttpRequest(); + ajaxRequests.push(newXhr); + return newXhr; + }, + + prototypeMock: function() { + return new FakeXMLHttpRequest(); + }, + + installed: false, + mode: null +} + + +// Jasmine-Ajax Glue code for Prototype.js +if (typeof Prototype != 'undefined' && Ajax && Ajax.Request) { + Ajax.Request.prototype.originalRequest = Ajax.Request.prototype.request; + Ajax.Request.prototype.request = function(url) { + this.originalRequest(url); + ajaxRequests.push(this); + }; + + Ajax.Request.prototype.response = function(responseOptions) { + return this.transport.response(responseOptions); + }; +} \ No newline at end of file diff --git a/spec/javascripts/widgets/comment-stream-spec.js b/spec/javascripts/widgets/comment-stream-spec.js index b367c558b..9f3fb2fa8 100644 --- a/spec/javascripts/widgets/comment-stream-spec.js +++ b/spec/javascripts/widgets/comment-stream-spec.js @@ -1,7 +1,11 @@ describe("Diaspora.Widgets.CommentStream", function() { var commentStream; beforeEach(function() { + spec.readFixture("ajax_comments_on_post"); + jasmine.Clock.useMock(); + jasmine.Ajax.useMock(); + spec.loadFixture("aspects_index_with_posts"); Diaspora.I18n.locale = { }; @@ -13,7 +17,21 @@ describe("Diaspora.Widgets.CommentStream", function() { describe("toggling comments", function() { it("toggles class hidden on the comments ul", function () { - spyOn($, "ajax"); + spyOn($, "ajax").andCallThrough(); + + expect($("ul.comments:first")).not.toHaveClass("hidden"); + + commentStream.showComments($.Event()); + + mostRecentAjaxRequest().response({ + responseHeaders: { + "Content-type": "text/html" + }, + responseText: spec.readFixture("ajax_comments_on_post"), + status: 200 + }); + + jasmine.Clock.tick(200); expect($("ul.comments:first")).not.toHaveClass("hidden"); @@ -22,16 +40,10 @@ describe("Diaspora.Widgets.CommentStream", function() { jasmine.Clock.tick(200); expect($("ul.comments:first")).toHaveClass("hidden"); - - commentStream.showComments($.Event()); - - $.ajax.mostRecentCall.args[0].success("comment response"); - jasmine.Clock.tick(200); - - expect($("ul.comments:first")).not.toHaveClass("hidden"); }); it("changes the text on the show comments link", function() { + spyOn($, "ajax").andCallThrough(); Diaspora.I18n.loadLocale({'comments' : { 'show': 'show comments translation', 'hide': 'hide comments translation' @@ -41,6 +53,14 @@ describe("Diaspora.Widgets.CommentStream", function() { commentStream.showComments($.Event()); + mostRecentAjaxRequest().response({ + responseHeaders: { + "Content-type": "text/html" + }, + responseText: spec.readFixture("ajax_comments_on_post"), + status: 200 + }); + jasmine.Clock.tick(200); expect(link.text()).toEqual("hide comments translation"); @@ -53,13 +73,20 @@ describe("Diaspora.Widgets.CommentStream", function() { }); it("only requests the comments when the loaded class is not present", function() { - spyOn($, "ajax"); - + spyOn($, "ajax").andCallThrough(); + expect(commentStream.commentsList).not.toHaveClass("loaded"); commentStream.showComments($.Event()); - $.ajax.mostRecentCall.args[0].success("comment response"); + mostRecentAjaxRequest().response({ + responseHeaders: { + "Content-type": "text/html" + }, + responseText: spec.readFixture("ajax_comments_on_post"), + status: 200 + }); + expect($.ajax.callCount).toEqual(1); expect(commentStream.commentsList).toHaveClass("loaded"); diff --git a/spec/javascripts/widgets/likes-spec.js b/spec/javascripts/widgets/likes-spec.js index f138abb98..d5e9ea295 100644 --- a/spec/javascripts/widgets/likes-spec.js +++ b/spec/javascripts/widgets/likes-spec.js @@ -1,8 +1,10 @@ describe("Diaspora.Widgets.Likes", function() { var likes; beforeEach(function() { + spec.readFixture("ajax_likes_on_post"); spec.loadFixture("aspects_index_with_a_post_with_likes"); likes = Diaspora.BaseWidget.instantiate("Likes", $(".stream_element .likes_container")); + jasmine.Ajax.useMock(); $.fx.off = true; }); @@ -42,13 +44,19 @@ describe("Diaspora.Widgets.Likes", function() { }); it("makes the likes list's html the response of the request", function() { - spyOn($, "ajax"); + spyOn($, "ajax").andCallThrough(); likes.expandLikes($.Event()); - $.ajax.mostRecentCall.args[0].success("some html response"); + mostRecentAjaxRequest().response({ + responseHeaders: { + "Content-type": "text/html" + }, + responseText: spec.readFixture("ajax_likes_on_post"), + status: 200 + }); - expect($(".stream_element .likes_list").html()).toEqual("some html response"); + expect($(".stream_element .likes_list").html()).toEqual(spec.readFixture("ajax_likes_on_post")); }); });