diff --git a/app/assets/javascripts/mobile/mobile_drawer.js b/app/assets/javascripts/mobile/mobile_drawer.js
index 547d703a54dfb27f2cce6781989783ced9cd3a61..7a96a37d45515c15171d25756f6b6abb588e53cc 100644
--- a/app/assets/javascripts/mobile/mobile_drawer.js
+++ b/app/assets/javascripts/mobile/mobile_drawer.js
@@ -1,21 +1,17 @@
 (function(){
   Diaspora.Mobile.Drawer = {
-    allAspects: $("#all_aspects"),
-    followedTags: $("#followed_tags"),
-    menuBadge: $("#menu-badge"),
-
     initialize: function(){
-      this.allAspects.bind("tap click", function(evt){
+      $("#all_aspects").bind("tap click", function(evt){
         evt.preventDefault();
         $(this).find("+ li").toggleClass("hide");
       });
 
-      this.menuBadge.bind("tap click", function(evt){
+      $("#menu-badge").bind("tap click", function(evt){
         evt.preventDefault();
         $("#app").toggleClass("draw");
       });
 
-      this.followedTags.bind("tap click", function(evt){
+      $("#followed_tags").bind("tap click", function(evt){
         evt.preventDefault();
         $(this).find("+ li").toggleClass("hide");
       });
diff --git a/app/assets/javascripts/mobile/mobile_post_actions.js b/app/assets/javascripts/mobile/mobile_post_actions.js
index 56109caed7e5fd24aea32813f99245819040af61..ff83a62f58e2298e8fc4e72ad6105c1aacc11eb9 100644
--- a/app/assets/javascripts/mobile/mobile_post_actions.js
+++ b/app/assets/javascripts/mobile/mobile_post_actions.js
@@ -48,7 +48,8 @@
         link.data("url", url.replace(/\/\d+$/, ""));
 
         if(likeCounter){
-          likeCounter.text(parseInt(likeCounter.text(), 10) - 1);
+          var newValue = parseInt(likeCounter.text(), 10) - 1;
+          likeCounter.text(Math.max(newValue, 0));
         }
       };
 
diff --git a/spec/controllers/jasmine_fixtures/aspects_spec.rb b/spec/controllers/jasmine_fixtures/aspects_spec.rb
index 2fcea501dcb5b9ad18a3eceab07e3e80b208d373..a2539dc97df90e14e3e6b07201b48278ee9b5e47 100644
--- a/spec/controllers/jasmine_fixtures/aspects_spec.rb
+++ b/spec/controllers/jasmine_fixtures/aspects_spec.rb
@@ -65,6 +65,20 @@ describe StreamsController, :type => :controller do
         save_fixture(html_for("body"), "aspects_index_mobile_post_with_comments")
       end
 
+      it "generates a mobile jasmine fixture with a public post", fixture: true do
+        message = bob.post(:status_message, text: "HALO WHIRLED", public: true)
+        5.times { bob.comment!(message, "what") }
+        get :aspects, format: :mobile
+        save_fixture(html_for("body"), "aspects_index_mobile_public_post")
+      end
+
+      it "generates a mobile jasmine fixture with an NSFW post", fixture: true do
+        message = bob.post(:status_message, text: "#NSFW", to: @bob.aspects.where(name: "generic").first.id)
+        5.times { bob.comment!(message, "what") }
+        get :aspects, format: :mobile
+        save_fixture(html_for("body"), "aspects_index_mobile_nsfw_post")
+      end
+
       it 'generates a jasmine fixture with a followed tag', :fixture => true do
         @tag = ActsAsTaggableOn::Tag.create!(:name => "partytimeexcellent")
         TagFollowing.create!(:tag => @tag, :user => alice)
diff --git a/spec/javascripts/mobile/mobile_application_spec.js b/spec/javascripts/mobile/mobile_application_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee9655dda6623ff1cbdf9577c14b3919d75a32ad
--- /dev/null
+++ b/spec/javascripts/mobile/mobile_application_spec.js
@@ -0,0 +1,22 @@
+describe("Diaspora.Mobile", function(){
+  describe("initialize", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_nsfw_post");
+      spyOn(window, "autosize");
+    });
+
+    it("calls autosize for textareas", function(){
+      Diaspora.Mobile.initialize();
+      expect(window.autosize).toHaveBeenCalled();
+      expect(window.autosize.calls.mostRecent().args[0].selector).toBe("textarea");
+    });
+
+    it("deactivates shield", function(){
+      Diaspora.Mobile.initialize();
+      var $shield = $(".stream_element").first();
+      expect($shield).toHaveClass("shield-active");
+      $shield.find(".shield a").click();
+      expect($shield).not.toHaveClass("shield-active");
+    });
+  });
+});
diff --git a/spec/javascripts/mobile/mobile_drawer_spec.js b/spec/javascripts/mobile/mobile_drawer_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..fe70a640a2021b3cd04d87c0574a8ce45170ab11
--- /dev/null
+++ b/spec/javascripts/mobile/mobile_drawer_spec.js
@@ -0,0 +1,47 @@
+describe("Diaspora.Mobile.Drawer", function(){
+  describe("initialize", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_post_with_comments");
+      Diaspora.Mobile.Drawer.initialize();
+      this.menuBadge = $("#menu-badge");
+      this.followedTags = $("#followed_tags");
+      this.allAspects = $("#all_aspects");
+    });
+
+    it("correctly binds events", function(){
+      expect($._data(this.allAspects[0], "events").tap.length).not.toBe(0);
+      expect($._data(this.allAspects[0], "events").click.length).not.toBe(0);
+      expect($._data(this.followedTags[0], "events").tap.length).not.toBe(0);
+      expect($._data(this.followedTags[0], "events").click.length).not.toBe(0);
+      expect($._data(this.menuBadge[0], "events").tap.length).not.toBe(0);
+      expect($._data(this.menuBadge[0], "events").click.length).not.toBe(0);
+    });
+
+    it("opens and closes the drawer", function(){
+      var $app = $("#app");
+      expect($app).not.toHaveClass("draw");
+      this.menuBadge.click();
+      expect($app).toHaveClass("draw");
+      this.menuBadge.click();
+      expect($app).not.toHaveClass("draw");
+    });
+
+    it("shows and hides the aspects", function(){
+      var $aspectList = this.allAspects.find("+ li");
+      expect($aspectList).toHaveClass("hide");
+      this.allAspects.click();
+      expect($aspectList).not.toHaveClass("hide");
+      this.allAspects.click();
+      expect($aspectList).toHaveClass("hide");
+    });
+
+    it("shows and hides the followed tags", function(){
+      var $tagList = this.followedTags.find("+ li");
+      expect($tagList).toHaveClass("hide");
+      this.followedTags.click();
+      expect($tagList).not.toHaveClass("hide");
+      this.followedTags.click();
+      expect($tagList).toHaveClass("hide");
+    });
+  });
+});
diff --git a/spec/javascripts/mobile/mobile_post_actions_spec.js b/spec/javascripts/mobile/mobile_post_actions_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b2a2f570e8f8288db03ba48b3a91553e77138083
--- /dev/null
+++ b/spec/javascripts/mobile/mobile_post_actions_spec.js
@@ -0,0 +1,232 @@
+describe("Diaspora.Mobile.PostActions", function(){
+  describe("initialize", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_public_post");
+      spyOn(Diaspora.Mobile.PostActions, "onLike");
+      spyOn(Diaspora.Mobile.PostActions, "onReshare");
+      Diaspora.Mobile.PostActions.initialize();
+    });
+
+    it("binds the events", function(){
+      $(".stream .like-action").trigger("tap");
+      expect(Diaspora.Mobile.PostActions.onLike).toHaveBeenCalled();
+      $(".stream .like-action").click();
+      expect(Diaspora.Mobile.PostActions.onLike).toHaveBeenCalled();
+      $(".stream .reshare-action").trigger("tap");
+      expect(Diaspora.Mobile.PostActions.onReshare).toHaveBeenCalled();
+      $(".stream .reshare-action").click();
+      expect(Diaspora.Mobile.PostActions.onReshare).toHaveBeenCalled();
+    });
+  });
+
+  describe("toggleActive", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_public_post");
+      Diaspora.Mobile.PostActions.initialize();
+      this.link = $(".stream .like-action").first();
+    });
+
+    it("toggles active and inactive classes", function(){
+      expect(this.link).toHaveClass("inactive");
+      expect(this.link).not.toHaveClass("active");
+      Diaspora.Mobile.PostActions.toggleActive(this.link);
+      expect(this.link).not.toHaveClass("inactive");
+      expect(this.link).toHaveClass("active");
+      Diaspora.Mobile.PostActions.toggleActive(this.link);
+      expect(this.link).toHaveClass("inactive");
+      expect(this.link).not.toHaveClass("active");
+    });
+  });
+
+  describe("showLoader and hideLoader", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_public_post");
+      Diaspora.Mobile.PostActions.initialize();
+      this.link = $(".stream .like-action").first();
+    });
+
+    it("adds and removes loading class", function(){
+      expect(this.link).not.toHaveClass("loading");
+      Diaspora.Mobile.PostActions.showLoader(this.link);
+      expect(this.link).toHaveClass("loading");
+      Diaspora.Mobile.PostActions.hideLoader(this.link);
+      expect(this.link).not.toHaveClass("loading");
+    });
+  });
+
+  describe("onLike", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_public_post");
+      spyOn(Diaspora.Mobile.PostActions, "like");
+      spyOn(Diaspora.Mobile.PostActions, "unlike");
+      Diaspora.Mobile.PostActions.initialize();
+      this.link = $(".stream .like-action").first();
+    });
+
+    it("doesn't activate the link if loading", function(){
+      this.link.addClass("loading");
+      this.link.click();
+      expect(Diaspora.Mobile.PostActions.like).not.toHaveBeenCalled();
+      expect(Diaspora.Mobile.PostActions.unlike).not.toHaveBeenCalled();
+    });
+
+    it("calls like if like button is inactive", function(){
+      this.link.removeClass("active").addClass("inactive");
+      this.link.click();
+      expect(Diaspora.Mobile.PostActions.like).toHaveBeenCalled();
+    });
+
+    it("calls unlike if like button is active", function(){
+      this.link.removeClass("inactive").addClass("active");
+      this.link.click();
+      expect(Diaspora.Mobile.PostActions.unlike).toHaveBeenCalled();
+    });
+  });
+
+  describe("like", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_public_post");
+      Diaspora.Mobile.PostActions.initialize();
+      this.link = $(".stream .like-action").first();
+      this.likeCounter = this.link.closest(".stream_element").find(".like-count");
+    });
+
+    it("always calls showLoader before sending request", function(){
+      spyOn(Diaspora.Mobile.PostActions, "showLoader");
+
+      Diaspora.Mobile.PostActions.like(this.likeCounter, this.link);
+      expect(Diaspora.Mobile.PostActions.showLoader).toHaveBeenCalled();
+    });
+
+    it("always calls hideLoader after receiving response", function(){
+      spyOn(Diaspora.Mobile.PostActions, "hideLoader");
+
+      Diaspora.Mobile.PostActions.like(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 400});
+      expect(Diaspora.Mobile.PostActions.hideLoader).toHaveBeenCalled();
+      Diaspora.Mobile.PostActions.like(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 201, responseText: "{\"id\": \"18\"}"});
+      expect(Diaspora.Mobile.PostActions.hideLoader).toHaveBeenCalled();
+    });
+
+    it("doesn't activate the link on error", function(){
+      spyOn(Diaspora.Mobile.PostActions, "toggleActive");
+
+      Diaspora.Mobile.PostActions.like(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 400});
+      expect(Diaspora.Mobile.PostActions.toggleActive).not.toHaveBeenCalled();
+      expect(this.likeCounter.text()).toBe("0");
+    });
+
+    it("activates link on success", function(){
+      spyOn(Diaspora.Mobile.PostActions, "toggleActive");
+      var data = this.link.data("url");
+
+      Diaspora.Mobile.PostActions.like(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 201, responseText: "{\"id\": \"18\"}"});
+      expect(Diaspora.Mobile.PostActions.toggleActive).toHaveBeenCalled();
+      expect(this.likeCounter.text()).toBe("1");
+      expect(this.link.data("url")).toBe(data + "/18");
+    });
+  });
+
+  describe("unlike", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_public_post");
+      Diaspora.Mobile.PostActions.initialize();
+      this.link = $(".stream .like-action").first();
+      this.likeCounter = this.link.closest(".stream_element").find(".like-count");
+      Diaspora.Mobile.PostActions.like(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 201, responseText: "{\"id\": \"18\"}"});
+    });
+
+    it("always calls showLoader before sending request", function(){
+      spyOn(Diaspora.Mobile.PostActions, "showLoader");
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      expect(Diaspora.Mobile.PostActions.showLoader).toHaveBeenCalled();
+    });
+
+    it("always calls hideLoader after receiving response", function(){
+      spyOn(Diaspora.Mobile.PostActions, "hideLoader");
+
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 400});
+      expect(Diaspora.Mobile.PostActions.hideLoader).toHaveBeenCalled();
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 204});
+      expect(Diaspora.Mobile.PostActions.hideLoader).toHaveBeenCalled();
+    });
+
+    it("doesn't unlike on error", function(){
+      spyOn(Diaspora.Mobile.PostActions, "toggleActive");
+
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 400});
+      expect(Diaspora.Mobile.PostActions.toggleActive).not.toHaveBeenCalled();
+      expect(this.likeCounter.text()).toBe("1");
+    });
+
+    it("deactivates link on success", function(){
+      spyOn(Diaspora.Mobile.PostActions, "toggleActive");
+      var data = this.link.data("url");
+
+      expect(this.likeCounter.text()).toBe("1");
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 204});
+      expect(Diaspora.Mobile.PostActions.toggleActive).toHaveBeenCalled();
+      expect(this.likeCounter.text()).toBe("0");
+      expect(this.link.data("url")).toBe(data.replace(/\/\d+$/, ""));
+    });
+
+    it("doesn't produce negative like count", function(){
+      expect(this.likeCounter.text()).toBe("1");
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 204});
+      expect(this.likeCounter.text()).toBe("0");
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 204});
+      expect(this.likeCounter.text()).toBe("0");
+      Diaspora.Mobile.PostActions.unlike(this.likeCounter, this.link);
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 204});
+      expect(this.likeCounter.text()).toBe("0");
+    });
+  });
+
+  describe("onReshare", function(){
+    beforeEach(function(){
+      spec.loadFixture("aspects_index_mobile_public_post");
+      Diaspora.Mobile.PostActions.initialize();
+      this.reshareLink = $(".stream .reshare-action");
+      spyOn(window, "confirm").and.returnValue(true);
+      spyOn(window, "alert");
+    });
+
+    it("always calls showLoader before sending request and hideLoader after receiving response", function(){
+      spyOn(Diaspora.Mobile.PostActions, "hideLoader");
+      spyOn(Diaspora.Mobile.PostActions, "showLoader");
+
+      this.reshareLink.click();
+      expect(Diaspora.Mobile.PostActions.showLoader).toHaveBeenCalled();
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 400});
+      expect(Diaspora.Mobile.PostActions.hideLoader).toHaveBeenCalled();
+      this.reshareLink.click();
+      expect(Diaspora.Mobile.PostActions.showLoader).toHaveBeenCalled();
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 201, responseText: "{}"});
+      expect(Diaspora.Mobile.PostActions.hideLoader).toHaveBeenCalled();
+    });
+
+    it("calls toggleActive on success", function(){
+      spyOn(Diaspora.Mobile.PostActions, "toggleActive");
+
+      this.reshareLink.click();
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 201, responseText: "{}"});
+      expect(Diaspora.Mobile.PostActions.toggleActive).toHaveBeenCalledWith(this.reshareLink);
+    });
+
+    it("pops an alert on error", function(){
+      this.reshareLink.click();
+      jasmine.Ajax.requests.mostRecent().respondWith({status: 400});
+      expect(window.alert).toHaveBeenCalledWith(Diaspora.I18n.t("failed_to_reshare"));
+    });
+  });
+});