From 9de6a26a221d1f874e1e036a87512484368a6313 Mon Sep 17 00:00:00 2001 From: Steffen van Bergerem <svbergerem@online.de> Date: Fri, 12 Dec 2014 00:00:08 +0100 Subject: [PATCH] Port contacts page to backbonejs --- .../app/collections/aspect_memberships.js | 6 + .../javascripts/app/collections/contacts.js | 21 ++ .../app/helpers/handlebars-helpers.js | 26 +- app/assets/javascripts/app/models/contact.js | 13 + .../contacts_view.js => pages/contacts.js} | 69 +---- app/assets/javascripts/app/router.js | 10 +- .../app/views/contact_stream_view.js | 77 ++++++ .../javascripts/app/views/contact_view.js | 72 ++++++ app/assets/stylesheets/contacts.css.scss | 4 +- app/assets/templates/comment_tpl.jst.hbs | 4 +- app/assets/templates/contact_tpl.jst.hbs | 23 ++ .../single-post-content_tpl.jst.hbs | 24 +- .../single-post-interactions_tpl.jst.hbs | 8 +- app/assets/templates/stream-faces_tpl.jst.hbs | 4 +- app/assets/templates/stream-frame_tpl.jst.hbs | 8 +- .../aspect_memberships_controller.rb | 9 +- app/controllers/contacts_controller.rb | 22 +- app/helpers/contacts_helper.rb | 24 +- app/presenters/aspect_membership_presenter.rb | 11 + app/presenters/contact_presenter.rb | 17 ++ app/views/contacts/_contact.html.haml | 12 - app/views/contacts/index.html.haml | 5 +- config/locales/javascript/javascript.en.yml | 1 + .../aspect_memberships_controller_spec.rb | 4 +- .../jasmine_fixtures/contacts_spec.rb | 10 +- .../collections/contacts_collection_spec.js | 37 +++ spec/javascripts/app/models/contact_spec.js | 20 ++ spec/javascripts/app/pages/contacts_spec.js | 101 ++++++++ .../app/views/contact_stream_view_spec.js | 77 ++++++ .../app/views/contact_view_spec.js | 136 ++++++++++ .../app/views/contacts_view_spec.js | 240 ------------------ .../aspect_membership_presenter_spec.rb | 15 ++ spec/presenters/contact_presenter_spec.rb | 26 ++ 33 files changed, 746 insertions(+), 390 deletions(-) create mode 100644 app/assets/javascripts/app/collections/aspect_memberships.js create mode 100644 app/assets/javascripts/app/collections/contacts.js create mode 100644 app/assets/javascripts/app/models/contact.js rename app/assets/javascripts/app/{views/contacts_view.js => pages/contacts.js} (50%) create mode 100644 app/assets/javascripts/app/views/contact_stream_view.js create mode 100644 app/assets/javascripts/app/views/contact_view.js create mode 100644 app/assets/templates/contact_tpl.jst.hbs create mode 100644 app/presenters/aspect_membership_presenter.rb create mode 100644 app/presenters/contact_presenter.rb delete mode 100644 app/views/contacts/_contact.html.haml create mode 100644 spec/javascripts/app/collections/contacts_collection_spec.js create mode 100644 spec/javascripts/app/models/contact_spec.js create mode 100644 spec/javascripts/app/pages/contacts_spec.js create mode 100644 spec/javascripts/app/views/contact_stream_view_spec.js create mode 100644 spec/javascripts/app/views/contact_view_spec.js delete mode 100644 spec/javascripts/app/views/contacts_view_spec.js create mode 100644 spec/presenters/aspect_membership_presenter_spec.rb create mode 100644 spec/presenters/contact_presenter_spec.rb 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 0000000000..dd8ee1b5d2 --- /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 0000000000..d0592155f2 --- /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 00c78ffaff..17f42e3c2a 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 0000000000..a1e014c4c1 --- /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 970719247e..ac44783944 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 c71222ff8b..76398707cd 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 0000000000..ad93301bd8 --- /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 0000000000..4fd2d7efb5 --- /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 5bf2597a63..b2eae53054 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 23eb368d9f..3196cad332 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 0000000000..a89542f4ef --- /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 a144e03902..e804b3c956 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 aebe7a6801..2488ea0de6 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 6db001f192..2e9898905c 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 5574a479de..a9f55163c1 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 5009902b92..eddfa39359 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 9a57ba3e43..0e6168c9c6 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 5ea96b5a26..3fdee52f35 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 0000000000..28a96e38f2 --- /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 0000000000..9ab8b2d267 --- /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 3e6aeb8a40..0000000000 --- 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 4d8f223b4f..62161cbfad 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 78fdc4533b..77fbeb3d14 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 0a5e125e90..abdae8de8a 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 f21500def4..6311c3b343 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 0000000000..3b64d4bd97 --- /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 0000000000..265039792c --- /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)).toBeTruethy; + }); + + 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 0000000000..6aa1975bf2 --- /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')).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('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 0000000000..955dd2e7b1 --- /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 0000000000..0dee502e93 --- /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 b3a9f736cb..0000000000 --- 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/presenters/aspect_membership_presenter_spec.rb b/spec/presenters/aspect_membership_presenter_spec.rb new file mode 100644 index 0000000000..86d25de66e --- /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 0000000000..2a1e0cefae --- /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 -- GitLab