diff --git a/Changelog.md b/Changelog.md index aabca0644c610da7ac9d6b5609fbed38967e5969..4eab24864603708cf422b7d375974d4cd3f0b9d4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -84,6 +84,7 @@ diaspora.yml file**. The existing settings from 0.4.x and before will not work a * Make sure conversations without any visibilities left are deleted [#5478](https://github.com/diaspora/diaspora/pull/5478) * Change tooltip for delete button in conversations view [#5477](https://github.com/diaspora/diaspora/pull/5477) * Replace a modifier-rescue with a specific rescue [#5491](https://github.com/diaspora/diaspora/pull/5491) +* Port contacts page to backbone [#5473](https://github.com/diaspora/diaspora/pull/5473) ## Bug fixes * orca cannot see 'Add Contact' button [#5158](https://github.com/diaspora/diaspora/pull/5158) diff --git a/app/assets/javascripts/app/collections/aspect_memberships.js b/app/assets/javascripts/app/collections/aspect_memberships.js new file mode 100644 index 0000000000000000000000000000000000000000..dd8ee1b5d2297e961f662cf7f7d44cab0c43355d --- /dev/null +++ b/app/assets/javascripts/app/collections/aspect_memberships.js @@ -0,0 +1,6 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +app.collections.AspectMemberships = Backbone.Collection.extend({ + model: app.models.AspectMembership +}) +// @license-end diff --git a/app/assets/javascripts/app/collections/contacts.js b/app/assets/javascripts/app/collections/contacts.js new file mode 100644 index 0000000000000000000000000000000000000000..d0592155f29c9373ba5ff0a86874e488bc223dbc --- /dev/null +++ b/app/assets/javascripts/app/collections/contacts.js @@ -0,0 +1,21 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +app.collections.Contacts = Backbone.Collection.extend({ + model: app.models.Contact, + + comparator : function(con1, con2) { + if( !con1.person || !con2.person ) return 1; + + if(app.aspect) { + var inAspect1 = con1.inAspect(app.aspect.get('id')); + var inAspect2 = con2.inAspect(app.aspect.get('id')); + if( inAspect1 && !inAspect2 ) return -1; + if( !inAspect1 && inAspect2 ) return 1; + } + + var n1 = con1.person.get('name'); + var n2 = con2.person.get('name'); + return n1.localeCompare(n2); + } +}); +// @license-end diff --git a/app/assets/javascripts/app/helpers/handlebars-helpers.js b/app/assets/javascripts/app/helpers/handlebars-helpers.js index 00c78ffaff8438aa1a673c9edccaffadfd8dce0a..17f42e3c2a639b752fc7571a32340cf0aa076023 100644 --- a/app/assets/javascripts/app/helpers/handlebars-helpers.js +++ b/app/assets/javascripts/app/helpers/handlebars-helpers.js @@ -21,7 +21,7 @@ Handlebars.registerHelper('urlTo', function(path_helper, id, data){ return Routes[path_helper+'_path'](id, data.hash); }); -Handlebars.registerHelper('linkToPerson', function(context, block) { +Handlebars.registerHelper('linkToAuthor', function(context, block) { if( !context ) context = this; var html = "<a href=\"/people/" + context.guid + "\" class=\"author-name "; html += Handlebars.helpers.hovercardable(context); @@ -32,6 +32,15 @@ Handlebars.registerHelper('linkToPerson', function(context, block) { return html }); +Handlebars.registerHelper('linkToPerson', function(context, block) { + if( !context ) context = this; + var html = "<a href=\"/people/" + context.guid + "\" class=\"name\">"; + html += block.fn(context); + html += "</a>"; + + return html +}); + // relationship indicator for profile page Handlebars.registerHelper('sharingMessage', function(person) { var i18n_scope = 'people.helper.is_not_sharing'; @@ -107,5 +116,20 @@ Handlebars.registerHelper('isCurrentProfilePage', function(id, diaspora_handle, return Handlebars.helpers.isCurrentPage('person', id, options) || Handlebars.helpers.isCurrentPage('user_profile', username, options); }); + +Handlebars.registerHelper('aspectMembershipIndicator', function(contact,in_aspect) { + if(!app.aspect || !app.aspect.get('id')) return '<div class="aspect_membership_dropdown placeholder"></div>'; + + var html = '<i class="entypo '; + if( in_aspect == 'in_aspect' ) { + html += 'circled-cross contact_remove-from-aspect" '; + html += 'title="' + Diaspora.I18n.t('contacts.remove_contact') + '" '; + } else { + html += 'circled-plus contact_add-to-aspect" '; + html += 'title="' + Diaspora.I18n.t('contacts.add_contact') + '" '; + } + html += '></i>'; + return html; +}); // @license-end diff --git a/app/assets/javascripts/app/models/contact.js b/app/assets/javascripts/app/models/contact.js new file mode 100644 index 0000000000000000000000000000000000000000..a1e014c4c1340ebc20d38cdd7e51bc7dd838f76f --- /dev/null +++ b/app/assets/javascripts/app/models/contact.js @@ -0,0 +1,13 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +app.models.Contact = Backbone.Model.extend({ + initialize : function() { + this.aspect_memberships = new app.collections.AspectMemberships(this.get('aspect_memberships')); + if( this.get('person') ) this.person = new app.models.Person(this.get('person')); + }, + + inAspect : function(id) { + return this.aspect_memberships.any(function(membership){ return membership.get('aspect').id == id; }); + } +}); +// @license-end diff --git a/app/assets/javascripts/app/views/contacts_view.js b/app/assets/javascripts/app/pages/contacts.js similarity index 50% rename from app/assets/javascripts/app/views/contacts_view.js rename to app/assets/javascripts/app/pages/contacts.js index 970719247e14825fb916792e349183204ccd640f..ac44783944f0fa7e59b0bf083f0751a403d623fa 100644 --- a/app/assets/javascripts/app/views/contacts_view.js +++ b/app/assets/javascripts/app/pages/contacts.js @@ -1,6 +1,6 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later -app.views.Contacts = Backbone.View.extend({ +app.pages.Contacts = Backbone.View.extend({ el: "#contacts_container", @@ -8,17 +8,15 @@ app.views.Contacts = Backbone.View.extend({ "click #contacts_visibility_toggle" : "toggleContactVisibility", "click #chat_privilege_toggle" : "toggleChatPrivilege", "click #change_aspect_name" : "showAspectNameForm", - "click .contact_remove-from-aspect" : "removeContactFromAspect", - "click .contact_add-to-aspect" : "addContactToAspect", "keyup #contact_list_search" : "searchContactList" }, - initialize: function() { + initialize: function(opts) { this.visibility_toggle = $("#contacts_visibility_toggle .entypo"); this.chat_toggle = $("#chat_privilege_toggle .entypo"); + this.stream = opts.stream; + this.stream.render(); $("#people_stream.contacts .header .entypo").tooltip({ 'placement': 'bottom'}); - $(".contact_remove-from-aspect").tooltip(); - $(".contact_add-to-aspect").tooltip(); $(document).on('ajax:success', 'form.edit_aspect', this.updateAspectName); }, @@ -69,65 +67,8 @@ app.views.Contacts = Backbone.View.extend({ $(".header > h3").show(); }, - addContactToAspect: function(e){ - var contact = $(e.currentTarget); - var aspect_membership = new app.models.AspectMembership({ - 'person_id': contact.attr('data-person_id'), - 'aspect_id': contact.attr('data-aspect_id') - }); - - aspect_membership.save({},{ - success: function(model,response){ - contact.attr('data-membership_id',model.id) - .tooltip('destroy') - .removeAttr('data-original-title') - .removeClass("contact_add-to-aspect").removeClass("circled-plus") - .addClass("contact_remove-from-aspect").addClass("circled-cross") - .attr('title', Diaspora.I18n.t('contacts.remove_contact')) - .tooltip() - .closest('.stream_element').addClass('in_aspect'); - }, - error: function(model,response){ - var msg = Diaspora.I18n.t('contacts.error_add', { 'name':contact.closest('.stream_element').find('.name').text() }); - Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg }); - } - }); - }, - - removeContactFromAspect: function(e){ - var contact = $(e.currentTarget); - var aspect_membership = new app.models.AspectMembership({ - 'id': contact.attr('data-membership_id') - }); - - aspect_membership.destroy({ - success: function(model,response){ - contact.removeAttr('data-membership_id') - .tooltip('destroy') - .removeAttr('data-original-title') - .removeClass("contact_remove-from-aspect").removeClass("circled-cross") - .addClass("contact_add-to-aspect").addClass("circled-plus") - .attr('title', Diaspora.I18n.t('contacts.add_contact')) - .tooltip() - .closest('.stream_element').removeClass('in_aspect'); - }, - error: function(model,response){ - var msg = Diaspora.I18n.t('contacts.error_remove', { 'name':contact.closest('.stream_element').find('.name').text() }); - Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg }); - } - }); - }, - searchContactList: function(e) { - var query = new RegExp($(e.target).val(),'i'); - - $("#people_stream.stream.contacts .stream_element").each(function(){ - if($(this).find(".name").text().match(query)){ - $(this).show(); - } else { - $(this).hide(); - } - }); + this.stream.search($(e.target).val()); } }); // @license-end diff --git a/app/assets/javascripts/app/router.js b/app/assets/javascripts/app/router.js index c71222ff8bdddcc513052c46b9492f03894dc7c3..76398707cd5105fa2e1a61bab7c65fa1cf1499fa 100644 --- a/app/assets/javascripts/app/router.js +++ b/app/assets/javascripts/app/router.js @@ -43,7 +43,15 @@ app.Router = Backbone.Router.extend({ }, contacts: function() { - app.contacts = new app.views.Contacts(); + app.aspect = new app.models.Aspect(gon.preloads.aspect); + app.contacts = new app.collections.Contacts(app.parsePreload('contacts')); + + var stream = new app.views.ContactStream({ + collection: app.contacts, + el: $('.stream.contacts #contact_stream'), + }); + + app.page = new app.pages.Contacts({stream: stream}); }, conversations: function() { diff --git a/app/assets/javascripts/app/views/contact_stream_view.js b/app/assets/javascripts/app/views/contact_stream_view.js new file mode 100644 index 0000000000000000000000000000000000000000..ad93301bd809be1ff5c92f97dee0d5e0ebd022cf --- /dev/null +++ b/app/assets/javascripts/app/views/contact_stream_view.js @@ -0,0 +1,77 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +app.views.ContactStream = Backbone.View.extend({ + initialize: function() { + this.itemCount = 0; + this.perPage = 25; + this.query = ''; + this.resultList = this.collection.toArray(); + var throttledScroll = _.throttle(_.bind(this.infScroll, this), 200); + $(window).scroll(throttledScroll); + this.on('renderContacts', this.renderContacts, this); + }, + + render: function() { + if( _.isEmpty(this.resultList) ) { + var content = document.createDocumentFragment(); + content = '<div id="no_contacts" class="well">' + + ' <h4>' + + Diaspora.I18n.t('contacts.search_no_results') + + ' </h4>' + + '</div>'; + this.$el.html(content); + } else { + this.$el.html(''); + this.renderContacts(); + } + }, + + renderContacts: function() { + this.$el.addClass("loading"); + var content = document.createDocumentFragment(); + _.rest(_.first(this.resultList , this.itemCount + this.perPage), this.itemCount).forEach( function(item) { + var view = new app.views.Contact({model: item}); + content.appendChild(view.render().el); + }); + + var size = _.size(this.resultList); + if( this.itemCount + this.perPage >= size ){ + this.itemCount = size; + this.off('renderContacts'); + } else { + this.itemCount += this.perPage; + } + this.$el.append(content); + this.$el.removeClass("loading"); + }, + + search: function(query) { + query = query.trim(); + if( query || this.query ) { + this.off('renderContacts'); + this.on('renderContacts', this.renderContacts, this); + this.itemCount = 0; + if( query ) { + this.query = query; + var regex = new RegExp(query,'i'); + this.resultList = this.collection.filter(function(contact) { + return regex.test(contact.get('person').name) || + regex.test(contact.get('person').diaspora_id); + }); + } else { + this.resultList = this.collection.toArray(); + this.query = ''; + } + this.render(); + } + }, + + infScroll: function() { + if( this.$el.hasClass('loading') ) return; + + var distanceTop = $(window).height() + $(window).scrollTop(), + distanceBottom = $(document).height() - distanceTop; + if(distanceBottom < 300) this.trigger('renderContacts'); + } +}); +// @license-end diff --git a/app/assets/javascripts/app/views/contact_view.js b/app/assets/javascripts/app/views/contact_view.js new file mode 100644 index 0000000000000000000000000000000000000000..4fd2d7efb5b024d704db8fccdbf7e81fd004430f --- /dev/null +++ b/app/assets/javascripts/app/views/contact_view.js @@ -0,0 +1,72 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +app.views.Contact = app.views.Base.extend({ + templateName: 'contact', + + events: { + "click .contact_add-to-aspect" : "addContactToAspect", + "click .contact_remove-from-aspect" : "removeContactFromAspect" + }, + + tooltipSelector: '.contact_add-to-aspect, .contact_remove-from-aspect', + + presenter: function() { + return _.extend(this.defaultPresenter(), { + person_id : this.model.get('person_id'), + person : this.model.get('person'), + in_aspect: (app.aspect && this.model.inAspect(app.aspect.get('id'))) ? 'in_aspect' : '', + }); + }, + + postRenderTemplate: function() { + var self = this; + var dropdownEl = this.$('.aspect_membership_dropdown.placeholder'); + if( dropdownEl.length == 0 ) { + return; + } + + // TODO render me client side!!! + var href = this.model.person.url() + '/aspect_membership_button?size=small'; + if( gon.bootstrap ) href += '&bootstrap=true'; + + $.get(href, function(resp) { + dropdownEl.html(resp); + new app.views.AspectMembership({el: $('.aspect_dropdown',dropdownEl)}); + + // UGLY (re-)attach the facebox + self.$('a[rel*=facebox]').facebox(); + }); + }, + + addContactToAspect: function(){ + var self = this; + this.model.aspect_memberships.create({ + 'person_id': this.model.get('person_id'), + 'aspect_id': app.aspect.get('id') + },{ + success: function(model,response){ + self.render(); + }, + error: function(model,response){ + var msg = Diaspora.I18n.t('contacts.error_add', { 'name': self.model.get('person').name }); + Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg }); + } + }); + }, + + removeContactFromAspect: function(){ + var self = this; + this.model.aspect_memberships + .find(function(membership){ return membership.get('aspect').id == app.aspect.id; }) + .destroy({ + success: function(model,response){ + self.render(); + }, + error: function(model,response){ + var msg = Diaspora.I18n.t('contacts.error_remove', { 'name': self.model.get('person').name }); + Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg }); + } + }); + } +}); +// @license-end diff --git a/app/assets/stylesheets/contacts.css.scss b/app/assets/stylesheets/contacts.css.scss index 5bf2597a631c8ae92234ff6b06c390557de3eab1..b2eae53054c4941847d1a5d2d43f08a2f726881c 100644 --- a/app/assets/stylesheets/contacts.css.scss +++ b/app/assets/stylesheets/contacts.css.scss @@ -66,8 +66,6 @@ } #no_contacts { + margin-top: 20px; text-align: center; - padding: 10px; - background-color: #eee; - color: $text-dark-grey; } diff --git a/app/assets/templates/comment_tpl.jst.hbs b/app/assets/templates/comment_tpl.jst.hbs index 23eb368d9fe3eea5fbf05bf0f06740db319727f4..3196cad33235756b0608e8f3465dd44fb7cb7083 100644 --- a/app/assets/templates/comment_tpl.jst.hbs +++ b/app/assets/templates/comment_tpl.jst.hbs @@ -1,8 +1,8 @@ <div id="{{guid}}"> <div class="img"> - {{#linkToPerson author}} + {{#linkToAuthor author}} {{{personImage this "small" "small"}}} - {{/linkToPerson}} + {{/linkToAuthor}} </div> <div class="bd"> diff --git a/app/assets/templates/contact_tpl.jst.hbs b/app/assets/templates/contact_tpl.jst.hbs new file mode 100644 index 0000000000000000000000000000000000000000..a89542f4ef1e04857cdb674edaf51b8bfa72359c --- /dev/null +++ b/app/assets/templates/contact_tpl.jst.hbs @@ -0,0 +1,23 @@ +<div class="stream_element media contact {{in_aspect}}" id={{person_id}}> + <div class="pull-right"> + {{{aspectMembershipIndicator this in_aspect}}} + </div> + + <div class="media-object pull-left"> + {{{personImage person 'small'}}} + </div> + + <div class="media-body"> + {{#linkToPerson person}} + {{name}} + {{/linkToPerson}} + + <div class="info diaspora_handle"> + {{person.diaspora_id}} + </div> + + <div class="info tags"> + {{{fmtTags person.profile.tags}}} + </div> + </div> +</div> diff --git a/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs b/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs index a144e03902378fad2b71b27262eefa364783cc5a..e804b3c956cc6e0717078d3a3ca6b34ed087a50e 100644 --- a/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs +++ b/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs @@ -3,26 +3,26 @@ <div id='post-info' class='span8'> <div class="img pull-left"> {{#if root}} - {{#linkToPerson root.author}} + {{#linkToAuthor root.author}} {{{personImage this 'medium'}}} - {{/linkToPerson}} + {{/linkToAuthor}} {{else}} - {{#linkToPerson author}} + {{#linkToAuthor author}} {{{personImage this 'medium'}}} - {{/linkToPerson}} + {{/linkToAuthor}} {{/if}} </div> <div class="bd"> <span class='author'> {{#if root}} - {{#linkToPerson root.author}} + {{#linkToAuthor root.author}} {{name}} - {{/linkToPerson}} + {{/linkToAuthor}} {{else}} - {{#linkToPerson author}} + {{#linkToAuthor author}} {{name}} - {{/linkToPerson}} + {{/linkToAuthor}} {{/if}} </span> @@ -69,14 +69,14 @@ <div class='span8' id='reshare-info'> <i class='entypo retweet small pull-left'></i> <div class="img pull-left"> - {{#linkToPerson author}} + {{#linkToAuthor author}} {{{personImage this 'small'}}} - {{/linkToPerson}} + {{/linkToAuthor}} </div> <span class="author"> - {{#linkToPerson author}} + {{#linkToAuthor author}} {{name}} - {{/linkToPerson}} + {{/linkToAuthor}} </span> <span class="post-time"> <a href="/posts/{{id}}"> diff --git a/app/assets/templates/single-post-viewer/single-post-interactions_tpl.jst.hbs b/app/assets/templates/single-post-viewer/single-post-interactions_tpl.jst.hbs index aebe7a6801dfe1300c08e5e72ec05f21575abdd8..2488ea0de6cc9f371316f2b20689d1a44fc0da24 100644 --- a/app/assets/templates/single-post-viewer/single-post-interactions_tpl.jst.hbs +++ b/app/assets/templates/single-post-viewer/single-post-interactions_tpl.jst.hbs @@ -6,9 +6,9 @@ </span> <span> {{#each reshares}} - {{#linkToPerson author}} + {{#linkToAuthor author}} {{{personImage this 'small' 'micro'}}} - {{/linkToPerson}} + {{/linkToAuthor}} {{/each}} </span> </div> @@ -21,9 +21,9 @@ </span> <span> {{#each likes}} - {{#linkToPerson author}} + {{#linkToAuthor author}} {{{personImage this 'small' 'micro'}}} - {{/linkToPerson}} + {{/linkToAuthor}} {{/each}} </span> </div> diff --git a/app/assets/templates/stream-faces_tpl.jst.hbs b/app/assets/templates/stream-faces_tpl.jst.hbs index 6db001f192370cbdd0f368a48922a1a48cc68206..2e9898905ca1c1776d789132d91c7086439cda88 100644 --- a/app/assets/templates/stream-faces_tpl.jst.hbs +++ b/app/assets/templates/stream-faces_tpl.jst.hbs @@ -1,5 +1,5 @@ {{#people}} - {{#linkToPerson this}} + {{#linkToAuthor this}} {{{personImage this "small"}}} - {{/linkToPerson}} + {{/linkToAuthor}} {{/people}} diff --git a/app/assets/templates/stream-frame_tpl.jst.hbs b/app/assets/templates/stream-frame_tpl.jst.hbs index 5574a479de1b55ce97740f3fd23995d9e1d6e4e3..a9f55163c1f53d8c7c87d42e1dd5fca2092abd7b 100644 --- a/app/assets/templates/stream-frame_tpl.jst.hbs +++ b/app/assets/templates/stream-frame_tpl.jst.hbs @@ -14,16 +14,16 @@ <div class='stream-frame-feedback'></div> <div class="media author"> - {{#linkToPerson author}} + {{#linkToAuthor author}} <div class="img"> <div class="profile-image-container smaller" style="background-image : url('{{avatar.large}}')"></div> </div> - {{/linkToPerson}} + {{/linkToAuthor}} <div class="bd"> - {{#linkToPerson author}} + {{#linkToAuthor author}} {{name}} - {{/linkToPerson}} + {{/linkToAuthor}} </div> </div> diff --git a/app/controllers/aspect_memberships_controller.rb b/app/controllers/aspect_memberships_controller.rb index 5009902b92641293c2baf4d6a934c823153f72f1..eddfa39359ab1a5408675a58303444ca1d122fcb 100644 --- a/app/controllers/aspect_memberships_controller.rb +++ b/app/controllers/aspect_memberships_controller.rb @@ -34,10 +34,7 @@ class AspectMembershipsController < ApplicationController respond_to do |format| format.json do if success - render :json => { - :person_id => contact.person_id, - :aspect_ids => contact.aspects.map{|a| a.id} - } + render :json => AspectMembershipPresenter.new(membership).base_hash else render :text => membership.errors.full_messages, :status => 403 end @@ -57,7 +54,9 @@ class AspectMembershipsController < ApplicationController flash.now[:notice] = I18n.t('aspects.add_to_aspect.success') respond_with do |format| format.json do - render :json => AspectMembership.where(:contact_id => @contact.id, :aspect_id => @aspect.id).first.to_json + render :json => AspectMembershipPresenter.new( + AspectMembership.where(:contact_id => @contact.id, :aspect_id => @aspect.id).first) + .base_hash end format.all { redirect_to :back } diff --git a/app/controllers/contacts_controller.rb b/app/controllers/contacts_controller.rb index 9a57ba3e43d613c379e4e4987cb4cbfb50dadfc8..0e6168c9c607b8eb86a721222e06640ae78ce54f 100644 --- a/app/controllers/contacts_controller.rb +++ b/app/controllers/contacts_controller.rb @@ -40,32 +40,24 @@ class ContactsController < ApplicationController @contacts = contacts_by_type(type) @contacts_size = @contacts.length + gon.preloads[:contacts] = @contacts.map{ |c| ContactPresenter.new(c, current_user).full_hash_with_person } end def contacts_by_type(type) - contacts = case type + case type when "all" - [current_user.contacts] + current_user.contacts when "only_sharing" - [current_user.contacts.only_sharing] + current_user.contacts.only_sharing when "receiving" - [current_user.contacts.receiving] + current_user.contacts.receiving when "by_aspect" @aspect = current_user.aspects.find(params[:a_id]) - @contacts_in_aspect = @aspect.contacts - @contacts_not_in_aspect = current_user.contacts.where.not(contacts: {id: @contacts_in_aspect.pluck(:id) }) - [@contacts_in_aspect, @contacts_not_in_aspect].map {|relation| - relation.includes(:aspect_memberships) - } + gon.preloads[:aspect] = AspectPresenter.new(@aspect).as_json + current_user.contacts else raise ArgumentError, "unknown type #{type}" end - - contacts.map {|relation| - relation.includes(:person => :profile).to_a.tap {|contacts| - contacts.sort_by! {|contact| contact.person.name } - } - }.inject(:+).paginate(:page => params[:page], :per_page => 25) end def set_up_contacts_mobile diff --git a/app/helpers/contacts_helper.rb b/app/helpers/contacts_helper.rb index 5ea96b5a268812308a1c60e19bb5ee00da46a0bd..3fdee52f35acaad92a6600a4655693130e43d74a 100644 --- a/app/helpers/contacts_helper.rb +++ b/app/helpers/contacts_helper.rb @@ -1,25 +1,9 @@ module ContactsHelper def contact_aspect_dropdown(contact) - membership = contact.aspect_memberships.where(:aspect_id => @aspect.id).first unless @aspect.nil? - - if membership - content_tag(:i, nil, :class => 'entypo circled-cross contact_remove-from-aspect', - :title => t('contacts.index.remove_contact'), - 'data-aspect_id' => @aspect.id, - 'data-person_id' => contact.person_id, - 'data-membership_id' => membership.id ) - - elsif @aspect.nil? - render :partial => 'people/relationship_action', - :locals => { :person => contact.person, - :contact => contact, - :current_user => current_user } - else - content_tag(:i, nil, :class => 'entypo circled-plus contact_add-to-aspect', - :title => t('contacts.index.add_contact'), - 'data-aspect_id' => @aspect.id, - 'data-person_id' => contact.person_id ) - end + render :partial => 'people/relationship_action', + :locals => { :person => contact.person, + :contact => contact, + :current_user => current_user } end def start_a_conversation_link(aspect, contacts_size) diff --git a/app/presenters/aspect_membership_presenter.rb b/app/presenters/aspect_membership_presenter.rb new file mode 100644 index 0000000000000000000000000000000000000000..28a96e38f203130c4aef55f03b0b62f6e26acd2b --- /dev/null +++ b/app/presenters/aspect_membership_presenter.rb @@ -0,0 +1,11 @@ +class AspectMembershipPresenter < BasePresenter + def initialize(membership) + @membership = membership + end + + def base_hash + { id: @membership.id, + aspect: AspectPresenter.new(@membership.aspect).as_json, + } + end +end diff --git a/app/presenters/contact_presenter.rb b/app/presenters/contact_presenter.rb new file mode 100644 index 0000000000000000000000000000000000000000..9ab8b2d2675b7fe2fbcbac6a8baeaf74b8f29ce3 --- /dev/null +++ b/app/presenters/contact_presenter.rb @@ -0,0 +1,17 @@ +class ContactPresenter < BasePresenter + def base_hash + { id: id, + person_id: person_id + } + end + + def full_hash + base_hash.merge({ + aspect_memberships: aspect_memberships.map{ |membership| AspectMembershipPresenter.new(membership).base_hash } + }) + end + + def full_hash_with_person + full_hash.merge({person: PersonPresenter.new(person).full_hash_with_profile}) + end +end diff --git a/app/views/contacts/_contact.html.haml b/app/views/contacts/_contact.html.haml deleted file mode 100644 index 3e6aeb8a409964359a2278b7de5adaf3a47b71d5..0000000000000000000000000000000000000000 --- a/app/views/contacts/_contact.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- membership = contact.aspect_memberships.where(:aspect_id => @aspect.id).first unless @aspect.nil? -.media.stream_element{:id => contact.person_id, :class => ("in_aspect" if membership)} - .pull-right - = contact_aspect_dropdown(contact) - .media-object.pull-left - = person_image_link(contact.person, :size => :thumb_small) - .media-body - = person_link(contact.person, :class => 'name') - .info.diaspora_handle - = contact.person_diaspora_handle - .info.tags - = Diaspora::Taggable.format_tags(contact.person.profile.tag_string) diff --git a/app/views/contacts/index.html.haml b/app/views/contacts/index.html.haml index 4d8f223b4f246903255956b540efea0546b012bf..62161cbfad7ddb08369909a6a4361e1e0bb435b8 100644 --- a/app/views/contacts/index.html.haml +++ b/app/views/contacts/index.html.haml @@ -11,8 +11,9 @@ = render 'contacts/header' - if @contacts_size > 0 - = render @contacts - = will_paginate @contacts + #contact_stream + -# JS + - else .no_contacts %h3 diff --git a/config/locales/javascript/javascript.en.yml b/config/locales/javascript/javascript.en.yml index 78fdc4533b3d9e243d92aacc13b3a9e07ea1940a..77fbeb3d14e3d38d735f30ab950e7d245ecfbec0 100644 --- a/config/locales/javascript/javascript.en.yml +++ b/config/locales/javascript/javascript.en.yml @@ -48,6 +48,7 @@ en: remove_contact: "Remove contact" error_add: "Couldn't add <%= name %> to the aspect :(" error_remove: "Couldn't remove <%= name %> from the aspect :(" + search_no_results: "No contacts found" my_activity: "My Activity" my_stream: "Stream" diff --git a/spec/controllers/aspect_memberships_controller_spec.rb b/spec/controllers/aspect_memberships_controller_spec.rb index 0a5e125e909f93b4e0f7a124dda0638f26f2a6b0..abdae8de8ad58b33b258ebbeab27d1013fa2de84 100644 --- a/spec/controllers/aspect_memberships_controller_spec.rb +++ b/spec/controllers/aspect_memberships_controller_spec.rb @@ -72,14 +72,14 @@ describe AspectMembershipsController, :type => :controller do end context 'json' do - it 'returns a list of aspect ids for the person' do + it 'returns the aspect membership' do post :create, :format => :json, :person_id => @person.id, :aspect_id => @aspect0.id contact = @controller.current_user.contact_for(@person) - expect(response.body).to eq(contact.aspect_memberships.first.to_json) + expect(response.body).to eq(AspectMembershipPresenter.new(contact.aspect_memberships.first).base_hash.to_json) end end end diff --git a/spec/controllers/jasmine_fixtures/contacts_spec.rb b/spec/controllers/jasmine_fixtures/contacts_spec.rb index f21500def401143154683d54f4dde14991f4a4e2..6311c3b34385006c5076909071a6feeeb16f32da 100644 --- a/spec/controllers/jasmine_fixtures/contacts_spec.rb +++ b/spec/controllers/jasmine_fixtures/contacts_spec.rb @@ -10,12 +10,20 @@ describe ContactsController, :type => :controller do AppConfig.chat.enabled = true @aspect = bob.aspects.create(:name => "another aspect") bob.share_with alice.person, @aspect + bob.share_with eve.person, @aspect sign_in :user, bob end - it "generates a jasmine fixture", :fixture => true do + it "generates the aspects_manage fixture", :fixture => true do get :index, :a_id => @aspect.id save_fixture(html_for("body"), "aspects_manage") end + + it "generates the contacts_json fixture", :fixture => true do + json = bob.contacts.map { |c| + ContactPresenter.new(c, bob).full_hash_with_person + }.to_json + save_fixture(json, "contacts_json") + end end end diff --git a/spec/javascripts/app/collections/contacts_collection_spec.js b/spec/javascripts/app/collections/contacts_collection_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..3b64d4bd9724caa1faeb7ec2a649a46e1bebb75e --- /dev/null +++ b/spec/javascripts/app/collections/contacts_collection_spec.js @@ -0,0 +1,37 @@ +describe("app.collections.Contacts", function(){ + beforeEach(function(){ + this.collection = new app.collections.Contacts(); + }); + + describe("comparator", function() { + beforeEach(function(){ + this.aspect = new app.models.Aspect({id: 42, name: "cats"}); + this.con1 = new app.models.Contact({ + person: { name: "aaa" }, + aspect_memberships: [] + }); + this.con2 = new app.models.Contact({ + person: { name: "aaa" }, + aspect_memberships: [{id: 23, aspect: this.aspect}] + }); + this.con3 = new app.models.Contact({ + person: { name: "zzz" }, + aspect_memberships: [{id: 23, aspect: this.aspect}] + }); + }); + + it("should compare the username if app.aspect is not present", function() { + expect(this.collection.comparator(this.con1, this.con3)).toBeLessThan(0); + }); + + it("should compare the aspect memberships if app.aspect is present", function() { + app.aspect = this.aspect; + expect(this.collection.comparator(this.con1, this.con3)).toBeGreaterThan(0); + }); + + it("should compare the username if the contacts have equal aspect memberships", function() { + app.aspect = this.aspect; + expect(this.collection.comparator(this.con2, this.con3)).toBeLessThan(0); + }); + }); +}); diff --git a/spec/javascripts/app/models/contact_spec.js b/spec/javascripts/app/models/contact_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b5fda20c1ae06ce0dfa20a0f97d34e9dc1749bf4 --- /dev/null +++ b/spec/javascripts/app/models/contact_spec.js @@ -0,0 +1,20 @@ +describe("app.models.Contact", function() { + + beforeEach(function(){ + this.aspect = factory.aspect(); + this.contact = new app.models.Contact({ + person: { name: "aaa" }, + aspect_memberships: [{id: 42, aspect: this.aspect}] + }); + }); + + describe("inAspect", function(){ + it("returns true if the contact has been added to the aspect", function(){ + expect(this.contact.inAspect(this.aspect.id)).toBeTruthy(); + }); + + it("returns false if the contact hasn't been added to the aspect", function(){ + expect(this.contact.inAspect(this.aspect.id+1)).toBeFalsy(); + }); + }); +}); diff --git a/spec/javascripts/app/pages/contacts_spec.js b/spec/javascripts/app/pages/contacts_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e62bce96a030f1c9a1d5e0d5220caa2bb551b1e5 --- /dev/null +++ b/spec/javascripts/app/pages/contacts_spec.js @@ -0,0 +1,101 @@ +describe("app.pages.Contacts", function(){ + beforeEach(function() { + spec.loadFixture("aspects_manage"); + this.view = new app.pages.Contacts({ + stream: { + render: function(){} + } + }); + Diaspora.I18n.load({ + contacts: { + aspect_list_is_visible: "Contacts in this aspect are able to see each other.", + aspect_list_is_not_visible: "Contacts in this aspect are not able to see each other.", + aspect_chat_is_enabled: "Contacts in this aspect are able to chat with you.", + aspect_chat_is_not_enabled: "Contacts in this aspect are not able to chat with you.", + } + }); + }); + + context('toggle chat privilege', function() { + beforeEach(function() { + this.chat_toggle = $("#chat_privilege_toggle"); + this.chat_icon = $("#chat_privilege_toggle .entypo"); + }); + + it('updates the title for the tooltip', function() { + expect(this.chat_icon.attr('data-original-title')).toBe( + Diaspora.I18n.t("contacts.aspect_chat_is_not_enabled") + ); + this.chat_toggle.trigger('click'); + expect(this.chat_icon.attr('data-original-title')).toBe( + Diaspora.I18n.t("contacts.aspect_chat_is_enabled") + ); + }); + + it('toggles the chat icon', function() { + expect(this.chat_icon.hasClass('enabled')).toBeFalsy(); + this.chat_toggle.trigger('click'); + expect(this.chat_icon.hasClass('enabled')).toBeTruthy(); + }); + }); + + context('toggle contacts visibility', function() { + beforeEach(function() { + this.visibility_toggle = $("#contacts_visibility_toggle"); + this.lock_icon = $("#contacts_visibility_toggle .entypo"); + }); + + it('updates the title for the tooltip', function() { + expect(this.lock_icon.attr('data-original-title')).toBe( + Diaspora.I18n.t("contacts.aspect_list_is_visible") + ); + + this.visibility_toggle.trigger('click'); + + expect(this.lock_icon.attr('data-original-title')).toBe( + Diaspora.I18n.t("contacts.aspect_list_is_not_visible") + ); + }); + + it('toggles the lock icon', function() { + expect(this.lock_icon.hasClass('lock-open')).toBeTruthy(); + expect(this.lock_icon.hasClass('lock')).toBeFalsy(); + + this.visibility_toggle.trigger('click'); + + expect(this.lock_icon.hasClass('lock')).toBeTruthy(); + expect(this.lock_icon.hasClass('lock-open')).toBeFalsy(); + }); + }); + + context('show aspect name form', function() { + beforeEach(function() { + this.button = $('#change_aspect_name'); + }); + + it('shows the form', function() { + expect($('#aspect_name_form').css('display')).toBe('none'); + this.button.trigger('click'); + expect($('#aspect_name_form').css('display')).not.toBe('none'); + }); + + it('hides the aspect name', function() { + expect($('.header > h3').css('display')).not.toBe('none'); + this.button.trigger('click'); + expect($('.header > h3').css('display')).toBe('none'); + }); + }); + + context('search contact list', function() { + beforeEach(function() { + this.searchinput = $('#contact_list_search'); + }); + + it('calls stream.search', function() { + this.view.stream.search = jasmine.createSpy(); + this.searchinput.val("Username"); + this.searchinput.trigger('keyup'); + expect(this.view.stream.search).toHaveBeenCalledWith("Username"); + }); + }); +}); diff --git a/spec/javascripts/app/views/contact_stream_view_spec.js b/spec/javascripts/app/views/contact_stream_view_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..955dd2e7b16f7feac26721b8fd83e65b22fdb800 --- /dev/null +++ b/spec/javascripts/app/views/contact_stream_view_spec.js @@ -0,0 +1,77 @@ +describe("app.views.ContactStream", function() { + beforeEach(function() { + loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}}); + spec.loadFixture("aspects_manage"); + this.contacts = new app.collections.Contacts($.parseJSON(spec.readFixture("contacts_json"))); + app.aspect = new app.models.Aspect(this.contacts.first().get('aspect_memberships')[0].aspect); + this.view = new app.views.ContactStream({ + collection : this.contacts, + el: $('.stream.contacts #contact_stream') + }); + + this.view.perPage=1; + + //clean the page + this.view.$el.html(''); + }); + + describe("initialize", function() { + it("binds an infinite scroll listener", function() { + spyOn($.fn, "scroll"); + new app.views.ContactStream({collection : this.contacts}); + expect($.fn.scroll).toHaveBeenCalled(); + }); + }); + + describe("search", function() { + it("filters the contacts", function() { + this.view.render(); + expect(this.view.$el.html()).toContain("alice"); + this.view.search("eve"); + expect(this.view.$el.html()).not.toContain("alice"); + expect(this.view.$el.html()).toContain("eve"); + }); + }); + + describe("infScroll", function() { + beforeEach(function() { + this.view.off("renderContacts"); + this.fn = jasmine.createSpy(); + this.view.on("renderContacts", this.fn); + spyOn($.fn, "height").and.returnValue(0); + spyOn($.fn, "scrollTop").and.returnValue(100); + }); + + it("triggers renderContacts when the user is at the bottom of the page", function() { + this.view.infScroll(); + expect(this.fn).toHaveBeenCalled(); + }); + }); + + describe("render", function() { + beforeEach(function() { + spyOn(this.view, "renderContacts"); + }); + + it("calls renderContacts", function() { + this.view.render(); + expect(this.view.renderContacts).toHaveBeenCalled(); + }); + }); + + describe("renderContacts", function() { + beforeEach(function() { + this.view.off("renderContacts"); + this.view.renderContacts(); + }); + + it("renders perPage contacts", function() { + expect(this.view.$el.find('.stream_element.contact').length).toBe(1); + }); + + it("renders more contacts when called a second time", function() { + this.view.renderContacts(); + expect(this.view.$el.find('.stream_element.contact').length).toBe(2); + }); + }); +}); diff --git a/spec/javascripts/app/views/contact_view_spec.js b/spec/javascripts/app/views/contact_view_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0dee502e938d5b252997215d785095e0b3d80302 --- /dev/null +++ b/spec/javascripts/app/views/contact_view_spec.js @@ -0,0 +1,136 @@ +describe("app.views.Contact", function(){ + beforeEach(function() { + this.aspect1 = factory.aspect({id: 1}); + this.aspect2 = factory.aspect({id: 2}); + + this.model = new app.models.Contact({ + person_id: 42, + person: { id: 42, name: 'alice' }, + aspect_memberships: [{id: 23, aspect: this.aspect1}] + }); + this.view = new app.views.Contact({ model: this.model }); + Diaspora.I18n.load({ + contacts: { + add_contact: "Add contact", + remove_contact: "Remove contact", + error_add: "Couldn't add <%= name %> to the aspect :(", + error_remove: "Couldn't remove <%= name %> from the aspect :(" + } + }); + }); + + context("#presenter", function() { + it("contains necessary elements", function() { + app.aspect = this.aspect1; + expect(this.view.presenter()).toEqual(jasmine.objectContaining({ + person_id: 42, + person: jasmine.objectContaining({id: 42, name: 'alice'}), + in_aspect: 'in_aspect' + })); + }); + }); + + context('add contact to aspect', function() { + beforeEach(function() { + app.aspect = this.aspect2; + this.view.render(); + this.button = this.view.$el.find('.contact_add-to-aspect'); + this.contact = this.view.$el.find('.stream_element.contact'); + this.aspect_membership = {id: 42, aspect: app.aspect.toJSON()}; + this.response = JSON.stringify(this.aspect_membership); + }); + + it('sends a correct ajax request', function() { + this.button.trigger('click'); + var obj = $.parseJSON(jasmine.Ajax.requests.mostRecent().params); + expect(obj.person_id).toBe(this.model.get('person_id')); + expect(obj.aspect_id).toBe(app.aspect.get('id')); + }); + + it('adds a aspect_membership to the contact', function() { + expect(this.model.aspect_memberships.length).toBe(1); + $('.contact_add-to-aspect',this.contact).trigger('click'); + jasmine.Ajax.requests.mostRecent().response({ + status: 200, // success + responseText: this.response + }); + expect(this.model.aspect_memberships.length).toBe(2); + }); + + it('calls render', function() { + spyOn(this.view, 'render'); + $('.contact_add-to-aspect',this.contact).trigger('click'); + jasmine.Ajax.requests.mostRecent().response({ + status: 200, // success + responseText: this.response + }); + expect(this.view.render).toHaveBeenCalled(); + }); + + + it('displays a flash message on errors', function(){ + $('.contact_add-to-aspect',this.contact).trigger('click'); + jasmine.Ajax.requests.mostRecent().response({ + status: 400, // fail + }); + expect($('[id^="flash"]')).toBeErrorFlashMessage( + Diaspora.I18n.t( + 'contacts.error_add', + {name: this.model.get('person').name} + ) + ); + }); + }); + + context('remove contact from aspect', function() { + beforeEach(function() { + app.aspect = this.aspect1; + this.view.render(); + this.button = this.view.$el.find('.contact_remove-from-aspect'); + this.contact = this.view.$el.find('.stream_element.contact'); + this.aspect_membership = this.model.aspect_memberships.first().toJSON(); + this.response = JSON.stringify(this.aspect_membership); + }); + + it('sends a correct ajax request', function() { + $('.contact_remove-from-aspect',this.contact).trigger('click'); + expect(jasmine.Ajax.requests.mostRecent().url).toBe( + "/aspect_memberships/"+this.aspect_membership.id + ); + }); + + it('removes the aspect_membership from the contact', function() { + expect(this.model.aspect_memberships.length).toBe(1); + $('.contact_remove-from-aspect',this.contact).trigger('click'); + jasmine.Ajax.requests.mostRecent().response({ + status: 200, // success + responseText: this.response + }); + expect(this.model.aspect_memberships.length).toBe(0); + }); + + it('calls render', function() { + spyOn(this.view, 'render'); + $('.contact_remove-from-aspect',this.contact).trigger('click'); + jasmine.Ajax.requests.mostRecent().response({ + status: 200, // success + responseText: this.response, + }); + expect(this.view.render).toHaveBeenCalled(); + }); + + it('displays a flash message on errors', function(){ + $('.contact_remove-from-aspect',this.contact).trigger('click'); + jasmine.Ajax.requests.mostRecent().response({ + status: 400, // fail + }); + expect($('[id^="flash"]')).toBeErrorFlashMessage( + Diaspora.I18n.t( + 'contacts.error_remove', + {name: this.model.get('person').name} + ) + ); + }); + }); + +}); diff --git a/spec/javascripts/app/views/contacts_view_spec.js b/spec/javascripts/app/views/contacts_view_spec.js deleted file mode 100644 index b3a9f736cb862348941e88fd2840bd59bca42dc4..0000000000000000000000000000000000000000 --- a/spec/javascripts/app/views/contacts_view_spec.js +++ /dev/null @@ -1,240 +0,0 @@ -describe("app.views.Contacts", function(){ - beforeEach(function() { - spec.loadFixture("aspects_manage"); - this.view = new app.views.Contacts(); - Diaspora.I18n.load({ - contacts: { - add_contact: "Add contact", - aspect_list_is_visible: "Contacts in this aspect are able to see each other.", - aspect_list_is_not_visible: "Contacts in this aspect are not able to see each other.", - aspect_chat_is_enabled: "Contacts in this aspect are able to chat with you.", - aspect_chat_is_not_enabled: "Contacts in this aspect are not able to chat with you.", - remove_contact: "Remove contact", - error_add: "Couldn't add <%= name %> to the aspect :(", - error_remove: "Couldn't remove <%= name %> from the aspect :(" - } - }); - }); - - context('toggle chat privilege', function() { - beforeEach(function() { - this.chat_toggle = $("#chat_privilege_toggle"); - this.chat_icon = $("#chat_privilege_toggle .entypo"); - }); - - it('updates the title for the tooltip', function() { - expect(this.chat_icon.attr('data-original-title')).toBe( - Diaspora.I18n.t("contacts.aspect_chat_is_not_enabled") - ); - this.chat_toggle.trigger('click'); - expect(this.chat_icon.attr('data-original-title')).toBe( - Diaspora.I18n.t("contacts.aspect_chat_is_enabled") - ); - }); - - it('toggles the chat icon', function() { - expect(this.chat_icon.hasClass('enabled')).toBeFalsy; - this.chat_toggle.trigger('click'); - expect(this.chat_icon.hasClass('enabled')).toBeTruethy; - }); - }); - - context('toggle contacts visibility', function() { - beforeEach(function() { - this.visibility_toggle = $("#contacts_visibility_toggle"); - this.lock_icon = $("#contacts_visibility_toggle .entypo"); - }); - - it('updates the title for the tooltip', function() { - expect(this.lock_icon.attr('data-original-title')).toBe( - Diaspora.I18n.t("contacts.aspect_list_is_visible") - ); - - this.visibility_toggle.trigger('click'); - - expect(this.lock_icon.attr('data-original-title')).toBe( - Diaspora.I18n.t("contacts.aspect_list_is_not_visible") - ); - }); - - it('toggles the lock icon', function() { - expect(this.lock_icon.hasClass('lock-open')).toBeTruethy; - expect(this.lock_icon.hasClass('lock')).toBeFalsy; - - this.visibility_toggle.trigger('click'); - - expect(this.lock_icon.hasClass('lock')).toBeTruethy; - expect(this.lock_icon.hasClass('lock-open')).toBeFalsy; - }); - }); - - context('show aspect name form', function() { - beforeEach(function() { - this.button = $('#change_aspect_name'); - }); - - it('shows the form', function() { - expect($('#aspect_name_form').css('display')).toBe('none'); - this.button.trigger('click'); - expect($('#aspect_name_form').css('display')).not.toBe('none'); - }); - - it('hides the aspect name', function() { - expect($('.header > h3').css('display')).not.toBe('none'); - this.button.trigger('click'); - expect($('.header > h3').css('display')).toBe('none'); - }); - }); - - context('add contact to aspect', function() { - beforeEach(function() { - this.contact = $('#people_stream .stream_element').last(); - this.button = this.contact.find('.contact_add-to-aspect'); - this.person_id = this.button.attr('data-person_id'); - this.aspect_id = this.button.attr('data-aspect_id'); - }); - - it('sends a correct ajax request', function() { - jasmine.Ajax.install(); - $('.contact_add-to-aspect',this.contact).trigger('click'); - var obj = $.parseJSON(jasmine.Ajax.requests.mostRecent().params); - expect(obj.person_id).toBe(this.person_id); - expect(obj.aspect_id).toBe(this.aspect_id); - }); - - it('adds a membership id to the contact', function() { - jasmine.Ajax.install(); - $('.contact_add-to-aspect',this.contact).trigger('click'); - jasmine.Ajax.requests.mostRecent().response({ - status: 200, // success - responseText: '{ "id": 42 }' - }); - expect(this.button.attr('data-membership_id')).toBe('42'); - }); - - it('displays a flash message on errors', function(){ - jasmine.Ajax.install(); - $('.contact_add-to-aspect',this.contact).trigger('click'); - jasmine.Ajax.requests.mostRecent().response({ - status: 400, // fail - }); - expect($('[id^="flash"]')).toBeErrorFlashMessage( - Diaspora.I18n.t( - 'contacts.error_add', - {name: this.contact.find('.name').text()} - ) - ); - }); - - it('changes the appearance of the contact', function() { - expect(this.button.hasClass('contact_add-to-aspect')).toBeTruethy; - expect(this.button.hasClass('circled-cross')).toBeTruethy; - expect(this.contact.hasClass('in_aspect')).toBeTruethy; - expect(this.button.hasClass('contact_remove-from-aspect')).toBeFalsy; - expect(this.button.hasClass('circled-plus')).toBeFalsy; - expect(this.button.attr('data-original-title')).toBe( - Diaspora.I18n.t('contacts.add_contact') - ); - jasmine.Ajax.install(); - $('.contact_add-to-aspect',this.contact).trigger('click'); - jasmine.Ajax.requests.mostRecent().response({ - status: 200, // success - responseText: '{ "id": 42 }' - }); - expect(this.button.hasClass('contact_add-to-aspect')).toBeFalsy; - expect(this.button.hasClass('circled-cross')).toBeFalsy; - expect(this.contact.hasClass('in_aspect')).toBeFalsy; - expect(this.button.hasClass('contact_remove-from-aspect')).toBeTruethy; - expect(this.button.hasClass('circled-plus')).toBeTruethy; - expect(this.button.attr('data-original-title')).toBe( - Diaspora.I18n.t('contacts.remove_contact') - ); - }); - }); - - context('remove contact from aspect', function() { - beforeEach(function() { - this.contact = $('#people_stream .stream_element').first(); - this.button = this.contact.find('.contact_remove-from-aspect'); - this.person_id = this.button.attr('data-person_id'); - this.aspect_id = this.button.attr('data-aspect_id'); - this.membership_id = this.button.attr('data-membership_id'); - - }); - - it('sends a correct ajax request', function() { - jasmine.Ajax.install(); - $('.contact_remove-from-aspect',this.contact).trigger('click'); - expect(jasmine.Ajax.requests.mostRecent().url).toBe( - "/aspect_memberships/"+this.membership_id - ); - }); - - it('removes the membership id from the contact', function() { - jasmine.Ajax.install(); - $('.contact_remove-from-aspect',this.contact).trigger('click'); - jasmine.Ajax.requests.mostRecent().response({ - status: 200, // success - responseText: '{}' - }); - expect(this.button.attr('data-membership_id')).toBe(undefined); - }); - - it('displays a flash message on errors', function(){ - jasmine.Ajax.install(); - $('.contact_remove-from-aspect',this.contact).trigger('click'); - jasmine.Ajax.requests.mostRecent().response({ - status: 400, // fail - }); - expect($('[id^="flash"]')).toBeErrorFlashMessage( - Diaspora.I18n.t( - 'contacts.error_remove', - {name: this.contact.find('.name').text()} - ) - ); - }); - - it('changes the appearance of the contact', function() { - expect(this.button.hasClass('contact_add-to-aspect')).toBeFalsy; - expect(this.button.hasClass('circled-cross')).toBeFalsy; - expect(this.contact.hasClass('in_aspect')).toBeFalsy; - expect(this.button.hasClass('contact_remove-from-aspect')).toBeTruethy; - expect(this.button.hasClass('circled-plus')).toBeTruethy; - expect(this.button.attr('data-original-title')).toBe( - Diaspora.I18n.t('contacts.remove_contact') - ); - - jasmine.Ajax.install(); - $('.contact_remove-from-aspect',this.contact).trigger('click'); - jasmine.Ajax.requests.mostRecent().response({ - status: 200, // success - responseText: '{}' - }); - - expect(this.button.hasClass('contact_add-to-aspect')).toBeTruethy; - expect(this.button.hasClass('circled-cross')).toBeTruethy; - expect(this.contact.hasClass('in_aspect')).toBeTruethy; - expect(this.button.hasClass('contact_remove-from-aspect')).toBeFalsy; - expect(this.button.hasClass('circled-plus')).toBeFalsy; - expect(this.button.attr('data-original-title')).toBe( - Diaspora.I18n.t('contacts.add_contact') - ); - }); - }); - - context('search contact list', function() { - beforeEach(function() { - this.searchinput = $('#contact_list_search'); - this.username = $('.stream_element .name').first().text(); - }); - - it('filters the contact list by name', function() { - expect($('.stream_element').length).toBeGreaterThan(1); - this.searchinput.val(this.username); - this.searchinput.trigger('keyup'); - expect($('.stream_element:visible').length).toBe(1); - expect($('.stream_element:visible .name').first().text()).toBe(this.username); - }); - }); - -}); diff --git a/spec/javascripts/app/views/notifications_view_spec.js b/spec/javascripts/app/views/notifications_view_spec.js index 53c5640fcceb8f6f1d9c09eb669efcf6331749f9..12034e4a6d1cfba93599f0371b55df8d6178e62a 100644 --- a/spec/javascripts/app/views/notifications_view_spec.js +++ b/spec/javascripts/app/views/notifications_view_spec.js @@ -63,13 +63,13 @@ describe("app.views.Notifications", function(){ it('toggles the unread class and changes the title', function() { this.view.updateView(this.readN.data('guid'), this.readN.data('type'), true); - expect(this.readN.hasClass('unread')).toBeTruethy; - expect(this.readN.hasClass('read')).toBeFalsy; + expect(this.readN.hasClass('unread')).toBeTruthy(); + expect(this.readN.hasClass('read')).toBeFalsy(); expect(this.readN.find('.unread-toggle .entypo').data('original-title')).toBe(Diaspora.I18n.t('notifications.mark_read')); this.view.updateView(this.readN.data('guid'), this.readN.data('type'), false); - expect(this.readN.hasClass('read')).toBeTruethy; - expect(this.readN.hasClass('unread')).toBeFalsy; + expect(this.readN.hasClass('read')).toBeTruthy(); + expect(this.readN.hasClass('unread')).toBeFalsy(); expect(this.readN.find('.unread-toggle .entypo').data('original-title')).toBe(Diaspora.I18n.t('notifications.mark_unread')); }); }); diff --git a/spec/presenters/aspect_membership_presenter_spec.rb b/spec/presenters/aspect_membership_presenter_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..86d25de66e8a6999db32e201105e942e54d14da7 --- /dev/null +++ b/spec/presenters/aspect_membership_presenter_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe AspectMembershipPresenter do + before do + @am = alice.aspects.where(:name => "generic").first.aspect_memberships.first + @presenter = AspectMembershipPresenter.new(@am) + end + + describe '#base_hash' do + it 'works' do + expect(@presenter.base_hash).to be_present + end + end + +end diff --git a/spec/presenters/contact_presenter_spec.rb b/spec/presenters/contact_presenter_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2a1e0cefae6e95783f2a189c1959452d442fca59 --- /dev/null +++ b/spec/presenters/contact_presenter_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe ContactPresenter do + before do + @presenter = ContactPresenter.new(alice.contact_for(bob.person)) + end + + describe '#base_hash' do + it 'works' do + expect(@presenter.base_hash).to be_present + end + end + + describe '#full_hash' do + it 'works' do + expect(@presenter.full_hash).to be_present + end + end + + describe '#full_hash_with_person' do + it 'works' do + expect(@presenter.full_hash_with_person).to be_present + end + end + +end