From e424896822523fbb87bd08ebc660634a26dd7b88 Mon Sep 17 00:00:00 2001 From: Augier <contact@c-henry.fr> Date: Sun, 10 Jul 2016 13:53:26 +0200 Subject: [PATCH] Fully port conversations to Backbone and drop inbox.js --- app/assets/javascripts/app/router.js | 9 +- .../app/views/conversations_form_view.js | 36 ++-- .../app/views/conversations_inbox_view.js | 82 +++++++++ .../app/views/conversations_view.js | 59 ------- app/assets/javascripts/inbox.js | 21 --- app/assets/javascripts/jasmine-load-all.js | 1 - app/assets/stylesheets/mobile/mobile.scss | 3 +- app/controllers/conversations_controller.rb | 18 +- app/views/conversations/_messages.haml | 17 +- app/views/conversations/_new.haml | 27 +-- app/views/conversations/create.js.erb | 4 +- app/views/conversations/index.haml | 36 ++-- app/views/conversations/index.mobile.haml | 2 +- app/views/conversations/new.mobile.haml | 2 +- app/views/conversations/show.js.erb | 10 -- app/views/people/contacts.haml | 2 +- config/locales/diaspora/en.yml | 2 +- config/locales/javascript/javascript.en.yml | 2 + config/routes.rb | 1 + features/desktop/conversations.feature | 16 +- .../step_definitions/conversations_steps.rb | 24 +-- .../conversations_controller_spec.rb | 30 +++- spec/javascripts/app/router_spec.js | 24 +++ .../app/views/conversations_form_view_spec.js | 110 ++++++++++++ .../views/conversations_inbox_view_spec.js | 164 ++++++++++++++++++ .../app/views/conversations_view_spec.js | 79 --------- 26 files changed, 513 insertions(+), 268 deletions(-) create mode 100644 app/assets/javascripts/app/views/conversations_inbox_view.js delete mode 100644 app/assets/javascripts/app/views/conversations_view.js delete mode 100644 app/assets/javascripts/inbox.js delete mode 100644 app/views/conversations/show.js.erb create mode 100644 spec/javascripts/app/views/conversations_form_view_spec.js create mode 100644 spec/javascripts/app/views/conversations_inbox_view_spec.js delete mode 100644 spec/javascripts/app/views/conversations_view_spec.js diff --git a/app/assets/javascripts/app/router.js b/app/assets/javascripts/app/router.js index 160123505b..cda3ec98d2 100644 --- a/app/assets/javascripts/app/router.js +++ b/app/assets/javascripts/app/router.js @@ -9,7 +9,7 @@ app.Router = Backbone.Router.extend({ "commented(/)": "stream", "community_spotlight(/)": "spotlight", "contacts(/)": "contacts", - "conversations(/)": "conversations", + "conversations(/)(:id)(/)": "conversations", "followed_tags(/)": "followed_tags", "getting_started(/)": "gettingStarted", "help(/)": "help", @@ -93,8 +93,11 @@ app.Router = Backbone.Router.extend({ app.page = new app.pages.Contacts({stream: stream}); }, - conversations: function() { - app.conversations = new app.views.Conversations(); + conversations: function(id) { + app.conversations = app.conversations || new app.views.ConversationsInbox(); + if (parseInt("" + id, 10)) { + app.conversations.renderConversation(id); + } }, /* eslint-disable camelcase */ diff --git a/app/assets/javascripts/app/views/conversations_form_view.js b/app/assets/javascripts/app/views/conversations_form_view.js index 53dbee6812..ebd59f1b41 100644 --- a/app/assets/javascripts/app/views/conversations_form_view.js +++ b/app/assets/javascripts/app/views/conversations_form_view.js @@ -1,28 +1,24 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later app.views.ConversationsForm = Backbone.View.extend({ + el: ".conversations-form-container", events: { - "keydown textarea#conversation_text" : "keyDown", + "keydown .conversation-message-text": "keyDown", + "submit #conversation-new": "onSubmitNewConversation" }, initialize: function(opts) { this.contacts = _.has(opts, "contacts") ? opts.contacts : null; - if(!this.contacts || this.contacts.length === 0) { - this.displayNoContactsMessage(); - return; - } this.prefill = []; if (_.has(opts, "prefillName") && _.has(opts, "prefillValue")) { - this.prefill = [{name : opts.prefillName, - value : opts.prefillValue}]; + this.prefill = [{name: opts.prefillName, value: opts.prefillValue}]; } - this.autocompleteInput = $("#contact_autocomplete"); this.prepareAutocomplete(this.contacts); }, prepareAutocomplete: function(data){ - this.autocompleteInput.autoSuggest(data, { + this.$("#contact-autocomplete").autoSuggest(data, { selectedItemProp: "name", searchObjProps: "name", asHtmlID: "contact_ids", @@ -32,20 +28,26 @@ app.views.ConversationsForm = Backbone.View.extend({ startText: '', emptyText: Diaspora.I18n.t("no_results"), preFill: this.prefill - }).focus(); - $("#contact_ids").attr("aria-labelledby", "toLabel"); - }, - - displayNoContactsMessage: function() { - $("form#new_conversation").replaceWith( - "<div class=\"well text-center\">" + Diaspora.I18n.t("conversation.new.no_contacts") + "</div>" - ); + }); + $("#contact_ids").attr("aria-labelledby", "toLabel").focus(); }, keyDown : function(evt) { if(evt.which === Keycodes.ENTER && evt.ctrlKey) { $(evt.target).parents("form").submit(); } + }, + + getConversationParticipants: function() { + return this.$("#as-values-contact_ids").val().split(","); + }, + + onSubmitNewConversation: function(evt) { + evt.preventDefault(); + if (this.getConversationParticipants().length === 0) { + evt.stopPropagation(); + app.flashMessages.error(Diaspora.I18n.t("conversation.create.no_recipient")); + } } }); // @license-end diff --git a/app/assets/javascripts/app/views/conversations_inbox_view.js b/app/assets/javascripts/app/views/conversations_inbox_view.js new file mode 100644 index 0000000000..97e31f5c38 --- /dev/null +++ b/app/assets/javascripts/app/views/conversations_inbox_view.js @@ -0,0 +1,82 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +app.views.ConversationsInbox = Backbone.View.extend({ + el: "#conversations-container", + + events: { + "click .conversation-wrapper": "displayConversation", + "click .new-conversation-btn": "displayNewConversation" + }, + + initialize: function() { + new app.views.ConversationsForm({contacts: gon.contacts}); + this.setupConversation(); + }, + + renderConversation: function(conversationId) { + var self = this; + $.ajax({ + url: Routes.conversationRaw(conversationId), + dataType: "html", + success: function(data) { + self.$el.find("#conversation-new").addClass("hidden"); + self.$el.find("#conversation-show").removeClass("hidden").html(data); + self.selectConversation(conversationId); + self.setupConversation(); + } + }); + }, + + selectConversation: function(conversationId) { + this.$el.find("#conversation-inbox .stream-element").removeClass("selected"); + if (conversationId) { + this.$el.find("#conversation-inbox .stream-element[data-guid='" + conversationId + "']").addClass("selected"); + } + }, + + displayNewConversation: function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + this.$el.find("#conversation-new").removeClass("hidden"); + this.$el.find("#conversation-show").addClass("hidden"); + this.selectConversation(); + app.router.navigate(Routes.conversations()); + }, + + setupConversation: function() { + app.helpers.timeago($(this.el)); + $(".control-icons a").tooltip({placement: "bottom"}); + + var conv = $(".conversation-wrapper .stream-element.selected"), + cBadge = $("#conversations-link .badge"); + + if (conv.hasClass("unread")) { + var unreadCount = parseInt(conv.find(".unread-message-count").text(), 10); + + if (cBadge.text() !== "") { + cBadge.text().replace(/\d+/, function(num) { + num = parseInt(num, 10) - unreadCount; + if (num > 0) { + cBadge.text(num); + } else { + cBadge.text(0).addClass("hidden"); + } + }); + } + conv.removeClass("unread"); + conv.find(".unread-message-count").remove(); + + var pos = $("#first_unread").offset().top - 50; + $("html").animate({scrollTop: pos}); + } else { + $("html").animate({scrollTop: 0}); + } + }, + + displayConversation: function(evt) { + var $target = $(evt.target).closest(".conversation-wrapper"); + app.router.navigate($target.data("conversation-path"), {trigger: true}); + } +}); +// @license-end + diff --git a/app/assets/javascripts/app/views/conversations_view.js b/app/assets/javascripts/app/views/conversations_view.js deleted file mode 100644 index 524b93f6b7..0000000000 --- a/app/assets/javascripts/app/views/conversations_view.js +++ /dev/null @@ -1,59 +0,0 @@ -// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - -app.views.Conversations = Backbone.View.extend({ - - el: "#conversations_container", - - events: { - "keydown textarea#message_text" : "keyDown", - "conversation:loaded" : "setupConversation" - }, - - initialize: function() { - if($("#conversation_new:visible").length > 0) { - new app.views.ConversationsForm({ - el: $("#conversation_new"), - contacts: gon.contacts - }); - } - this.setupConversation(); - }, - - setupConversation: function() { - app.helpers.timeago($(this.el)); - $(".control-icons a").tooltip({placement: "bottom"}); - - var conv = $(".conversation-wrapper .stream-element.selected"), - cBadge = $("#conversations-link .badge"); - - if(conv.hasClass("unread") ){ - var unreadCount = parseInt(conv.find(".unread-message-count").text(), 10); - - if(cBadge.text() !== "") { - cBadge.text().replace(/\d+/, function(num){ - num = parseInt(num, 10) - unreadCount; - if(num > 0) { - cBadge.text(num); - } else { - cBadge.text(0).addClass("hidden"); - } - }); - } - conv.removeClass("unread"); - conv.find(".unread-message-count").remove(); - - var pos = $("#first_unread").offset().top - 50; - $("html").animate({scrollTop:pos}); - } else { - $("html").animate({scrollTop:0}); - } - }, - - keyDown : function(evt) { - if(evt.which === Keycodes.ENTER && evt.ctrlKey) { - $(evt.target).parents("form").submit(); - } - } -}); -// @license-end - diff --git a/app/assets/javascripts/inbox.js b/app/assets/javascripts/inbox.js deleted file mode 100644 index df8bc02195..0000000000 --- a/app/assets/javascripts/inbox.js +++ /dev/null @@ -1,21 +0,0 @@ -// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later -$(document).ready(function(){ - $(document).on('click', '.conversation-wrapper', function(){ - var conversation_path = $(this).data('conversation-path'); - $.getScript(conversation_path, function() { - Diaspora.page.directionDetector.updateBinds(); - }); - history.pushState(null, "", conversation_path); - return false; - }); - - $(window).bind("popstate", function(){ - if (/conversations\/\d+/.test(location.href)) { - $.getScript(location.href, function() { - Diaspora.page.directionDetector.updateBinds(); - }); - return false; - } - }); -}); -// @license-end diff --git a/app/assets/javascripts/jasmine-load-all.js b/app/assets/javascripts/jasmine-load-all.js index 37920dc400..c84835fe3f 100644 --- a/app/assets/javascripts/jasmine-load-all.js +++ b/app/assets/javascripts/jasmine-load-all.js @@ -3,7 +3,6 @@ //= require templates //= require main //= require fileuploader-custom -//= require inbox //= require mobile/mobile //= require jquery.autoSuggest.custom //= require contact-list diff --git a/app/assets/stylesheets/mobile/mobile.scss b/app/assets/stylesheets/mobile/mobile.scss index 588ce26b80..193665b134 100644 --- a/app/assets/stylesheets/mobile/mobile.scss +++ b/app/assets/stylesheets/mobile/mobile.scss @@ -594,7 +594,8 @@ form#new_user.new_user input.btn { text-shadow: 1px 1px 20px rgb(126, 240, 77); } -#conversation_inbox, .notifications { +.conversation-inbox, +.notifications { div.pagination { width: 100%; margin-left: auto; diff --git a/app/controllers/conversations_controller.rb b/app/controllers/conversations_controller.rb index 11dccbd0a0..1a09e9d9da 100644 --- a/app/controllers/conversations_controller.rb +++ b/app/controllers/conversations_controller.rb @@ -24,7 +24,7 @@ class ConversationsController < ApplicationController gon.contacts = contacts_data respond_with do |format| - format.html + format.html { render "index", locals: {no_contacts: current_user.contacts.mutual.empty?} } format.json { render json: @visibilities.map(&:conversation), status: 200 } end end @@ -53,7 +53,7 @@ class ConversationsController < ApplicationController @response[:success] = false @response[:message] = I18n.t('conversations.create.fail') if person_ids.blank? - @response[:message] = I18n.t('conversations.create.no_contact') + @response[:message] = I18n.t("javascripts.conversation.create.no_recipient") end end respond_to do |format| @@ -64,7 +64,7 @@ class ConversationsController < ApplicationController def show respond_to do |format| format.html do - redirect_to conversations_path(:conversation_id => params[:id]) + redirect_to conversations_path(conversation_id: params[:id]) return end @@ -72,7 +72,6 @@ class ConversationsController < ApplicationController @first_unread_message_id = @conversation.first_unread_message(current_user).try(:id) @conversation.set_read(current_user) - format.js format.json { render :json => @conversation, :status => 200 } else redirect_to conversations_path @@ -80,6 +79,17 @@ class ConversationsController < ApplicationController end end + def raw + @conversation = current_user.conversations.where(id: params[:conversation_id]).first + if @conversation + @first_unread_message_id = @conversation.first_unread_message(current_user).try(:id) + @conversation.set_read(current_user) + render partial: "conversations/show", locals: {conversation: @conversation} + else + render nothing: true, status: 404 + end + end + def new if !params[:modal] && !session[:mobile_view] && request.format.html? redirect_to conversations_path diff --git a/app/views/conversations/_messages.haml b/app/views/conversations/_messages.haml index 4b8f3f6333..d41d0e574d 100644 --- a/app/views/conversations/_messages.haml +++ b/app/views/conversations/_messages.haml @@ -6,17 +6,18 @@ .media-left = owner_image_tag(:thumb_small) .media-body - = form_for [conversation, Message.new], html: {class: "control-group"} do |message| + = form_for [conversation, Message.new], html: {id: "response-message", class: "control-group"} do |message| .form-group - %label#messageLabel.sr-only{for: "message_text"} - = t("conversations.new.message") + %label.sr-only#message-label{for: "response-message-text"}= t("conversations.new.message") = message.text_area :text, - rows: 5, + rows: 5, tabindex: 1, - class: "form-control form-group", - aria: {labelledby: "messageLabel"} + id: "response-message-text", + class: "form-control form-group conversation-message-text", + aria: {labelledby: "message-label"} = message.submit t("conversations.show.reply"), - "data-disable-with" => t("conversations.show.replying"), - class: "btn btn-primary pull-right", tabindex: 2 + "data-disable-with" => t("conversations.show.replying"), + :class => "btn btn-primary pull-right", + :tabindex => 2 .clearfix diff --git a/app/views/conversations/_new.haml b/app/views/conversations/_new.haml index 2358b8b199..e8ab11d378 100644 --- a/app/views/conversations/_new.haml +++ b/app/views/conversations/_new.haml @@ -1,22 +1,25 @@ .container-fluid - = form_for Conversation.new, html: {class: "form-horizontal form_do_not_clear"}, remote: true do |conversation| + = form_for Conversation.new, html: {id: "new-conversation", + class: "new-conversation form-horizontal form-do-not-clear"}, remote: true do |conversation| .form-group %label#toLabel{for: "contact_ids"} = t(".to") - = text_field_tag "contact_autocomplete", nil, class: "form-control" + = text_field_tag "contact_autocomplete", nil, id: "contact-autocomplete", class: "form-control" .form-group - %label#subjectLabel{for: "conversation_subject"} + %label#subject-label{for: "conversation-subject"} = t(".subject") = conversation.text_field :subject, + id: "conversation-subject", class: "input-block-level form-control", - aria: {labelledby: "subjectLabel"} + aria: {labelledby: "subject-label"}, + value: "", + placeholder: t("conversations.new.subject_default") .form-group - %label#messageLabel.sr-only{for: "conversation_text"} - = t(".message") - = text_area_tag "conversation[text]", - "", - rows: 5, - class: "input-block-level form-control", - aria: {labelledby: "messageLabel"} + %label.sr-only#message-label{for: "new-message-text"} = t(".message") + = text_area_tag "conversation[text]", "", + rows: 5, + id: "new-message-text", + class: "conversation-message-text input-block-level form-control", + aria: {labelledby: "message-label"} .form-group - = conversation.submit t('.send'), 'data-disable-with' => t('.sending'), class: 'btn btn-primary pull-right' + = conversation.submit t(".send"), "data-disable-with" => t(".sending"), :class => "btn btn-primary pull-right" diff --git a/app/views/conversations/create.js.erb b/app/views/conversations/create.js.erb index 92ae238e2a..3310fcb5b0 100644 --- a/app/views/conversations/create.js.erb +++ b/app/views/conversations/create.js.erb @@ -1,10 +1,12 @@ var response = <%= raw @response.to_json %>; <% if session[:mobile_view] %> +if(response.success) { window.location.href = "<%= conversations_path(conversation_id: @conversation.id) %>"; +} <% else %> if(response.success){ app.flashMessages.success(response.message); - $("#new_conversation").removeClass('form_do_not_clear').clearForm(); + $("#new-conversation").removeClass('form-do-not-clear').clearForm(); window.location.href = "<%= conversations_path(conversation_id: @conversation.id) %>"; } else { app.flashMessages.error(response.message); diff --git a/app/views/conversations/index.haml b/app/views/conversations/index.haml index 31297c79e5..c60d0d22e6 100644 --- a/app/views/conversations/index.haml +++ b/app/views/conversations/index.haml @@ -1,41 +1,35 @@ -- content_for :head do - = javascript_include_tag :inbox - - content_for :page_title do - = t('.conversations_inbox') + = t(".conversations_inbox") -.container-fluid#conversations_container +.container-fluid#conversations-container .row .col-md-4 .sidebar#left_pane .sidebar-header.clearfix#left_pane_header .pull-right - = link_to t(".new_conversation"), conversations_path, class: "btn btn-default" + = link_to t(".new_conversation"), conversations_path, class: "new-conversation-btn btn btn-default" %h3 = t(".inbox") - .conversation-inbox#conversation_inbox - .stream.conversations + .conversation-inbox#conversation-inbox + .conversations-form-container.stream.conversations - if @visibilities.count > 0 = render partial: "conversations/conversation", collection: @visibilities, as: :visibility - else .no-conversations - = t('.no_messages') + = t(".no_messages") .pagination-container = will_paginate @visibilities, previous_label: "«", next_label: "»", inner_window: 1, renderer: WillPaginate::ActionView::BootstrapLinkRenderer - - .col-md-8 - - if @conversation - .stream_container - #conversation_show + .conversations-form-container.stream_container + #conversation-show{class: @conversation ? "" : "hidden"} + - if @conversation = render 'conversations/show', conversation: @conversation - - else - .stream_container.hidden - #conversation_show - .framed-content.clearfix#conversation_new + #conversation-new{class: @conversation ? "framed-content clearfix hidden" : "framed-content clearfix"} .new-conversation - %h3.text-center - = t("conversations.index.new_conversation") - = render "conversations/new" + %h3.text-center= t("conversations.index.new_conversation") + - if no_contacts + .well.text-center= t("javascripts.conversation.new.no_contacts") + - else + = render "conversations/new" diff --git a/app/views/conversations/index.mobile.haml b/app/views/conversations/index.mobile.haml index 89dfd53d32..fe6eefa320 100644 --- a/app/views/conversations/index.mobile.haml +++ b/app/views/conversations/index.mobile.haml @@ -12,7 +12,7 @@ .stream %p{ class: "conversation_#{name}" }= msg -#conversation_inbox +.conversation-inbox#conversation-inbox .stream.conversations - if @visibilities.count > 0 = render partial: "conversations/conversation", collection: @visibilities, as: :visibility diff --git a/app/views/conversations/new.mobile.haml b/app/views/conversations/new.mobile.haml index 5bc187d0d1..32d328b0fe 100644 --- a/app/views/conversations/new.mobile.haml +++ b/app/views/conversations/new.mobile.haml @@ -5,7 +5,7 @@ :javascript $(document).ready(function () { var data = $.parseJSON( "#{escape_javascript(@contacts_json)}" ), - autocompleteInput = $("#contact_autocomplete"); + autocompleteInput = $("#contact-autocomplete"); autocompleteInput.autoSuggest(data, { selectedItemProp: "name", diff --git a/app/views/conversations/show.js.erb b/app/views/conversations/show.js.erb deleted file mode 100644 index ad87047bb3..0000000000 --- a/app/views/conversations/show.js.erb +++ /dev/null @@ -1,10 +0,0 @@ -if($('.stream_container').hasClass('hidden')){ - $('#conversation_new').hide(); - $('.stream_container').removeClass('hidden'); -} - -$('#conversation_show').html("<%= escape_javascript(render('conversations/show', :conversation => @conversation)) %>"); - -$(".stream-element", "#conversation_inbox").removeClass('selected'); -$(".stream-element[data-guid='<%= @conversation.id %>']", "#conversation_inbox").addClass('selected'); -$('#conversation_show').trigger("conversation:loaded"); diff --git a/app/views/people/contacts.haml b/app/views/people/contacts.haml index 489f0eb17a..3eeddb0835 100644 --- a/app/views/people/contacts.haml +++ b/app/views/people/contacts.haml @@ -27,7 +27,7 @@ id: 'mentionModal' -if @contact - #new_conversation_pane + .conversations-form-container#new_conversation_pane = render 'shared/modal', path: new_conversation_path(:contact_id => @contact.id, name: @contact.person.name, modal: true), title: t('conversations.index.new_conversation'), diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index f98d9e467e..81bcfe05da 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -274,6 +274,7 @@ en: new_conversation: "New conversation" no_messages: "No messages" inbox: "Inbox" + no_contacts: "You need to add some contacts before you can start a conversation" show: reply: "Reply" replying: "Replying..." @@ -290,7 +291,6 @@ en: create: sent: "Message sent" fail: "Invalid message" - no_contact: "Hey, you need to add the contact first!" new_conversation: fail: "Invalid message" destroy: diff --git a/config/locales/javascript/javascript.en.yml b/config/locales/javascript/javascript.en.yml index 081057195f..2f1f7362c0 100644 --- a/config/locales/javascript/javascript.en.yml +++ b/config/locales/javascript/javascript.en.yml @@ -240,6 +240,8 @@ en: posts: "Posts" conversation: + create: + no_recipient: "Hey, you need to add a recipient first!" new: no_contacts: "You need to add some contacts before you can start a conversation." diff --git a/config/routes.rb b/config/routes.rb index 09fa750bb0..4e7b9d4987 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -77,6 +77,7 @@ Diaspora::Application.routes.draw do resources :conversations, except: %i(edit update destroy) do resources :messages, only: %i(create) delete 'visibility' => 'conversation_visibilities#destroy' + get "raw" end resources :notifications, :only => [:index, :update] do diff --git a/features/desktop/conversations.feature b/features/desktop/conversations.feature index 2f152b8ef8..2fcc54c2fd 100644 --- a/features/desktop/conversations.feature +++ b/features/desktop/conversations.feature @@ -18,10 +18,10 @@ Feature: private conversations Scenario: send a message When I sign in as "bob@bob.bob" And I send a message with subject "Greetings" and text "hello, alice!" to "Alice Awesome" - Then I should see "Greetings" within "#conversation_inbox" - And I should see "Greetings" within "#conversation_show" - And I should see "less than a minute ago" within "#conversation_inbox" - And I should see "less than a minute ago" within "#conversation_show" + Then I should see "Greetings" within "#conversation-inbox" + And I should see "Greetings" within "#conversation-show" + And I should see "less than a minute ago" within "#conversation-inbox" + And I should see "less than a minute ago" within "#conversation-show" And I should see "Alice Awesome" as a participant And "Alice Awesome" should be part of active conversation And I should see "hello, alice!" within ".stream_container" @@ -34,8 +34,8 @@ Feature: private conversations Scenario: send a message using keyboard shortcuts When I sign in as "bob@bob.bob" And I send a message with subject "Greetings" and text "hello, alice!" to "Alice Awesome" using keyboard shortcuts - Then I should see "Greetings" within "#conversation_inbox" - And I should see "Greetings" within "#conversation_show" + Then I should see "Greetings" within "#conversation-inbox" + And I should see "Greetings" within "#conversation-show" And "Alice Awesome" should be part of active conversation And I should see "hello, alice!" within ".stream_container" When I reply with "hey, how you doing?" using keyboard shortcuts @@ -47,9 +47,9 @@ Feature: private conversations Scenario: delete a conversation When I sign in as "bob@bob.bob" And I send a message with subject "Greetings" and text "hello, alice!" to "Alice Awesome" - Then I should see "Greetings" within "#conversation_inbox" + Then I should see "Greetings" within "#conversation-inbox" When I click on selector ".hide_conversation" - Then I should not see "Greetings" within "#conversation_inbox" + Then I should not see "Greetings" within "#conversation-inbox" When I sign in as "alice@alice.alice" Then I should have 1 unread private message And I should have 1 email delivery diff --git a/features/step_definitions/conversations_steps.rb b/features/step_definitions/conversations_steps.rb index d47c36bf76..1e7bace8b7 100644 --- a/features/step_definitions/conversations_steps.rb +++ b/features/step_definitions/conversations_steps.rb @@ -14,38 +14,38 @@ end Then /^I send a message with subject "([^"]*)" and text "([^"]*)" to "([^"]*)"$/ do |subject, text, person| step %(I am on the conversations page) - within("#conversation_new", match: :first) do + within("#new-conversation", match: :first) do step %(I fill in "contact_autocomplete" with "#{person}") step %(I press the first ".as-result-item" within ".as-results") - step %(I fill in "conversation_subject" with "#{subject}") - step %(I fill in "conversation_text" with "#{text}") + step %(I fill in "conversation-subject" with "#{subject}") + step %(I fill in "new-message-text" with "#{text}") step %(I press "Send") end end Then /^I send a message with subject "([^"]*)" and text "([^"]*)" to "([^"]*)" using keyboard shortcuts$/ do |subject, text, person| step %(I am on the conversations page) - within("#conversation_new", match: :first) do + within("#new-conversation", match: :first) do step %(I fill in "contact_autocomplete" with "#{person}") step %(I press the first ".as-result-item" within ".as-results") - step %(I fill in "conversation_subject" with "#{subject}") - step %(I fill in "conversation_text" with "#{text}") - find("#conversation_text").native.send_key %i(Ctrl Return) + step %(I fill in "conversation-subject" with "#{subject}") + step %(I fill in "new-message-text" with "#{text}") + find("#new-message-text").native.send_key %i(Ctrl Return) end end When /^I reply with "([^"]*)"$/ do |text| step %(I am on the conversations page) step %(I press the first ".conversation" within ".conversations") - step %(I fill in "message_text" with "#{text}") + step %(I fill in "response-message-text" with "#{text}") step %(I press "Reply") end When /^I reply with "([^"]*)" using keyboard shortcuts$/ do |text| step %(I am on the conversations page) step %(I press the first ".conversation" within ".conversations") - step %(I fill in "message_text" with "#{text}") - find("#message_text").native.send_key %i(Ctrl Return) + step %(I fill in "response-message-text" with "#{text}") + find("#response-message-text").native.send_key %i(Ctrl Return) end Then /^I send a mobile message with subject "([^"]*)" and text "([^"]*)" to "([^"]*)"$/ do |subject, text, person| @@ -53,8 +53,8 @@ Then /^I send a mobile message with subject "([^"]*)" and text "([^"]*)" to "([^ step %(I follow "New conversation") step %(I fill in "contact_autocomplete" with "#{person}") step %(I press the first ".as-result-item" within ".as-results") - step %(I fill in "conversation_subject" with "#{subject}") - step %(I fill in "conversation_text" with "#{text}") + step %(I fill in "conversation-subject" with "#{subject}") + step %(I fill in "new-message-text" with "#{text}") step %(I press "Send") end diff --git a/spec/controllers/conversations_controller_spec.rb b/spec/controllers/conversations_controller_spec.rb index 2dd8681c97..5ba05c98f5 100644 --- a/spec/controllers/conversations_controller_spec.rb +++ b/spec/controllers/conversations_controller_spec.rb @@ -259,7 +259,7 @@ describe ConversationsController, :type => :controller do it 'should set response with success to false and message to fail due to no contact' do post :create, @hash expect(assigns[:response][:success]).to eq(false) - expect(assigns[:response][:message]).to eq(I18n.t('conversations.create.no_contact')) + expect(assigns[:response][:message]).to eq(I18n.t("javascripts.conversation.create.no_recipient")) end end @@ -300,12 +300,6 @@ describe ConversationsController, :type => :controller do @conversation = Conversation.create(hash) end - it 'succeeds with js' do - xhr :get, :show, :id => @conversation.id, :format => :js - expect(response).to be_success - expect(assigns[:conversation]).to eq(@conversation) - end - it 'succeeds with json' do get :show, :id => @conversation.id, :format => :json expect(response).to be_success @@ -318,4 +312,26 @@ describe ConversationsController, :type => :controller do expect(response).to redirect_to(conversations_path(:conversation_id => @conversation.id)) end end + + describe "#raw" do + before do + hash = { + author: alice.person, + participant_ids: [alice.contacts.first.person.id, alice.person.id], + subject: "not spam", + messages_attributes: [{author: alice.person, text: "cool stuff"}] + } + @conversation = Conversation.create(hash) + end + + it "returns html of conversation" do + get :raw, conversation_id: @conversation.id + expect(response).to render_template(partial: "show", locals: {conversation: @conversation}) + end + + it "returns 404 when requesting non-existant conversation" do + get :raw, conversation_id: -1 + expect(response).to have_http_status(404) + end + end end diff --git a/spec/javascripts/app/router_spec.js b/spec/javascripts/app/router_spec.js index 3b185a5f24..640441dc39 100644 --- a/spec/javascripts/app/router_spec.js +++ b/spec/javascripts/app/router_spec.js @@ -80,6 +80,30 @@ describe('app.Router', function () { }); }); + describe("conversations", function() { + beforeEach(function() { + this.router = new app.Router(); + }); + + it("doesn't do anything if no conversation id is passed", function() { + spyOn(app.views.ConversationsInbox.prototype, "renderConversation"); + this.router.conversations(); + expect(app.views.ConversationsInbox.prototype.renderConversation).not.toHaveBeenCalled(); + }); + + it("doesn't do anything if id is not a readable number", function() { + spyOn(app.views.ConversationsInbox.prototype, "renderConversation"); + this.router.conversations("yolo"); + expect(app.views.ConversationsInbox.prototype.renderConversation).not.toHaveBeenCalled(); + }); + + it("renders the conversation if id is a readable number", function() { + spyOn(app.views.ConversationsInbox.prototype, "renderConversation"); + this.router.conversations("12"); + expect(app.views.ConversationsInbox.prototype.renderConversation).toHaveBeenCalledWith("12"); + }); + }); + describe("stream", function() { it("calls _initializeStreamView", function() { spyOn(app.router, "_initializeStreamView"); diff --git a/spec/javascripts/app/views/conversations_form_view_spec.js b/spec/javascripts/app/views/conversations_form_view_spec.js new file mode 100644 index 0000000000..79305115ad --- /dev/null +++ b/spec/javascripts/app/views/conversations_form_view_spec.js @@ -0,0 +1,110 @@ +describe("app.views.ConversationsForm", function() { + describe("keyDown", function() { + beforeEach(function() { + this.submitCallback = jasmine.createSpy().and.returnValue(false); + spec.loadFixture("conversations_read"); + new app.views.ConversationsForm(); + }); + + context("on new message form", function() { + beforeEach(function() { + $("#conversation-new").removeClass("hidden"); + $("#conversation-show").addClass("hidden"); + }); + + it("should submit the form with ctrl+enter", function() { + $("#new-conversation").submit(this.submitCallback); + var e = $.Event("keydown", {which: Keycodes.ENTER, ctrlKey: true}); + $("#new-message-text").trigger(e); + expect(this.submitCallback).toHaveBeenCalled(); + }); + + it("shouldn't submit the form without the ctrl key", function() { + $("#new-conversation").submit(this.submitCallback); + var e = $.Event("keydown", {which: Keycodes.ENTER, ctrlKey: false}); + $("#new-message-text").trigger(e); + expect(this.submitCallback).not.toHaveBeenCalled(); + }); + }); + + context("on response message form", function() { + beforeEach(function() { + $("#conversation-new").addClass("hidden"); + $("#conversation-show").removeClass("hidden"); + }); + + it("should submit the form with ctrl+enter", function() { + $("#response-message").submit(this.submitCallback); + var e = $.Event("keydown", {which: Keycodes.ENTER, ctrlKey: true}); + $("#response-message-text").trigger(e); + expect(this.submitCallback).toHaveBeenCalled(); + }); + + it("shouldn't submit the form without the ctrl key", function() { + $("#response-message").submit(this.submitCallback); + var e = $.Event("keydown", {which: Keycodes.ENTER, ctrlKey: false}); + $("#response-message-text").trigger(e); + expect(this.submitCallback).not.toHaveBeenCalled(); + }); + }); + }); + + describe("onSubmitNewConversation", function() { + beforeEach(function() { + spec.loadFixture("conversations_read"); + $("#conversation-new").removeClass("hidden"); + $("#conversation-show").addClass("hidden"); + spyOn(app.views.ConversationsForm.prototype, "onSubmitNewConversation").and.callThrough(); + this.target = new app.views.ConversationsForm(); + }); + + it("onSubmitNewConversation is called when submitting the conversation form", function() { + spyOn(app.views.ConversationsForm.prototype, "getConversationParticipants").and.returnValue([]); + $("#conversation-new").trigger("submit"); + + expect(app.views.ConversationsForm.prototype.onSubmitNewConversation).toHaveBeenCalled(); + }); + + it("does not submit a conversation with no recipient", function() { + spyOn(app.views.ConversationsForm.prototype, "getConversationParticipants").and.returnValue([]); + var event = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]); + + this.target.onSubmitNewConversation(event); + + expect(event.preventDefault).toHaveBeenCalled(); + expect(event.stopPropagation).toHaveBeenCalled(); + }); + + it("submits a conversation with recipients", function() { + spyOn(app.views.ConversationsForm.prototype, "getConversationParticipants").and.returnValue([1]); + var event = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]); + + this.target.onSubmitNewConversation(event); + + expect(event.preventDefault).toHaveBeenCalled(); + expect(event.stopPropagation).not.toHaveBeenCalled(); + }); + + it("flashes an error message when submitting a conversation with no recipient", function() { + spyOn(app.views.FlashMessages.prototype, "error"); + spyOn(app.views.ConversationsForm.prototype, "getConversationParticipants").and.returnValue([]); + var event = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]); + + this.target.onSubmitNewConversation(event); + + expect(app.views.FlashMessages.prototype.error) + .toHaveBeenCalledWith(Diaspora.I18n.t("conversation.create.no_recipient")); + }); + + it("does not flash an error message when submitting a conversation with recipients", function() { + spyOn(app.views.FlashMessages.prototype, "error"); + spyOn(app.views.ConversationsForm.prototype, "getConversationParticipants").and.returnValue([1]); + var event = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]); + + this.target.onSubmitNewConversation(event); + + expect(app.views.FlashMessages.prototype.error).not + .toHaveBeenCalledWith(Diaspora.I18n.t("conversation.create.no_recipient")); + }); + }); +}); diff --git a/spec/javascripts/app/views/conversations_inbox_view_spec.js b/spec/javascripts/app/views/conversations_inbox_view_spec.js new file mode 100644 index 0000000000..122c889c11 --- /dev/null +++ b/spec/javascripts/app/views/conversations_inbox_view_spec.js @@ -0,0 +1,164 @@ +describe("app.views.ConversationsInbox", function() { + describe("initialize", function() { + beforeEach(function() { + spec.loadFixture("conversations_read"); + $("#conversation-new").removeClass("hidden"); + $("#conversation-show").addClass("hidden"); + }); + + it("initializes the conversations form", function() { + spyOn(app.views.ConversationsForm.prototype, "initialize"); + new app.views.ConversationsInbox(); + expect(app.views.ConversationsForm.prototype.initialize).toHaveBeenCalled(); + }); + + it("call setupConversation", function() { + spyOn(app.views.ConversationsInbox.prototype, "setupConversation"); + new app.views.ConversationsInbox(); + expect(app.views.ConversationsInbox.prototype.setupConversation).toHaveBeenCalled(); + }); + }); + + describe("renderConversation", function() { + beforeEach(function() { + spec.loadFixture("conversations_read"); + $("#conversation-new").removeClass("hidden"); + $("#conversation-show").addClass("hidden"); + var conversations = $("#conversation-inbox .stream-element"); + conversations.removeClass("selected"); + this.conversationId = conversations.first().data("guid"); + this.target = new app.views.ConversationsInbox(); + }); + + it("renders conversation of given id", function() { + spyOn($, "ajax").and.callThrough(); + spyOn(app.views.ConversationsInbox.prototype, "selectConversation"); + spyOn(app.views.ConversationsInbox.prototype, "setupConversation"); + this.target.renderConversation(this.conversationId); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + responseText: "<div id='fake-conversation-content'></div>" + }); + + expect($.ajax).toHaveBeenCalled(); + expect(jasmine.Ajax.requests.mostRecent().url).toBe("/conversations/" + this.conversationId + "/raw"); + expect(app.views.ConversationsInbox.prototype.selectConversation).toHaveBeenCalledWith(this.conversationId); + expect(app.views.ConversationsInbox.prototype.setupConversation).toHaveBeenCalled(); + expect($("#conversation-new")).toHaveClass("hidden"); + expect($("#conversation-show")).not.toHaveClass("hidden"); + expect($("#conversation-show #fake-conversation-content").length).toBe(1); + }); + }); + + describe("selectConversation", function() { + beforeEach(function() { + spec.loadFixture("conversations_read"); + this.conversationId = $("#conversation-inbox .stream-element").first().data("guid"); + this.target = new app.views.ConversationsInbox(); + $("#conversation-inbox .stream-element").addClass("selected"); + }); + + it("unselects every conversation if called with no parameters", function() { + expect($("#conversation-inbox .stream-element.selected").length).not.toBe(0); + this.target.selectConversation(); + expect($("#conversation-inbox .stream-element.selected").length).toBe(0); + }); + + it("selects the given conversation", function() { + expect($("#conversation-inbox .stream-element.selected").length).not.toBe(1); + this.target.selectConversation(this.conversationId); + expect($("#conversation-inbox .stream-element.selected").length).toBe(1); + expect($("#conversation-inbox .stream-element.selected").data("guid")).toBe(this.conversationId); + }); + }); + + describe("displayNewConversation", function() { + beforeEach(function() { + spec.loadFixture("conversations_read"); + $("#conversation-new").addClass("hidden"); + $("#conversation-show").removeClass("hidden"); + spyOn(app.views.ConversationsInbox.prototype, "selectConversation"); + new app.views.ConversationsInbox(); + }); + + it("displays the new conversation panel", function() { + $(".new-conversation-btn").click(); + + expect(app.views.ConversationsInbox.prototype.selectConversation).toHaveBeenCalledWith(); + expect($("#conversation-new")).not.toHaveClass("hidden"); + expect($("#conversation-show")).toHaveClass("hidden"); + expect(window.location.pathname).toBe("/conversations"); + }); + }); + + describe("setupConversation", function() { + context("for unread conversations", function() { + beforeEach(function() { + spec.loadFixture("conversations_unread"); + // select second conversation that is still unread + $(".conversation-wrapper > .conversation.selected").removeClass("selected"); + $(".conversation-wrapper > .conversation.unread").addClass("selected"); + }); + + it("removes the unread class from the conversation", function() { + expect($(".conversation-wrapper > .conversation.selected")).toHaveClass("unread"); + new app.views.ConversationsInbox(); + expect($(".conversation-wrapper > .conversation.selected")).not.toHaveClass("unread"); + }); + + it("removes the unread message counter from the conversation", function() { + expect($(".conversation-wrapper > .conversation.selected .unread-message-count").length).toEqual(1); + new app.views.ConversationsInbox(); + expect($(".conversation-wrapper > .conversation.selected .unread-message-count").length).toEqual(0); + }); + + it("decreases the unread message count in the header", function() { + var badge = "<div id=\"conversations-link\"><div class=\"badge\">3</div></div>"; + $("header").append(badge); + expect($("#conversations-link .badge").text().trim()).toEqual("3"); + expect($(".conversation-wrapper > .conversation .unread-message-count").text().trim()).toEqual("1"); + new app.views.ConversationsInbox(); + expect($("#conversations-link .badge").text().trim()).toEqual("2"); + }); + + it("removes the badge in the header if there are no unread messages left", function() { + var badge = "<div id=\"conversations-link\"><div class=\"badge\">1</div></div>"; + $("header").append(badge); + expect($("#conversations-link .badge").text().trim()).toEqual("1"); + expect($(".conversation-wrapper > .conversation.selected .unread-message-count").text().trim()).toEqual("1"); + new app.views.ConversationsInbox(); + expect($("#conversations-link .badge").text().trim()).toEqual("0"); + expect($("#conversations-link .badge")).toHaveClass("hidden"); + }); + }); + + context("for read conversations", function() { + beforeEach(function() { + spec.loadFixture("conversations_read"); + }); + + it("does not change the badge in the header", function() { + var badge = "<div id=\"conversations-link\"><div class=\"badge\">3</div></div>"; + $("header").append(badge); + expect($("#conversations-link .badge").text().trim()).toEqual("3"); + new app.views.ConversationsInbox(); + expect($("#conversations-link .badge").text().trim()).toEqual("3"); + }); + }); + }); + + describe("displayConversation", function() { + beforeEach(function() { + spyOn(app.router, "navigate"); + spec.loadFixture("conversations_read"); + new app.views.ConversationsInbox(); + }); + + it("calls app.router.navigate with correct parameters", function() { + var conversationEl = $(".conversation-wrapper").first(); + var conversationPath = conversationEl.data("conversation-path"); + conversationEl.children().first().click(); + expect(app.router.navigate).toHaveBeenCalledWith(conversationPath, {trigger: true}); + }); + }); +}); diff --git a/spec/javascripts/app/views/conversations_view_spec.js b/spec/javascripts/app/views/conversations_view_spec.js deleted file mode 100644 index ae5d62e103..0000000000 --- a/spec/javascripts/app/views/conversations_view_spec.js +++ /dev/null @@ -1,79 +0,0 @@ -describe("app.views.Conversations", function(){ - describe("setupConversation", function() { - context("for unread conversations", function() { - beforeEach(function() { - spec.loadFixture("conversations_unread"); - // select second conversation that is still unread - $(".conversation-wrapper > .conversation.selected").removeClass("selected"); - $(".conversation-wrapper > .conversation.unread").addClass("selected"); - }); - - it("removes the unread class from the conversation", function() { - expect($(".conversation-wrapper > .conversation.selected")).toHaveClass("unread"); - new app.views.Conversations(); - expect($(".conversation-wrapper > .conversation.selected")).not.toHaveClass("unread"); - }); - - it("removes the unread message counter from the conversation", function() { - expect($(".conversation-wrapper > .conversation.selected .unread-message-count").length).toEqual(1); - new app.views.Conversations(); - expect($(".conversation-wrapper > .conversation.selected .unread-message-count").length).toEqual(0); - }); - - it("decreases the unread message count in the header", function() { - var badge = "<div id=\"conversations-link\"><div class=\"badge\">3</div></div>"; - $("header").append(badge); - expect($("#conversations-link .badge").text().trim()).toEqual("3"); - expect($(".conversation-wrapper > .conversation .unread-message-count").text().trim()).toEqual("1"); - new app.views.Conversations(); - expect($("#conversations-link .badge").text().trim()).toEqual("2"); - }); - - it("removes the badge in the header if there are no unread messages left", function() { - var badge = "<div id=\"conversations-link\"><div class=\"badge\">1</div></div>"; - $("header").append(badge); - expect($("#conversations-link .badge").text().trim()).toEqual("1"); - expect($(".conversation-wrapper > .conversation.selected .unread-message-count").text().trim()).toEqual("1"); - new app.views.Conversations(); - expect($("#conversations-link .badge").text().trim()).toEqual("0"); - expect($("#conversations-link .badge")).toHaveClass("hidden"); - }); - }); - - context("for read conversations", function() { - beforeEach(function() { - spec.loadFixture("conversations_read"); - }); - - it("does not change the badge in the header", function() { - var badge = "<div id=\"conversations-link\"><div class=\"badge\">3</div></div>"; - $("header").append(badge); - expect($("#conversations-link .badge").text().trim()).toEqual("3"); - new app.views.Conversations(); - expect($("#conversations-link .badge").text().trim()).toEqual("3"); - }); - }); - }); - - describe("keyDown", function(){ - beforeEach(function() { - this.submitCallback = jasmine.createSpy().and.returnValue(false); - spec.loadFixture("conversations_read"); - new app.views.Conversations(); - }); - - it("should submit the form with ctrl+enter", function(){ - $("form#new_message").submit(this.submitCallback); - var e = $.Event("keydown", { which: Keycodes.ENTER, ctrlKey: true }); - $("textarea#message_text").trigger(e); - expect(this.submitCallback).toHaveBeenCalled(); - }); - - it("shouldn't submit the form without the ctrl key", function(){ - $("form#new_message").submit(this.submitCallback); - var e = $.Event("keydown", { which: Keycodes.ENTER, ctrlKey: false }); - $("textarea#message_text").trigger(e); - expect(this.submitCallback).not.toHaveBeenCalled(); - }); - }); -}); -- GitLab