Use typeahead on conversations
This commit is contained in:
parent
5269a0d3c0
commit
f2fdaf1daf
27 changed files with 749 additions and 262 deletions
|
|
@ -79,6 +79,9 @@ app.pages.Contacts = Backbone.View.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
showMessageModal: function(){
|
showMessageModal: function(){
|
||||||
|
$("#conversationModal").on("modal:loaded", function() {
|
||||||
|
new app.views.ConversationsForm({prefill: gon.conversationPrefill});
|
||||||
|
});
|
||||||
app.helpers.showModal("#conversationModal");
|
app.helpers.showModal("#conversationModal");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,40 +5,83 @@ app.views.ConversationsForm = Backbone.View.extend({
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
"keydown .conversation-message-text": "keyDown",
|
"keydown .conversation-message-text": "keyDown",
|
||||||
|
"click .conversation-recipient-tag .remove": "removeRecipient"
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function(opts) {
|
initialize: function(opts) {
|
||||||
this.contacts = _.has(opts, "contacts") ? opts.contacts : null;
|
opts = opts || {};
|
||||||
this.prefill = [];
|
this.conversationRecipients = [];
|
||||||
if (_.has(opts, "prefillName") && _.has(opts, "prefillValue")) {
|
|
||||||
this.prefill = [{name: opts.prefillName, value: opts.prefillValue}];
|
this.typeaheadElement = this.$el.find("#contacts-search-input");
|
||||||
|
this.contactsIdsListInput = this.$el.find("#contact-ids");
|
||||||
|
this.tagListElement = this.$("#recipients-tag-list");
|
||||||
|
|
||||||
|
this.search = new app.views.SearchBase({
|
||||||
|
el: this.$el.find("#new-conversation"),
|
||||||
|
typeaheadInput: this.typeaheadElement,
|
||||||
|
customSearch: true,
|
||||||
|
autoselect: true,
|
||||||
|
remoteRoute: {url: "/contacts", extraParameters: "mutual=true"}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bindTypeaheadEvents();
|
||||||
|
|
||||||
|
this.tagListElement.empty();
|
||||||
|
if (opts.prefill) {
|
||||||
|
this.prefill(opts.prefill);
|
||||||
}
|
}
|
||||||
this.prepareAutocomplete(this.contacts);
|
|
||||||
this.$("form#new-conversation").on("ajax:success", this.conversationCreateSuccess);
|
this.$("form#new-conversation").on("ajax:success", this.conversationCreateSuccess);
|
||||||
this.$("form#new-conversation").on("ajax:error", this.conversationCreateError);
|
this.$("form#new-conversation").on("ajax:error", this.conversationCreateError);
|
||||||
},
|
},
|
||||||
|
|
||||||
prepareAutocomplete: function(data){
|
addRecipient: function(person) {
|
||||||
this.$("#contact-autocomplete").autoSuggest(data, {
|
this.conversationRecipients.push(person);
|
||||||
selectedItemProp: "name",
|
this.updateContactIdsListInput();
|
||||||
searchObjProps: "name",
|
/* eslint-disable camelcase */
|
||||||
asHtmlID: "contact_ids",
|
this.tagListElement.append(HandlebarsTemplates.conversation_recipient_tag_tpl(person));
|
||||||
retrieveLimit: 10,
|
/* eslint-enable camelcase */
|
||||||
minChars: 1,
|
|
||||||
keyDelay: 0,
|
|
||||||
startText: '',
|
|
||||||
emptyText: Diaspora.I18n.t("no_results"),
|
|
||||||
preFill: this.prefill
|
|
||||||
});
|
|
||||||
$("#contact_ids").attr("aria-labelledby", "toLabel").focus();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
keyDown : function(evt) {
|
prefill: function(handles) {
|
||||||
if(evt.which === Keycodes.ENTER && evt.ctrlKey) {
|
handles.forEach(this.addRecipient.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateContactIdsListInput: function() {
|
||||||
|
this.contactsIdsListInput.val(_(this.conversationRecipients).pluck("id").join(","));
|
||||||
|
this.search.ignoreDiasporaIds.length = 0;
|
||||||
|
this.conversationRecipients.forEach(this.search.ignorePersonForSuggestions.bind(this.search));
|
||||||
|
},
|
||||||
|
|
||||||
|
bindTypeaheadEvents: function() {
|
||||||
|
this.typeaheadElement.on("typeahead:select", function(evt, person) {
|
||||||
|
this.onSuggestionSelection(person);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuggestionSelection: function(person) {
|
||||||
|
this.addRecipient(person);
|
||||||
|
this.typeaheadElement.typeahead("val", "");
|
||||||
|
},
|
||||||
|
|
||||||
|
keyDown: function(evt) {
|
||||||
|
if (evt.which === Keycodes.ENTER && evt.ctrlKey) {
|
||||||
$(evt.target).parents("form").submit();
|
$(evt.target).parents("form").submit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeRecipient: function(evt) {
|
||||||
|
var $recipientTagEl = $(evt.target).parents(".conversation-recipient-tag");
|
||||||
|
var diasporaHandle = $recipientTagEl.data("diaspora-handle");
|
||||||
|
|
||||||
|
this.conversationRecipients = this.conversationRecipients.filter(function(person) {
|
||||||
|
return diasporaHandle.localeCompare(person.handle) !== 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateContactIdsListInput();
|
||||||
|
$recipientTagEl.remove();
|
||||||
|
},
|
||||||
|
|
||||||
conversationCreateSuccess: function(evt, data) {
|
conversationCreateSuccess: function(evt, data) {
|
||||||
app._changeLocation(Routes.conversation(data.id));
|
app._changeLocation(Routes.conversation(data.id));
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ app.views.ConversationsInbox = Backbone.View.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
new app.views.ConversationsForm({contacts: gon.contacts});
|
new app.views.ConversationsForm();
|
||||||
this.setupConversation();
|
this.setupConversation();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,11 @@ app.views.ProfileHeader = app.views.Base.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
showMessageModal: function(){
|
showMessageModal: function(){
|
||||||
|
$("#conversationModal").on("modal:loaded", function() {
|
||||||
|
new app.views.ConversationsForm({prefill: gon.conversationPrefill});
|
||||||
|
});
|
||||||
app.helpers.showModal("#conversationModal");
|
app.helpers.showModal("#conversationModal");
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
// @license-end
|
// @license-end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ app.views.PublisherMention = app.views.SearchBase.extend({
|
||||||
typeaheadInput: this.typeaheadInput,
|
typeaheadInput: this.typeaheadInput,
|
||||||
customSearch: true,
|
customSearch: true,
|
||||||
autoselect: true,
|
autoselect: true,
|
||||||
remoteRoute: "/contacts"
|
remoteRoute: {url: "/contacts"}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,13 @@ app.views.SearchBase = app.views.Base.extend({
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allow bloodhound to look for remote results if there is a route given in the options
|
// Allow bloodhound to look for remote results if there is a route given in the options
|
||||||
if(options.remoteRoute) {
|
if (options.remoteRoute && options.remoteRoute.url) {
|
||||||
|
var extraParameters = "";
|
||||||
|
if (options.remoteRoute.extraParameters) {
|
||||||
|
extraParameters += "&" + options.remoteRoute.extraParameters;
|
||||||
|
}
|
||||||
bloodhoundOptions.remote = {
|
bloodhoundOptions.remote = {
|
||||||
url: options.remoteRoute + ".json?q=%QUERY",
|
url: options.remoteRoute.url + ".json?q=%QUERY" + extraParameters,
|
||||||
wildcard: "%QUERY",
|
wildcard: "%QUERY",
|
||||||
transform: this.transformBloodhoundResponse.bind(this)
|
transform: this.transformBloodhoundResponse.bind(this)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ app.views.Search = app.views.SearchBase.extend({
|
||||||
this.searchInput = this.$("#q");
|
this.searchInput = this.$("#q");
|
||||||
app.views.SearchBase.prototype.initialize.call(this, {
|
app.views.SearchBase.prototype.initialize.call(this, {
|
||||||
typeaheadInput: this.searchInput,
|
typeaheadInput: this.searchInput,
|
||||||
remoteRoute: this.$el.attr("action"),
|
remoteRoute: {url: this.$el.attr("action")},
|
||||||
suggestionLink: true
|
suggestionLink: true
|
||||||
});
|
});
|
||||||
this.searchInput.on("typeahead:select", this.suggestionSelected);
|
this.searchInput.on("typeahead:select", this.suggestionSelected);
|
||||||
|
|
|
||||||
|
|
@ -183,10 +183,60 @@
|
||||||
}
|
}
|
||||||
// scss-lint:enable SelectorDepth
|
// scss-lint:enable SelectorDepth
|
||||||
|
|
||||||
#new_conversation_pane {
|
.new-conversation {
|
||||||
ul.as-selections { width: 100% !important; }
|
ul.as-selections { width: 100% !important; }
|
||||||
|
|
||||||
input#contact_ids { box-shadow: none; }
|
input#contact_ids { box-shadow: none; }
|
||||||
|
|
||||||
label { font-weight: bold; }
|
label { font-weight: bold; }
|
||||||
|
|
||||||
|
.twitter-typeahead,
|
||||||
|
.tt-menu {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipients-tag-list {
|
||||||
|
.conversation-recipient-tag {
|
||||||
|
background-color: $brand-primary;
|
||||||
|
border-radius: $btn-border-radius-base;
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 0 2px $form-group-margin-bottom;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&:first-child { margin-left: 0; }
|
||||||
|
|
||||||
|
&:last-child { margin-right: 0; }
|
||||||
|
|
||||||
|
div {
|
||||||
|
align-self: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
height: 40px;
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-and-handle {
|
||||||
|
color: $white;
|
||||||
|
margin-right: 8px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.diaspora-id { font-size: $font-size-small; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.entypo-circled-cross {
|
||||||
|
color: $white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
|
||||||
|
&:hover { color: $light-grey; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-conversation.form-horizontal .form-group:last-of-type { margin-bottom: 0; }
|
.new-conversation.form-horizontal .form-group:last-of-type { margin-bottom: 0; }
|
||||||
|
|
|
||||||
|
|
@ -61,3 +61,5 @@
|
||||||
.subject { padding: 0 10px; }
|
.subject { padding: 0 10px; }
|
||||||
|
|
||||||
.message-count, .unread-message-count { margin: 10px 2px; }
|
.message-count, .unread-message-count { margin: 10px 2px; }
|
||||||
|
|
||||||
|
.new-conversation .as-selections { background-color: transparent; }
|
||||||
|
|
|
||||||
12
app/assets/templates/conversation_recipient_tag_tpl.jst.hbs
Normal file
12
app/assets/templates/conversation_recipient_tag_tpl.jst.hbs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div class="conversation-recipient-tag clearfix" data-diaspora-handle="{{ handle }}">
|
||||||
|
<div href="{{ url }}">
|
||||||
|
<img src="{{ avatar }}" class="avatar img-responsive center-block">
|
||||||
|
</div>
|
||||||
|
<div class="pull-left clearfix name-and-handle" href="{{ url }}">
|
||||||
|
<div class="name">{{ name }}</div>
|
||||||
|
<div class="diaspora-id">{{ handle }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="remove pull-right clearfix">
|
||||||
|
<i class="entypo-circled-cross"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -17,7 +17,8 @@ class ContactsController < ApplicationController
|
||||||
# Used for mentions in the publisher and pagination on the contacts page
|
# Used for mentions in the publisher and pagination on the contacts page
|
||||||
format.json {
|
format.json {
|
||||||
@people = if params[:q].present?
|
@people = if params[:q].present?
|
||||||
Person.search(params[:q], current_user, only_contacts: true).limit(15)
|
mutual = params[:mutual].present? && params[:mutual]
|
||||||
|
Person.search(params[:q], current_user, only_contacts: true, mutual: mutual).limit(15)
|
||||||
else
|
else
|
||||||
set_up_contacts_json
|
set_up_contacts_json
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,12 @@ class ConversationsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
contact_ids = params[:contact_ids]
|
# Contacts autocomplete does not work the same way on mobile and desktop
|
||||||
|
# Mobile returns contact ids array while desktop returns person id
|
||||||
# Can't split nil
|
# This will have to be removed when mobile autocomplete is ported to Typeahead
|
||||||
if contact_ids
|
recipients_param, column = [%i(contact_ids id), %i(person_ids person_id)].find {|param, _| params[param].present? }
|
||||||
contact_ids = contact_ids.split(',') if contact_ids.is_a? String
|
if recipients_param
|
||||||
person_ids = current_user.contacts.where(id: contact_ids).pluck(:person_id)
|
person_ids = current_user.contacts.where(column => params[recipients_param].split(",")).pluck(:person_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
opts = params.require(:conversation).permit(:subject)
|
opts = params.require(:conversation).permit(:subject)
|
||||||
|
|
@ -91,17 +91,23 @@ class ConversationsController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@contacts_json = contacts_data.to_json
|
|
||||||
@contact_ids = ""
|
|
||||||
|
|
||||||
if params[:contact_id]
|
|
||||||
@contact_ids = current_user.contacts.find(params[:contact_id]).id
|
|
||||||
elsif params[:aspect_id]
|
|
||||||
@contact_ids = current_user.aspects.find(params[:aspect_id]).contacts.map{|c| c.id}.join(',')
|
|
||||||
end
|
|
||||||
if session[:mobile_view] == true && request.format.html?
|
if session[:mobile_view] == true && request.format.html?
|
||||||
|
@contacts_json = contacts_data.to_json
|
||||||
|
|
||||||
|
@contact_ids = if params[:contact_id]
|
||||||
|
current_user.contacts.find(params[:contact_id]).id
|
||||||
|
elsif params[:aspect_id]
|
||||||
|
current_user.aspects.find(params[:aspect_id]).contacts.pluck(:id).join(",")
|
||||||
|
end
|
||||||
|
|
||||||
render :layout => true
|
render :layout => true
|
||||||
else
|
else
|
||||||
|
if params[:contact_id]
|
||||||
|
gon.push conversation_prefill: [current_user.contacts.find(params[:contact_id]).person.as_json]
|
||||||
|
elsif params[:aspect_id]
|
||||||
|
gon.push conversation_prefill: current_user.aspects
|
||||||
|
.find(params[:aspect_id]).contacts.map {|c| c.person.as_json }
|
||||||
|
end
|
||||||
render :layout => false
|
render :layout => false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ class Person < ActiveRecord::Base
|
||||||
[where_clause, q_tokens]
|
[where_clause, q_tokens]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.search(search_str, user, only_contacts: false)
|
def self.search(search_str, user, only_contacts: false, mutual: false)
|
||||||
search_str.strip!
|
search_str.strip!
|
||||||
return none if search_str.blank? || search_str.size < 2
|
return none if search_str.blank? || search_str.size < 2
|
||||||
|
|
||||||
|
|
@ -159,6 +159,8 @@ class Person < ActiveRecord::Base
|
||||||
).searchable(user)
|
).searchable(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
query = query.where(contacts: {sharing: true, receiving: true}) if mutual
|
||||||
|
|
||||||
query.where(closed_account: false)
|
query.where(closed_account: false)
|
||||||
.where(sql, *tokens)
|
.where(sql, *tokens)
|
||||||
.includes(:profile)
|
.includes(:profile)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
.spinner
|
.spinner
|
||||||
|
|
||||||
-if @aspect
|
-if @aspect
|
||||||
#new_conversation_pane
|
.conversations-form-container#new_conversation_pane
|
||||||
= render 'shared/modal',
|
= render 'shared/modal',
|
||||||
:path => new_conversation_path(:aspect_id => @aspect.id, :name => @aspect.name, :modal => true),
|
:path => new_conversation_path(:aspect_id => @aspect.id, :name => @aspect.name, :modal => true),
|
||||||
:title => t('conversations.index.new_conversation'),
|
:title => t('conversations.index.new_conversation'),
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,13 @@
|
||||||
= form_for Conversation.new, html: {id: "new-conversation",
|
= form_for Conversation.new, html: {id: "new-conversation",
|
||||||
class: "new-conversation form-horizontal"}, remote: true do |conversation|
|
class: "new-conversation form-horizontal"}, remote: true do |conversation|
|
||||||
.form-group
|
.form-group
|
||||||
%label#toLabel{for: "contact_ids"}
|
%label#to-label{for: "contacts-search-input"}= t(".to")
|
||||||
= t(".to")
|
.recipients-tag-list.clearfix#recipients-tag-list
|
||||||
= text_field_tag "contact_autocomplete", nil, id: "contact-autocomplete", class: "form-control"
|
= text_field_tag "contact_autocomplete", nil, id: "contacts-search-input", class: "form-control"
|
||||||
|
- unless defined?(mobile) && mobile
|
||||||
|
= text_field_tag "person_ids", nil, id: "contact-ids", type: "hidden",
|
||||||
|
aria: {labelledby: "to-label"}
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
%label#subject-label{for: "conversation-subject"}
|
%label#subject-label{for: "conversation-subject"}
|
||||||
= t(".subject")
|
= t(".subject")
|
||||||
|
|
@ -14,12 +18,14 @@
|
||||||
aria: {labelledby: "subject-label"},
|
aria: {labelledby: "subject-label"},
|
||||||
value: "",
|
value: "",
|
||||||
placeholder: t("conversations.new.subject_default")
|
placeholder: t("conversations.new.subject_default")
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
%label.sr-only#message-label{for: "new-message-text"} = t(".message")
|
%label.sr-only#message-label{for: "new-message-text"}= t(".message")
|
||||||
= text_area_tag "conversation[text]", "",
|
= text_area_tag "conversation[text]", "",
|
||||||
rows: 5,
|
rows: 5,
|
||||||
id: "new-message-text",
|
id: "new-message-text",
|
||||||
class: "conversation-message-text input-block-level form-control",
|
class: "conversation-message-text input-block-level form-control",
|
||||||
aria: {labelledby: "message-label"}
|
aria: {labelledby: "message-label"}
|
||||||
|
|
||||||
.form-group
|
.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"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,2 @@
|
||||||
:javascript
|
= include_gon camel_case: true
|
||||||
$(document).ready(function () {
|
|
||||||
var data = $.parseJSON( "#{escape_javascript(@contacts_json)}" );
|
|
||||||
new app.views.ConversationsForm({
|
|
||||||
el: $("form#new-conversation").parent(),
|
|
||||||
contacts: data,
|
|
||||||
prefillName: "#{h params[:name]}",
|
|
||||||
prefillValue: "#{@contact_ids}"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
= render 'conversations/new'
|
= render 'conversations/new'
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
:plain
|
:plain
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
var data = $.parseJSON( "#{escape_javascript(@contacts_json).html_safe}" ),
|
var data = $.parseJSON( "#{escape_javascript(@contacts_json).html_safe}" ),
|
||||||
autocompleteInput = $("#contact-autocomplete");
|
autocompleteInput = $("#contacts-search-input");
|
||||||
|
|
||||||
autocompleteInput.autoSuggest(data, {
|
autocompleteInput.autoSuggest(data, {
|
||||||
selectedItemProp: "name",
|
selectedItemProp: "name",
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
retrieveLimit: 10,
|
retrieveLimit: 10,
|
||||||
minChars: 1,
|
minChars: 1,
|
||||||
keyDelay: 0,
|
keyDelay: 0,
|
||||||
startText: '',
|
startText: "",
|
||||||
emptyText: "#{t("no_results")}",
|
emptyText: "#{t("no_results")}",
|
||||||
preFill: [{name : "#{h params[:name]}",
|
preFill: [{name : "#{h params[:name]}",
|
||||||
value : "#{@contact_ids}"}]
|
value : "#{@contact_ids}"}]
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
#flash-messages
|
#flash-messages
|
||||||
.container-fluid.row
|
.container-fluid.row
|
||||||
%h3
|
%h3
|
||||||
= t('conversations.index.new_conversation')
|
= t("conversations.index.new_conversation")
|
||||||
|
|
||||||
= render 'conversations/new'
|
= render "conversations/new", mobile: true
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
id: 'mentionModal'
|
id: 'mentionModal'
|
||||||
|
|
||||||
-if @contact
|
-if @contact
|
||||||
#new_conversation_pane
|
.conversations-form-container#new_conversation_pane
|
||||||
= render 'shared/modal',
|
= render 'shared/modal',
|
||||||
path: new_conversation_path(:contact_id => @contact.id, name: @contact.person.name, modal: true),
|
path: new_conversation_path(:contact_id => @contact.id, name: @contact.person.name, modal: true),
|
||||||
title: t('conversations.index.new_conversation'),
|
title: t('conversations.index.new_conversation'),
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ end
|
||||||
Then /^I send a message with subject "([^"]*)" and text "([^"]*)" to "([^"]*)"$/ do |subject, text, person|
|
Then /^I send a message with subject "([^"]*)" and text "([^"]*)" to "([^"]*)"$/ do |subject, text, person|
|
||||||
step %(I am on the conversations page)
|
step %(I am on the conversations page)
|
||||||
within("#new-conversation", match: :first) do
|
within("#new-conversation", match: :first) do
|
||||||
step %(I fill in "contact_autocomplete" with "#{person}")
|
find("#contacts-search-input").native.send_key(person.to_s)
|
||||||
step %(I press the first ".as-result-item" within ".as-results")
|
step %(I press the first ".tt-suggestion" within ".twitter-typeahead")
|
||||||
step %(I fill in "conversation-subject" with "#{subject}")
|
step %(I fill in "conversation-subject" with "#{subject}")
|
||||||
step %(I fill in "new-message-text" with "#{text}")
|
step %(I fill in "new-message-text" with "#{text}")
|
||||||
step %(I press "Send")
|
step %(I press "Send")
|
||||||
|
|
@ -26,8 +26,8 @@ end
|
||||||
Then /^I send a message with subject "([^"]*)" and text "([^"]*)" to "([^"]*)" using keyboard shortcuts$/ do |subject, text, person|
|
Then /^I send a message with subject "([^"]*)" and text "([^"]*)" to "([^"]*)" using keyboard shortcuts$/ do |subject, text, person|
|
||||||
step %(I am on the conversations page)
|
step %(I am on the conversations page)
|
||||||
within("#new-conversation", match: :first) do
|
within("#new-conversation", match: :first) do
|
||||||
step %(I fill in "contact_autocomplete" with "#{person}")
|
find("#contacts-search-input").native.send_key(person.to_s)
|
||||||
step %(I press the first ".as-result-item" within ".as-results")
|
step %(I press the first ".tt-suggestion" within ".twitter-typeahead")
|
||||||
step %(I fill in "conversation-subject" with "#{subject}")
|
step %(I fill in "conversation-subject" with "#{subject}")
|
||||||
step %(I fill in "new-message-text" with "#{text}")
|
step %(I fill in "new-message-text" with "#{text}")
|
||||||
find("#new-message-text").native.send_key %i(Ctrl Return)
|
find("#new-message-text").native.send_key %i(Ctrl Return)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ describe ContactsController, :type => :controller do
|
||||||
@person1 = FactoryGirl.create(:person)
|
@person1 = FactoryGirl.create(:person)
|
||||||
bob.share_with(@person1, bob.aspects.first)
|
bob.share_with(@person1, bob.aspects.first)
|
||||||
@person2 = FactoryGirl.create(:person)
|
@person2 = FactoryGirl.create(:person)
|
||||||
|
@person3 = FactoryGirl.create(:person)
|
||||||
|
bob.contacts.create(person: @person3, aspects: [bob.aspects.first], receiving: true, sharing: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "succeeds" do
|
it "succeeds" do
|
||||||
|
|
@ -53,6 +55,15 @@ describe ContactsController, :type => :controller do
|
||||||
get :index, q: @person2.first_name, format: "json"
|
get :index, q: @person2.first_name, format: "json"
|
||||||
expect(response.body).to eq([].to_json)
|
expect(response.body).to eq([].to_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "only returns mutual contacts when mutual parameter is true" do
|
||||||
|
get :index, q: @person1.first_name, mutual: true, format: "json"
|
||||||
|
expect(response.body).to eq([].to_json)
|
||||||
|
get :index, q: @person2.first_name, mutual: true, format: "json"
|
||||||
|
expect(response.body).to eq([].to_json)
|
||||||
|
get :index, q: @person3.first_name, mutual: true, format: "json"
|
||||||
|
expect(response.body).to eq([@person3].to_json)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "for pagination on the contacts page" do
|
context "for pagination on the contacts page" do
|
||||||
|
|
|
||||||
|
|
@ -16,48 +16,57 @@ describe ConversationsController, :type => :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#new modal' do
|
describe "#new modal" do
|
||||||
it 'succeeds' do
|
context "desktop and mobile" do
|
||||||
get :new, :modal => true
|
it "succeeds" do
|
||||||
expect(response).to be_success
|
get :new, modal: true
|
||||||
end
|
expect(response).to be_success
|
||||||
|
end
|
||||||
|
|
||||||
it "assigns a json list of contacts that are sharing with the person" do
|
it "assigns a contact if passed a contact id" do
|
||||||
sharing_user = FactoryGirl.create(:user_with_aspect)
|
get :new, contact_id: alice.contacts.first.id, modal: true
|
||||||
sharing_user.share_with(alice.person, sharing_user.aspects.first)
|
expect(controller.gon.conversation_prefill).to eq([alice.contacts.first.person.as_json])
|
||||||
get :new, :modal => true
|
end
|
||||||
expect(assigns(:contacts_json)).to include(alice.contacts.where(sharing: true, receiving: true).first.person.name)
|
|
||||||
alice.contacts << Contact.new(:person_id => eve.person.id, :user_id => alice.id, :sharing => false, :receiving => true)
|
|
||||||
expect(assigns(:contacts_json)).not_to include(alice.contacts.where(sharing: false).first.person.name)
|
|
||||||
expect(assigns(:contacts_json)).not_to include(alice.contacts.where(receiving: false).first.person.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "assigns a contact if passed a contact id" do
|
it "assigns a set of contacts if passed an aspect id" do
|
||||||
get :new, :contact_id => alice.contacts.first.id, :modal => true
|
get :new, aspect_id: alice.aspects.first.id, modal: true
|
||||||
expect(assigns(:contact_ids)).to eq(alice.contacts.first.id)
|
expect(controller.gon.conversation_prefill).to eq(alice.aspects.first.contacts.map {|c| c.person.as_json })
|
||||||
end
|
end
|
||||||
|
|
||||||
it "assigns a set of contacts if passed an aspect id" do
|
it "does not allow XSS via the name parameter" do
|
||||||
get :new, :aspect_id => alice.aspects.first.id, :modal => true
|
["</script><script>alert(1);</script>",
|
||||||
expect(assigns(:contact_ids)).to eq(alice.aspects.first.contacts.map(&:id).join(','))
|
'"}]});alert(1);(function f() {var foo = [{b:"'].each do |xss|
|
||||||
end
|
get :new, modal: true, name: xss
|
||||||
|
expect(response.body).not_to include xss
|
||||||
it "does not allow XSS via the name parameter" do
|
end
|
||||||
["</script><script>alert(1);</script>",
|
|
||||||
'"}]});alert(1);(function f() {var foo = [{b:"'].each do |xss|
|
|
||||||
get :new, :modal => true, name: xss
|
|
||||||
expect(response.body).not_to include xss
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not allow XSS via the profile name" do
|
context "mobile" do
|
||||||
xss = "<script>alert(0);</script>"
|
before do
|
||||||
contact = alice.contacts.first
|
controller.session[:mobile_view] = true
|
||||||
contact.person.profile.update_attribute(:first_name, xss)
|
end
|
||||||
get :new, :modal => true
|
|
||||||
json = JSON.parse(assigns(:contacts_json)).first
|
it "assigns a json list of contacts that are sharing with the person" do
|
||||||
expect(json['value'].to_s).to eq(contact.id.to_s)
|
sharing_user = FactoryGirl.create(:user_with_aspect)
|
||||||
expect(json['name']).to_not include(xss)
|
sharing_user.share_with(alice.person, sharing_user.aspects.first)
|
||||||
|
get :new, modal: true
|
||||||
|
expect(assigns(:contacts_json))
|
||||||
|
.to include(alice.contacts.where(sharing: true, receiving: true).first.person.name)
|
||||||
|
alice.contacts << Contact.new(person_id: eve.person.id, user_id: alice.id, sharing: false, receiving: true)
|
||||||
|
expect(assigns(:contacts_json)).not_to include(alice.contacts.where(sharing: false).first.person.name)
|
||||||
|
expect(assigns(:contacts_json)).not_to include(alice.contacts.where(receiving: false).first.person.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not allow XSS via the profile name" do
|
||||||
|
xss = "<script>alert(0);</script>"
|
||||||
|
contact = alice.contacts.first
|
||||||
|
contact.person.profile.update_attribute(:first_name, xss)
|
||||||
|
get :new, modal: true
|
||||||
|
json = JSON.parse(assigns(:contacts_json)).first
|
||||||
|
expect(json["value"].to_s).to eq(contact.id.to_s)
|
||||||
|
expect(json["name"]).to_not include(xss)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -115,203 +124,323 @@ describe ConversationsController, :type => :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#create' do
|
describe "#create" do
|
||||||
context 'with a valid conversation' do
|
context "desktop" do
|
||||||
before do
|
context "with a valid conversation" do
|
||||||
@hash = {
|
before do
|
||||||
:format => :js,
|
@hash = {
|
||||||
:conversation => {
|
format: :js,
|
||||||
:subject => "secret stuff",
|
conversation: {subject: "secret stuff", text: "text debug"},
|
||||||
:text => 'text debug'
|
person_ids: [alice.contacts.first.person.id]
|
||||||
},
|
|
||||||
:contact_ids => [alice.contacts.first.id]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a conversation' do
|
|
||||||
expect {
|
|
||||||
post :create, @hash
|
|
||||||
}.to change(Conversation, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a message' do
|
|
||||||
expect {
|
|
||||||
post :create, @hash
|
|
||||||
}.to change(Message, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "responds with the conversation id as JSON" do
|
|
||||||
post :create, @hash
|
|
||||||
expect(response).to be_success
|
|
||||||
expect(JSON.parse(response.body)["id"]).to eq(Conversation.first.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sets the author to the current_user' do
|
|
||||||
@hash[:author] = FactoryGirl.create(:user)
|
|
||||||
post :create, @hash
|
|
||||||
expect(Message.first.author).to eq(alice.person)
|
|
||||||
expect(Conversation.first.author).to eq(alice.person)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'dispatches the conversation' do
|
|
||||||
cnv = Conversation.create(
|
|
||||||
{
|
|
||||||
:author => alice.person,
|
|
||||||
:participant_ids => [alice.contacts.first.person.id, alice.person.id],
|
|
||||||
:subject => 'not spam',
|
|
||||||
:messages_attributes => [ {:author => alice.person, :text => 'cool stuff'} ]
|
|
||||||
}
|
}
|
||||||
)
|
end
|
||||||
|
|
||||||
expect(Diaspora::Federation::Dispatcher).to receive(:defer_dispatch)
|
it "creates a conversation" do
|
||||||
post :create, @hash
|
expect { post :create, @hash }.to change(Conversation, :count).by(1)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context 'with empty subject' do
|
it "creates a message" do
|
||||||
before do
|
expect { post :create, @hash }.to change(Message, :count).by(1)
|
||||||
@hash = {
|
end
|
||||||
:format => :js,
|
|
||||||
:conversation => {
|
|
||||||
:subject => ' ',
|
|
||||||
:text => 'text debug'
|
|
||||||
},
|
|
||||||
:contact_ids => [alice.contacts.first.id]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a conversation' do
|
it "responds with the conversation id as JSON" do
|
||||||
expect {
|
|
||||||
post :create, @hash
|
post :create, @hash
|
||||||
}.to change(Conversation, :count).by(1)
|
expect(response).to be_success
|
||||||
end
|
expect(JSON.parse(response.body)["id"]).to eq(Conversation.first.id)
|
||||||
|
end
|
||||||
|
|
||||||
it 'creates a message' do
|
it "sets the author to the current_user" do
|
||||||
expect {
|
@hash[:author] = FactoryGirl.create(:user)
|
||||||
post :create, @hash
|
post :create, @hash
|
||||||
}.to change(Message, :count).by(1)
|
expect(Message.first.author).to eq(alice.person)
|
||||||
|
expect(Conversation.first.author).to eq(alice.person)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dispatches the conversation" do
|
||||||
|
Conversation.create(author: alice.person, participant_ids: [alice.contacts.first.person.id, alice.person.id],
|
||||||
|
subject: "not spam", messages_attributes: [{author: alice.person, text: "cool stuff"}])
|
||||||
|
|
||||||
|
expect(Diaspora::Federation::Dispatcher).to receive(:defer_dispatch)
|
||||||
|
post :create, @hash
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "responds with the conversation id as JSON" do
|
context "with empty subject" do
|
||||||
post :create, @hash
|
before do
|
||||||
expect(response).to be_success
|
@hash = {
|
||||||
expect(JSON.parse(response.body)["id"]).to eq(Conversation.first.id)
|
format: :js,
|
||||||
|
conversation: {subject: " ", text: "text debug"},
|
||||||
|
person_ids: [alice.contacts.first.person.id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a conversation" do
|
||||||
|
expect { post :create, @hash }.to change(Conversation, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a message" do
|
||||||
|
expect { post :create, @hash }.to change(Message, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with the conversation id as JSON" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(JSON.parse(response.body)["id"]).to eq(Conversation.first.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with empty text" do
|
||||||
|
before do
|
||||||
|
@hash = {
|
||||||
|
format: :js,
|
||||||
|
conversation: {subject: "secret stuff", text: " "},
|
||||||
|
person_ids: [alice.contacts.first.person.id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a conversation" do
|
||||||
|
count = Conversation.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Conversation.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a message" do
|
||||||
|
count = Message.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Message.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with an error message" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).not_to be_success
|
||||||
|
expect(response.body).to eq(I18n.t("conversations.create.fail"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with empty contact" do
|
||||||
|
before do
|
||||||
|
@hash = {
|
||||||
|
format: :js,
|
||||||
|
conversation: {subject: "secret stuff", text: "text debug"},
|
||||||
|
person_ids: " "
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a conversation" do
|
||||||
|
count = Conversation.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Conversation.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a message" do
|
||||||
|
count = Message.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Message.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with an error message" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).not_to be_success
|
||||||
|
expect(response.body).to eq(I18n.t("javascripts.conversation.create.no_recipient"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with nil contact" do
|
||||||
|
before do
|
||||||
|
@hash = {
|
||||||
|
format: :js,
|
||||||
|
conversation: {subject: "secret stuff", text: "text debug"},
|
||||||
|
person_ids: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a conversation" do
|
||||||
|
count = Conversation.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Conversation.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a message" do
|
||||||
|
count = Message.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Message.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with an error message" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).not_to be_success
|
||||||
|
expect(response.body).to eq(I18n.t("javascripts.conversation.create.no_recipient"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with empty text' do
|
context "mobile" do
|
||||||
before do
|
before do
|
||||||
@hash = {
|
controller.session[:mobile_view] = true
|
||||||
:format => :js,
|
|
||||||
:conversation => {
|
|
||||||
:subject => 'secret stuff',
|
|
||||||
:text => ' '
|
|
||||||
},
|
|
||||||
:contact_ids => [alice.contacts.first.id]
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create a conversation' do
|
context "with a valid conversation" do
|
||||||
count = Conversation.count
|
before do
|
||||||
post :create, @hash
|
@hash = {
|
||||||
expect(Conversation.count).to eq(count)
|
format: :js,
|
||||||
|
conversation: {subject: "secret stuff", text: "text debug"},
|
||||||
|
contact_ids: [alice.contacts.first.id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a conversation" do
|
||||||
|
expect { post :create, @hash }.to change(Conversation, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a message" do
|
||||||
|
expect { post :create, @hash }.to change(Message, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with the conversation id as JSON" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(JSON.parse(response.body)["id"]).to eq(Conversation.first.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets the author to the current_user" do
|
||||||
|
@hash[:author] = FactoryGirl.create(:user)
|
||||||
|
post :create, @hash
|
||||||
|
expect(Message.first.author).to eq(alice.person)
|
||||||
|
expect(Conversation.first.author).to eq(alice.person)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dispatches the conversation" do
|
||||||
|
Conversation.create(author: alice.person, participant_ids: [alice.contacts.first.person.id, alice.person.id],
|
||||||
|
subject: "not spam", messages_attributes: [{author: alice.person, text: "cool stuff"}])
|
||||||
|
|
||||||
|
expect(Diaspora::Federation::Dispatcher).to receive(:defer_dispatch)
|
||||||
|
post :create, @hash
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create a message' do
|
context "with empty subject" do
|
||||||
count = Message.count
|
before do
|
||||||
post :create, @hash
|
@hash = {
|
||||||
expect(Message.count).to eq(count)
|
format: :js,
|
||||||
|
conversation: {subject: " ", text: "text debug"},
|
||||||
|
contact_ids: [alice.contacts.first.id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a conversation" do
|
||||||
|
expect { post :create, @hash }.to change(Conversation, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a message" do
|
||||||
|
expect { post :create, @hash }.to change(Message, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with the conversation id as JSON" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(JSON.parse(response.body)["id"]).to eq(Conversation.first.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "responds with an error message" do
|
context "with empty text" do
|
||||||
post :create, @hash
|
before do
|
||||||
expect(response).not_to be_success
|
@hash = {
|
||||||
expect(response.body).to eq(I18n.t("conversations.create.fail"))
|
format: :js,
|
||||||
end
|
conversation: {subject: "secret stuff", text: " "},
|
||||||
end
|
contact_ids: [alice.contacts.first.id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
context 'with empty contact' do
|
it "does not create a conversation" do
|
||||||
before do
|
count = Conversation.count
|
||||||
@hash = {
|
post :create, @hash
|
||||||
:format => :js,
|
expect(Conversation.count).to eq(count)
|
||||||
:conversation => {
|
end
|
||||||
:subject => 'secret stuff',
|
|
||||||
:text => 'text debug'
|
it "does not create a message" do
|
||||||
},
|
count = Message.count
|
||||||
:contact_ids => ' '
|
post :create, @hash
|
||||||
}
|
expect(Message.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with an error message" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).not_to be_success
|
||||||
|
expect(response.body).to eq(I18n.t("conversations.create.fail"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create a conversation' do
|
context "with empty contact" do
|
||||||
count = Conversation.count
|
before do
|
||||||
post :create, @hash
|
@hash = {
|
||||||
expect(Conversation.count).to eq(count)
|
format: :js,
|
||||||
|
conversation: {subject: "secret stuff", text: "text debug"},
|
||||||
|
contact_ids: " "
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a conversation" do
|
||||||
|
count = Conversation.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Conversation.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not create a message" do
|
||||||
|
count = Message.count
|
||||||
|
post :create, @hash
|
||||||
|
expect(Message.count).to eq(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with an error message" do
|
||||||
|
post :create, @hash
|
||||||
|
expect(response).not_to be_success
|
||||||
|
expect(response.body).to eq(I18n.t("javascripts.conversation.create.no_recipient"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create a message' do
|
context "with nil contact" do
|
||||||
count = Message.count
|
before do
|
||||||
post :create, @hash
|
@hash = {
|
||||||
expect(Message.count).to eq(count)
|
format: :js,
|
||||||
end
|
conversation: {subject: "secret stuff", text: "text debug"},
|
||||||
|
contact_ids: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
it "responds with an error message" do
|
it "does not create a conversation" do
|
||||||
post :create, @hash
|
count = Conversation.count
|
||||||
expect(response).not_to be_success
|
post :create, @hash
|
||||||
expect(response.body).to eq(I18n.t("javascripts.conversation.create.no_recipient"))
|
expect(Conversation.count).to eq(count)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context 'with nil contact' do
|
it "does not create a message" do
|
||||||
before do
|
count = Message.count
|
||||||
@hash = {
|
post :create, @hash
|
||||||
:format => :js,
|
expect(Message.count).to eq(count)
|
||||||
:conversation => {
|
end
|
||||||
:subject => 'secret stuff',
|
|
||||||
:text => 'text debug'
|
|
||||||
},
|
|
||||||
:contact_ids => nil
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not create a conversation' do
|
|
||||||
count = Conversation.count
|
|
||||||
post :create, @hash
|
|
||||||
expect(Conversation.count).to eq(count)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not create a message' do
|
|
||||||
count = Message.count
|
|
||||||
post :create, @hash
|
|
||||||
expect(Message.count).to eq(count)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "responds with an error message" do
|
|
||||||
post :create, @hash
|
|
||||||
expect(response).not_to be_success
|
|
||||||
expect(response.body).to eq(I18n.t("javascripts.conversation.create.no_recipient"))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#show' do
|
describe "#show" do
|
||||||
before do
|
before do
|
||||||
hash = {
|
hash = {
|
||||||
:author => alice.person,
|
author: alice.person,
|
||||||
:participant_ids => [alice.contacts.first.person.id, alice.person.id],
|
participant_ids: [alice.contacts.first.person.id, alice.person.id],
|
||||||
:subject => 'not spam',
|
subject: "not spam",
|
||||||
:messages_attributes => [ {:author => alice.person, :text => 'cool stuff'} ]
|
messages_attributes: [{author: alice.person, text: "cool stuff"}]
|
||||||
}
|
}
|
||||||
@conversation = Conversation.create(hash)
|
@conversation = Conversation.create(hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'succeeds with json' do
|
it "succeeds with json" do
|
||||||
get :show, :id => @conversation.id, :format => :json
|
get :show, :id => @conversation.id, :format => :json
|
||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
expect(assigns[:conversation]).to eq(@conversation)
|
expect(assigns[:conversation]).to eq(@conversation)
|
||||||
expect(response.body).to include @conversation.guid
|
expect(response.body).to include @conversation.guid
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'redirects to index' do
|
it "redirects to index" do
|
||||||
get :show, :id => @conversation.id
|
get :show, :id => @conversation.id
|
||||||
expect(response).to redirect_to(conversations_path(:conversation_id => @conversation.id))
|
expect(response).to redirect_to(conversations_path(:conversation_id => @conversation.id))
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ describe ConversationsController, :type => :controller do
|
||||||
|
|
||||||
get :index, :conversation_id => @conv1.id
|
get :index, :conversation_id => @conv1.id
|
||||||
save_fixture(html_for("body"), "conversations_read")
|
save_fixture(html_for("body"), "conversations_read")
|
||||||
|
|
||||||
|
get :new, modal: true
|
||||||
|
save_fixture(response.body, "conversations_modal")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -277,4 +277,31 @@ describe("app.pages.Contacts", function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("showMessageModal", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
$("body").append("<div id='conversationModal'/>").append(spec.readFixture("conversations_modal"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls app.helpers.showModal", function() {
|
||||||
|
spyOn(app.helpers, "showModal");
|
||||||
|
this.view.showMessageModal();
|
||||||
|
expect(app.helpers.showModal);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("app.views.ConversationsForm with correct parameters when modal is loaded", function() {
|
||||||
|
gon.conversationPrefill = [
|
||||||
|
{id: 1, name: "diaspora user", handle: "diaspora-user@pod.tld"},
|
||||||
|
{id: 2, name: "other diaspora user", handle: "other-diaspora-user@pod.tld"},
|
||||||
|
{id: 3, name: "user@pod.tld", handle: "user@pod.tld"}
|
||||||
|
];
|
||||||
|
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "initialize");
|
||||||
|
this.view.showMessageModal();
|
||||||
|
$("#conversationModal").trigger("modal:loaded");
|
||||||
|
expect($("#conversationModal").length).toBe(1);
|
||||||
|
expect(app.views.ConversationsForm.prototype.initialize)
|
||||||
|
.toHaveBeenCalledWith({prefill: gon.conversationPrefill});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,133 @@
|
||||||
describe("app.views.ConversationsForm", function() {
|
describe("app.views.ConversationsForm", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
spec.loadFixture("conversations_read");
|
spec.loadFixture("conversations_read");
|
||||||
|
this.target = new app.views.ConversationsForm();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initialize", function() {
|
||||||
|
it("initializes the conversation participants list", function() {
|
||||||
|
expect(this.target.conversationRecipients).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initializes the search view", function() {
|
||||||
|
spyOn(app.views.SearchBase.prototype, "initialize");
|
||||||
|
this.target.initialize();
|
||||||
|
expect(app.views.SearchBase.prototype.initialize).toHaveBeenCalled();
|
||||||
|
expect(app.views.SearchBase.prototype.initialize.calls.argsFor(0)[0].customSearch).toBe(true);
|
||||||
|
expect(app.views.SearchBase.prototype.initialize.calls.argsFor(0)[0].autoselect).toBe(true);
|
||||||
|
expect(app.views.SearchBase.prototype.initialize.calls.argsFor(0)[0].remoteRoute).toEqual({
|
||||||
|
url: "/contacts",
|
||||||
|
extraParameters: "mutual=true"
|
||||||
|
});
|
||||||
|
expect(this.target.search).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls bindTypeaheadEvents", function() {
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "bindTypeaheadEvents");
|
||||||
|
this.target.initialize();
|
||||||
|
expect(app.views.ConversationsForm.prototype.bindTypeaheadEvents).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls prefill correctly", function() {
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "prefill");
|
||||||
|
this.target.initialize();
|
||||||
|
expect(app.views.ConversationsForm.prototype.prefill).not.toHaveBeenCalled();
|
||||||
|
this.target.initialize({prefill: {}});
|
||||||
|
expect(app.views.ConversationsForm.prototype.prefill).toHaveBeenCalledWith({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addRecipient", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
$("#conversation-new").removeClass("hidden");
|
||||||
|
$("#conversation-show").addClass("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("add the participant", function() {
|
||||||
|
expect(this.target.conversationRecipients).toEqual([]);
|
||||||
|
this.target.addRecipient({name: "diaspora user", handle: "diaspora-user@pod.tld"});
|
||||||
|
expect(this.target.conversationRecipients).toEqual([{name: "diaspora user", handle: "diaspora-user@pod.tld"}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("call updateContactIdsListInput", function() {
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "updateContactIdsListInput");
|
||||||
|
this.target.addRecipient({name: "diaspora user", handle: "diaspora-user@pod.tld"});
|
||||||
|
expect(app.views.ConversationsForm.prototype.updateContactIdsListInput).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a recipient tag", function() {
|
||||||
|
expect($(".conversation-recipient-tag").length).toBe(0);
|
||||||
|
this.target.addRecipient({name: "diaspora user", handle: "diaspora-user@pod.tld"});
|
||||||
|
expect($(".conversation-recipient-tag").length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("prefill", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.prefills = [{name: "diaspora user"}, {name: "other diaspora user"}, {name: "user"}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it("call addRecipient for each prefilled participant", function() {
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "addRecipient");
|
||||||
|
this.target.prefill(this.prefills);
|
||||||
|
expect(app.views.ConversationsForm.prototype.addRecipient).toHaveBeenCalledTimes(this.prefills.length);
|
||||||
|
var allArgsFlattened = app.views.ConversationsForm.prototype.addRecipient.calls.allArgs().map(function(arg) {
|
||||||
|
return arg[0];
|
||||||
|
});
|
||||||
|
expect(allArgsFlattened).toEqual(this.prefills);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateContactIdsListInput", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.target.conversationRecipients.push({id: 1, name: "diaspora user", handle: "diaspora-user@pod.tld"});
|
||||||
|
this.target.conversationRecipients
|
||||||
|
.push({id: 2, name: "other diaspora user", handle: "other-diaspora-user@pod.tld"});
|
||||||
|
this.target.conversationRecipients.push({id: 3, name: "user@pod.tld", handle: "user@pod.tld"});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates hidden input value", function() {
|
||||||
|
this.target.updateContactIdsListInput();
|
||||||
|
expect(this.target.contactsIdsListInput.val()).toBe("1,2,3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls app.views.SearchBase.ignorePersonForSuggestions() for each participant", function() {
|
||||||
|
spyOn(app.views.SearchBase.prototype, "ignorePersonForSuggestions");
|
||||||
|
this.target.updateContactIdsListInput();
|
||||||
|
expect(app.views.SearchBase.prototype.ignorePersonForSuggestions).toHaveBeenCalledTimes(3);
|
||||||
|
expect(app.views.SearchBase.prototype.ignorePersonForSuggestions.calls.argsFor(0)[0])
|
||||||
|
.toEqual({id: 1, name: "diaspora user", handle: "diaspora-user@pod.tld"});
|
||||||
|
expect(app.views.SearchBase.prototype.ignorePersonForSuggestions.calls.argsFor(1)[0])
|
||||||
|
.toEqual({id: 2, name: "other diaspora user", handle: "other-diaspora-user@pod.tld"});
|
||||||
|
expect(app.views.SearchBase.prototype.ignorePersonForSuggestions.calls.argsFor(2)[0])
|
||||||
|
.toEqual({id: 3, name: "user@pod.tld", handle: "user@pod.tld"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("bindTypeaheadEvents", function() {
|
||||||
|
it("calls onSuggestionSelection() when clicking on a result", function() {
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "onSuggestionSelection");
|
||||||
|
var event = $.Event("typeahead:select");
|
||||||
|
var person = {name: "diaspora user"};
|
||||||
|
this.target.typeaheadElement.trigger(event, [person]);
|
||||||
|
expect(app.views.ConversationsForm.prototype.onSuggestionSelection).toHaveBeenCalledWith(person);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("onSuggestionSelection", function() {
|
||||||
|
it("calls addRecipient, updateContactIdsListInput and $.fn.typeahead", function() {
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "addRecipient");
|
||||||
|
spyOn($.fn, "typeahead");
|
||||||
|
var person = {name: "diaspora user"};
|
||||||
|
this.target.onSuggestionSelection(person);
|
||||||
|
expect(app.views.ConversationsForm.prototype.addRecipient).toHaveBeenCalledWith(person);
|
||||||
|
expect($.fn.typeahead).toHaveBeenCalledWith("val", "");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("keyDown", function() {
|
describe("keyDown", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
this.submitCallback = jasmine.createSpy().and.returnValue(false);
|
this.submitCallback = jasmine.createSpy().and.returnValue(false);
|
||||||
new app.views.ConversationsForm();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context("on new message form", function() {
|
context("on new message form", function() {
|
||||||
|
|
@ -52,6 +173,54 @@ describe("app.views.ConversationsForm", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("removeRecipient", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.target.addRecipient({id: 1, name: "diaspora user", handle: "diaspora-user@pod.tld"});
|
||||||
|
this.target.addRecipient({id: 2, name: "other diaspora user", handle: "other-diaspora-user@pod.tld"});
|
||||||
|
this.target.addRecipient({id: 3, name: "user@pod.tld", handle: "user@pod.tld"});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the user from conversation recipients when clicking the tag's remove button", function() {
|
||||||
|
expect(this.target.conversationRecipients).toEqual([
|
||||||
|
{id: 1, name: "diaspora user", handle: "diaspora-user@pod.tld"},
|
||||||
|
{id: 2, name: "other diaspora user", handle: "other-diaspora-user@pod.tld"},
|
||||||
|
{id: 3, name: "user@pod.tld", handle: "user@pod.tld"}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$("[data-diaspora-handle='diaspora-user@pod.tld'] .remove").click();
|
||||||
|
|
||||||
|
expect(this.target.conversationRecipients).toEqual([
|
||||||
|
{id: 2, name: "other diaspora user", handle: "other-diaspora-user@pod.tld"},
|
||||||
|
{id: 3, name: "user@pod.tld", handle: "user@pod.tld"}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$("[data-diaspora-handle='other-diaspora-user@pod.tld'] .remove").click();
|
||||||
|
|
||||||
|
expect(this.target.conversationRecipients).toEqual([
|
||||||
|
{id: 3, name: "user@pod.tld", handle: "user@pod.tld"}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$("[data-diaspora-handle='user@pod.tld'] .remove").click();
|
||||||
|
|
||||||
|
expect(this.target.conversationRecipients).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the tag element when clicking the tag's remove button", function() {
|
||||||
|
expect($("[data-diaspora-handle='diaspora-user@pod.tld']").length).toBe(1);
|
||||||
|
$("[data-diaspora-handle='diaspora-user@pod.tld'] .remove").click();
|
||||||
|
expect($("[data-diaspora-handle='other-diaspora-user@pod.tld']").length).toBe(1);
|
||||||
|
$("[data-diaspora-handle='other-diaspora-user@pod.tld'] .remove").click();
|
||||||
|
expect($("[data-diaspora-handle='user@pod.tld']").length).toBe(1);
|
||||||
|
$("[data-diaspora-handle='user@pod.tld'] .remove").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls updateContactIdsListInput", function() {
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "updateContactIdsListInput");
|
||||||
|
$("[data-diaspora-handle='diaspora-user@pod.tld'] .remove").click();
|
||||||
|
expect(app.views.ConversationsForm.prototype.updateContactIdsListInput).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("conversationCreateSuccess", function() {
|
describe("conversationCreateSuccess", function() {
|
||||||
it("is called when there was a successful ajax request for the conversation form", function() {
|
it("is called when there was a successful ajax request for the conversation form", function() {
|
||||||
spyOn(app.views.ConversationsForm.prototype, "conversationCreateSuccess");
|
spyOn(app.views.ConversationsForm.prototype, "conversationCreateSuccess");
|
||||||
|
|
|
||||||
|
|
@ -30,4 +30,30 @@ describe("app.views.ProfileHeader", function() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("showMessageModal", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
$("body").append("<div id='conversationModal'/>").append(spec.readFixture("conversations_modal"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls app.helpers.showModal", function() {
|
||||||
|
spyOn(app.helpers, "showModal");
|
||||||
|
this.view.showMessageModal();
|
||||||
|
expect(app.helpers.showModal);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("app.views.ConversationsForm with correct parameterswhen modal is loaded", function() {
|
||||||
|
gon.conversationPrefill = [
|
||||||
|
{id: 1, name: "diaspora user", handle: "diaspora-user@pod.tld"},
|
||||||
|
{id: 2, name: "other diaspora user", handle: "other-diaspora-user@pod.tld"},
|
||||||
|
{id: 3, name: "user@pod.tld", handle: "user@pod.tld"}
|
||||||
|
];
|
||||||
|
|
||||||
|
spyOn(app.views.ConversationsForm.prototype, "initialize");
|
||||||
|
this.view.showMessageModal();
|
||||||
|
$("#conversationModal").trigger("modal:loaded");
|
||||||
|
expect(app.views.ConversationsForm.prototype.initialize)
|
||||||
|
.toHaveBeenCalledWith({prefill: gon.conversationPrefill});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ describe("app.views.PublisherMention", function() {
|
||||||
expect(call.args[0].typeaheadInput.selector).toBe("#publisher .typeahead-mention-box");
|
expect(call.args[0].typeaheadInput.selector).toBe("#publisher .typeahead-mention-box");
|
||||||
expect(call.args[0].customSearch).toBeTruthy();
|
expect(call.args[0].customSearch).toBeTruthy();
|
||||||
expect(call.args[0].autoselect).toBeTruthy();
|
expect(call.args[0].autoselect).toBeTruthy();
|
||||||
expect(call.args[0].remoteRoute).toBe("/contacts");
|
expect(call.args[0].remoteRoute).toEqual({url: "/contacts"});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls bindTypeaheadEvents", function() {
|
it("calls bindTypeaheadEvents", function() {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ describe("app.views.Search", function() {
|
||||||
this.view = new app.views.Search({el: "#search_people_form"});
|
this.view = new app.views.Search({el: "#search_people_form"});
|
||||||
var call = app.views.SearchBase.prototype.initialize.calls.mostRecent();
|
var call = app.views.SearchBase.prototype.initialize.calls.mostRecent();
|
||||||
expect(call.args[0].typeaheadInput.selector).toBe("#search_people_form #q");
|
expect(call.args[0].typeaheadInput.selector).toBe("#search_people_form #q");
|
||||||
expect(call.args[0].remoteRoute).toBe("/search");
|
expect(call.args[0].remoteRoute).toEqual({url: "/search"});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("binds typeahead:select", function() {
|
it("binds typeahead:select", function() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue