Merge pull request #4861 from JannikStreek/feature/3676-conduct_polls
Feature/3676 Conduct Polls
This commit is contained in:
commit
faad980df0
45 changed files with 1074 additions and 30 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -65,3 +65,6 @@ dump.rdb
|
|||
|
||||
#Rubinius's JIT
|
||||
*.rbc
|
||||
|
||||
#IDE
|
||||
diaspora.iml
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
* Added comment count to statistic to enable calculations of posts/comments ratios [#4799](https://github.com/diaspora/diaspora/pull/4799)
|
||||
* Add filters to notifications controller [#4814](https://github.com/diaspora/diaspora/pull/4814)
|
||||
* Activate hovercards in SPV and conversations [#4870](https://github.com/diaspora/diaspora/pull/4870)
|
||||
* Added possibility to conduct polls [#4861](https://github.com/diaspora/diaspora/pull/4861)
|
||||
|
||||
# 0.3.0.3
|
||||
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
## Bug fixes
|
||||
* Fix regression caused by using after_commit with nested '#save' which lead to an infinite recursion [#4715](https://github.com/diaspora/diaspora/issues/4715)
|
||||
* Save textarea value before rendering comments when clicked 'show more...' [#4514](https://github.com/diaspora/diaspora/issues/4514)
|
||||
|
||||
# 0.3.0.0
|
||||
|
||||
## Pod statistics
|
||||
|
|
|
|||
5
app/assets/javascripts/app/models/poll_participation.js
Normal file
5
app/assets/javascripts/app/models/poll_participation.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
app.models.PollParticipation = Backbone.Model.extend({
|
||||
url : function(){
|
||||
"/poll_participations"
|
||||
}
|
||||
});
|
||||
|
|
@ -13,7 +13,8 @@ app.models.StatusMessage = app.models.Post.extend({
|
|||
status_message : _.clone(this.attributes),
|
||||
aspect_ids : this.get("aspect_ids"),
|
||||
photos : this.photos && this.photos.pluck("id"),
|
||||
services : this.get("services")
|
||||
services : this.get("services"),
|
||||
poll : this.get("poll")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
94
app/assets/javascripts/app/views/poll_view.js
Normal file
94
app/assets/javascripts/app/views/poll_view.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
app.views.Poll = app.views.Base.extend({
|
||||
templateName : "poll",
|
||||
|
||||
events : {
|
||||
"click .submit" : "vote",
|
||||
"click .toggle_result" : "toggleResult"
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
this.poll = this.model.attributes.poll;
|
||||
this.progressBarFactor = 3;
|
||||
this.toggleMode = 0;
|
||||
},
|
||||
|
||||
postRenderTemplate : function() {
|
||||
if(this.poll) {
|
||||
this.setProgressBar();
|
||||
}
|
||||
},
|
||||
|
||||
removeForm : function() {
|
||||
var cnt = this.$("form").contents();
|
||||
this.$("form").replaceWith(cnt);
|
||||
this.$('input').remove();
|
||||
this.$('submit').remove();
|
||||
this.$('.toggle_result_wrapper').remove();
|
||||
},
|
||||
|
||||
setProgressBar : function() {
|
||||
var answers = this.poll.poll_answers;
|
||||
for(index = 0; index < answers.length; ++index) {
|
||||
var percentage = 0;
|
||||
if(this.poll.participation_count != 0) {
|
||||
percentage = Math.round(answers[index].vote_count / this.poll.participation_count * 100);
|
||||
}
|
||||
var progressBar = this.$(".poll_progress_bar[data-answerid="+answers[index].id+"]");
|
||||
progressBar.parent().next().html(" - " + percentage + "%");
|
||||
var width = percentage * this.progressBarFactor;
|
||||
progressBar.css("width", width + "px");
|
||||
}
|
||||
},
|
||||
|
||||
toggleResult : function(e) {
|
||||
this.$('.poll_progress_bar_wrapper').toggle();
|
||||
this.$('.percentage').toggle();
|
||||
if(this.toggleMode == 0) {
|
||||
this.$('.toggle_result').html(Diaspora.I18n.t("poll.close_result"));
|
||||
this.toggleMode = 1;
|
||||
}else{
|
||||
this.$('.toggle_result').html(Diaspora.I18n.t("poll.show_result"));
|
||||
this.toggleMode = 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
refreshResult : function(answerId) {
|
||||
this.updateCounter(answerId);
|
||||
this.setProgressBar();
|
||||
},
|
||||
|
||||
updateCounter : function(answerId) {
|
||||
this.poll.participation_count++;
|
||||
this.$('.poll_statistic').html(Diaspora.I18n.t("poll.count", {"count" : this.poll.participation_count}));
|
||||
var answers = this.poll.poll_answers;
|
||||
for(index = 0; index < answers.length; ++index) {
|
||||
if(answers[index].id == answerId) {
|
||||
answers[index].vote_count++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
vote : function(evt){
|
||||
var result = parseInt($(evt.target).parent().find("input[name=vote]:checked").val());
|
||||
var pollParticipation = new app.models.PollParticipation();
|
||||
var parent = this;
|
||||
pollParticipation.save({
|
||||
"poll_answer_id" : result,
|
||||
"poll_id" : this.poll.poll_id
|
||||
},{
|
||||
url : "/posts/"+this.poll.post_id+"/poll_participations",
|
||||
success : function(model, response) {
|
||||
parent.removeForm();
|
||||
parent.refreshResult(result);
|
||||
if(parent.toggleMode == 0) {
|
||||
parent.toggleResult(null);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -23,12 +23,16 @@ app.views.Publisher = Backbone.View.extend({
|
|||
"click .post_preview_button" : "createPostPreview",
|
||||
"textchange #status_message_fake_text": "handleTextchange",
|
||||
"click #locator" : "showLocation",
|
||||
"click #poll_creator" : "showPollCreator",
|
||||
"click #add_poll_answer" : "addPollAnswer",
|
||||
"click .remove_poll_answer" : "removePollAnswer",
|
||||
"click #hide_location" : "destroyLocation",
|
||||
"keypress #location_address" : "avoidEnter"
|
||||
},
|
||||
|
||||
initialize : function(opts){
|
||||
this.standalone = opts ? opts.standalone : false;
|
||||
this.option_counter = 1;
|
||||
|
||||
// init shortcut references to the various elements
|
||||
this.el_input = this.$('#status_message_fake_text');
|
||||
|
|
@ -37,6 +41,8 @@ app.views.Publisher = Backbone.View.extend({
|
|||
this.el_submit = this.$('input[type=submit], button#submit');
|
||||
this.el_preview = this.$('button.post_preview_button');
|
||||
this.el_photozone = this.$('#photodropzone');
|
||||
this.el_poll_creator = this.$('#poll_creator_wrapper');
|
||||
this.el_poll_answer = this.$('#poll_creator_wrapper .poll_answer');
|
||||
|
||||
// init mentions plugin
|
||||
Mentions.initialize(this.el_input);
|
||||
|
|
@ -69,7 +75,7 @@ app.views.Publisher = Backbone.View.extend({
|
|||
});
|
||||
|
||||
this.initSubviews();
|
||||
|
||||
this.addPollAnswer();
|
||||
return this;
|
||||
},
|
||||
|
||||
|
|
@ -136,7 +142,9 @@ app.views.Publisher = Backbone.View.extend({
|
|||
"photos" : serializedForm["photos[]"],
|
||||
"services" : serializedForm["services[]"],
|
||||
"location_address" : $("#location_address").val(),
|
||||
"location_coords" : serializedForm["location[coords]"]
|
||||
"location_coords" : serializedForm["location[coords]"],
|
||||
"poll_question" : serializedForm["poll_question"],
|
||||
"poll_answers" : serializedForm["poll_answers[]"]
|
||||
}, {
|
||||
url : "/status_messages",
|
||||
success : function() {
|
||||
|
|
@ -171,6 +179,36 @@ app.views.Publisher = Backbone.View.extend({
|
|||
}
|
||||
},
|
||||
|
||||
showPollCreator: function(){
|
||||
this.el_poll_creator.toggle();
|
||||
},
|
||||
|
||||
addPollAnswer: function(){
|
||||
if($(".poll_answer").size() == 1) {
|
||||
$(".remove_poll_answer").css("visibility","visible");
|
||||
}
|
||||
|
||||
this.option_counter++;
|
||||
var clone = this.el_poll_answer.clone();
|
||||
|
||||
var answer = clone.find('.poll_answer_input');
|
||||
answer.val("");
|
||||
|
||||
var placeholder = answer.attr("placeholder");
|
||||
var expression = /[^0-9]+/;
|
||||
answer.attr("placeholder", expression.exec(placeholder) + this.option_counter);
|
||||
|
||||
$('#poll_creator_wrapper .poll_answer').last().after(clone);
|
||||
},
|
||||
|
||||
removePollAnswer: function(evt){
|
||||
$(evt.currentTarget).parent().remove();
|
||||
if($(".poll_answer").size() == 1) {
|
||||
$(".remove_poll_answer").css("visibility","hidden");;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
// avoid submitting form when pressing Enter key
|
||||
avoidEnter: function(evt){
|
||||
if(evt.keyCode == 13)
|
||||
|
|
@ -295,6 +333,9 @@ app.views.Publisher = Backbone.View.extend({
|
|||
// clear location
|
||||
this.destroyLocation();
|
||||
|
||||
// clear poll form
|
||||
this.clearPollForm();
|
||||
|
||||
// force textchange plugin to update lastValue
|
||||
this.el_input.data('lastValue', '');
|
||||
this.el_hiddenInput.data('lastValue', '');
|
||||
|
|
@ -302,6 +343,11 @@ app.views.Publisher = Backbone.View.extend({
|
|||
return this;
|
||||
},
|
||||
|
||||
clearPollForm : function(){
|
||||
this.$('#poll_question').val('');
|
||||
this.$('.poll_answer_input').val('');
|
||||
},
|
||||
|
||||
tryClose : function(){
|
||||
// if it is not submittable, close it.
|
||||
if( !this._submittable() ){
|
||||
|
|
@ -323,7 +369,7 @@ app.views.Publisher = Backbone.View.extend({
|
|||
$(this.el).addClass("closed");
|
||||
this.el_wrapper.removeClass("active");
|
||||
this.el_input.css('height', '');
|
||||
|
||||
this.el_poll_creator.hide();
|
||||
return this;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ app.views.StreamPost = app.views.Post.extend({
|
|||
".post-content" : "postContentView",
|
||||
".oembed" : "oEmbedView",
|
||||
".opengraph" : "openGraphView",
|
||||
".poll" : "pollView",
|
||||
".status-message-location" : "postLocationStreamView"
|
||||
},
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ app.views.StreamPost = app.views.Post.extend({
|
|||
this.commentStreamView = new app.views.CommentStream({model : this.model});
|
||||
this.oEmbedView = new app.views.OEmbed({model : this.model});
|
||||
this.openGraphView = new app.views.OpenGraph({model : this.model});
|
||||
this.pollView = new app.views.Poll({model : this.model});
|
||||
},
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
@import 'header'
|
||||
@import 'footer'
|
||||
@import 'opengraph'
|
||||
@import 'poll'
|
||||
@import 'help'
|
||||
@import 'profile'
|
||||
@import 'publisher_blueprint'
|
||||
|
|
|
|||
45
app/assets/stylesheets/poll.css.scss
Normal file
45
app/assets/stylesheets/poll.css.scss
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
.poll_form {
|
||||
display: block;
|
||||
margin: 10px 0px 10px 0px;
|
||||
border-top: solid 1px $border-grey;
|
||||
border-bottom: solid 1px $border-grey;
|
||||
padding: 10px 0px 5px 0px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.poll_form input[type="radio"] {
|
||||
display:inline !important;
|
||||
}
|
||||
|
||||
.poll_result {
|
||||
width:100%px;
|
||||
}
|
||||
|
||||
.poll_progress_bar {
|
||||
position:absolute;
|
||||
width:0px;
|
||||
height:15px;
|
||||
top:-12px;
|
||||
z-index:-1;
|
||||
background-color:$background-grey;
|
||||
}
|
||||
|
||||
.poll_statistic{
|
||||
float:right;
|
||||
}
|
||||
|
||||
.poll_progress_bar_wrapper {
|
||||
position: relative;
|
||||
width: 0;
|
||||
height: 0;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.poll_answer_entry{
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.percentage {
|
||||
display:inline;
|
||||
}
|
||||
|
|
@ -82,6 +82,16 @@
|
|||
&.with_attachments .row-fluid#photodropzone_container {
|
||||
border-top: 1px dashed $border-grey;
|
||||
}
|
||||
|
||||
#poll_creator_wrapper {
|
||||
display:none;
|
||||
border-top: 1px dashed $border-grey;
|
||||
padding:4px 6px 4px 6px;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
&.with_location .row-fluid#location_container {
|
||||
height: 30px;
|
||||
#hide_location { display: none !important; }
|
||||
|
|
@ -162,6 +172,7 @@
|
|||
margin-right: 5px;
|
||||
#file-upload,
|
||||
#locator,
|
||||
#poll_creator,
|
||||
#hide_location {
|
||||
text-decoration: none !important;
|
||||
font-size: 16px;
|
||||
|
|
|
|||
|
|
@ -309,8 +309,75 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
#poll_creator {
|
||||
bottom: 1px !important;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
position: absolute !important;
|
||||
right: 55px;
|
||||
i {
|
||||
@include opacity(0.4);
|
||||
}
|
||||
&:hover {
|
||||
color: $text-dark-grey;
|
||||
cursor: pointer;
|
||||
i {
|
||||
@include opacity(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 19px;
|
||||
width: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
#poll_creator_wrapper {
|
||||
display:none;
|
||||
border: 1px solid $border-dark-grey;
|
||||
padding:5px;
|
||||
margin-top:1em;
|
||||
@include border-radius(2px);
|
||||
}
|
||||
|
||||
.remove_poll_answer {
|
||||
visibility:hidden;
|
||||
float:right;
|
||||
display: table-cell;
|
||||
|
||||
.icons-deletelabel {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
margin-top:5px;
|
||||
}
|
||||
}
|
||||
|
||||
.poll_answer_input {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
#add_poll_answer_wrapper {
|
||||
padding:5px 0 5px 0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
#poll_question_wrapper {
|
||||
}
|
||||
|
||||
#poll_question {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
.poll_answer {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.poll_answer_input_wrapper {
|
||||
display: table-cell;
|
||||
}
|
||||
32
app/assets/templates/poll_tpl.jst.hbs
Normal file
32
app/assets/templates/poll_tpl.jst.hbs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{{#if poll}}
|
||||
<div class="poll_form">
|
||||
<p class="poll_statistic">{{t "poll.count" count=poll.participation_count}}</p>
|
||||
<strong>{{poll.question}}</strong><br/>
|
||||
{{#unless already_participated_in_poll}}
|
||||
<form action="/posts/{{poll.post_id}}/poll_participations" method="POST">
|
||||
{{#poll.poll_answers}}
|
||||
<input type="radio" name="vote" value="{{id}}"/>
|
||||
<div class="poll_progress_bar_wrapper" style="display:none;">
|
||||
<div class="poll_progress_bar" data-answerid="{{id}}"></div>
|
||||
</div>
|
||||
{{answer}}
|
||||
<p class="percentage" style="display:none;"></p>
|
||||
<br/>
|
||||
{{/poll.poll_answers}}
|
||||
<input type="submit" class="button submit" style="float:right;" value="{{t "poll.vote"}}"/>
|
||||
</form>
|
||||
<p class="toggle_result_wrapper">
|
||||
<br/><a href="#" class="toggle_result">{{t "poll.show_result"}}</a><br/>
|
||||
</p>
|
||||
{{else}}
|
||||
{{#poll.poll_answers}}
|
||||
<div class="poll_progress_bar_wrapper">
|
||||
<div class="poll_progress_bar" data-answerid="{{id}}"></div>
|
||||
</div>
|
||||
{{answer}}
|
||||
<p class="percentage"></p>
|
||||
<br/>
|
||||
{{/poll.poll_answers}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
@ -18,4 +18,5 @@
|
|||
{{{text}}}
|
||||
<div class="oembed"></div>
|
||||
<div class="opengraph"></div>
|
||||
<div class="poll"></div>
|
||||
</div>
|
||||
|
|
|
|||
28
app/controllers/poll_participations_controller.rb
Normal file
28
app/controllers/poll_participations_controller.rb
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
class PollParticipationsController < ApplicationController
|
||||
include ApplicationHelper
|
||||
before_filter :authenticate_user!
|
||||
|
||||
def create
|
||||
answer = PollAnswer.find(params[:poll_answer_id])
|
||||
poll_participation = current_user.participate_in_poll!(target, answer) if target
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.mobile { redirect_to stream_path }
|
||||
format.json { render json: poll_participation, :status => 201 }
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.mobile { redirect_to stream_path }
|
||||
format.json { render :nothing => true, :status => 403 }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def target
|
||||
@target ||= if params[:post_id]
|
||||
current_user.find_visible_shareable_by_id(Post, params[:post_id]) || raise(ActiveRecord::RecordNotFound.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -49,6 +49,14 @@ class StatusMessagesController < ApplicationController
|
|||
|
||||
@status_message = current_user.build_post(:status_message, params[:status_message])
|
||||
@status_message.build_location(:address => params[:location_address], :coordinates => params[:location_coords]) if params[:location_address].present?
|
||||
if params[:poll_question].present?
|
||||
@status_message.build_poll(:question => params[:poll_question])
|
||||
[*params[:poll_answers]].each do |poll_answer|
|
||||
@status_message.poll.poll_answers.build(:answer => poll_answer)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@status_message.attach_photos_by_ids(params[:photos])
|
||||
|
||||
if @status_message.save
|
||||
|
|
@ -78,7 +86,7 @@ class StatusMessagesController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.mobile { redirect_to stream_path }
|
||||
format.json { render :nothing => true , :status => 403 }
|
||||
format.json { render :nothing => true, :status => 403 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
40
app/models/poll.rb
Normal file
40
app/models/poll.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
class Poll < ActiveRecord::Base
|
||||
include Diaspora::Federated::Base
|
||||
include Diaspora::Guid
|
||||
attr_accessible :question, :poll_answers
|
||||
belongs_to :status_message
|
||||
has_many :poll_answers
|
||||
has_many :poll_participations
|
||||
|
||||
xml_attr :question
|
||||
xml_attr :poll_answers, :as => [PollAnswer]
|
||||
|
||||
#forward some requests to status message, because a poll is just attached to a status message and is not sharable itself
|
||||
delegate :author, :author_id, :public?, :subscribers, to: :status_message
|
||||
|
||||
validate :enough_poll_answers
|
||||
|
||||
self.include_root_in_json = false
|
||||
|
||||
def enough_poll_answers
|
||||
errors.add(:poll_answers, I18n.t("activerecord.errors.models.poll.attributes.poll_answers.not_enough_poll_answers")) if poll_answers.size < 2
|
||||
end
|
||||
|
||||
def as_json(options={})
|
||||
{
|
||||
:poll_id => self.id,
|
||||
:post_id => self.status_message.id,
|
||||
:question => self.question,
|
||||
:poll_answers => self.poll_answers,
|
||||
:participation_count => self.participation_count,
|
||||
}
|
||||
end
|
||||
|
||||
def participation_count
|
||||
poll_answers.sum("vote_count")
|
||||
end
|
||||
|
||||
def already_participated?(user)
|
||||
poll_participations.where(:author_id => user.person.id).present?
|
||||
end
|
||||
end
|
||||
13
app/models/poll_answer.rb
Normal file
13
app/models/poll_answer.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class PollAnswer < ActiveRecord::Base
|
||||
|
||||
include Diaspora::Federated::Base
|
||||
include Diaspora::Guid
|
||||
|
||||
belongs_to :poll
|
||||
has_many :poll_participations
|
||||
|
||||
xml_attr :answer
|
||||
|
||||
self.include_root_in_json = false
|
||||
|
||||
end
|
||||
66
app/models/poll_participation.rb
Normal file
66
app/models/poll_participation.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
class PollParticipation < ActiveRecord::Base
|
||||
|
||||
include Diaspora::Federated::Base
|
||||
|
||||
include Diaspora::Guid
|
||||
include Diaspora::Relayable
|
||||
belongs_to :poll
|
||||
belongs_to :poll_answer, counter_cache: :vote_count
|
||||
belongs_to :author, :class_name => 'Person', :foreign_key => :author_id
|
||||
xml_attr :diaspora_handle
|
||||
xml_attr :poll_answer_guid
|
||||
xml_convention :underscore
|
||||
validate :not_already_participated
|
||||
|
||||
def parent_class
|
||||
Poll
|
||||
end
|
||||
|
||||
def parent
|
||||
self.poll
|
||||
end
|
||||
|
||||
def poll_answer_guid
|
||||
poll_answer.guid
|
||||
end
|
||||
|
||||
def poll_answer_guid= new_poll_answer_guid
|
||||
self.poll_answer = PollAnswer.where(:guid => new_poll_answer_guid).first
|
||||
end
|
||||
|
||||
def parent= parent
|
||||
self.poll = parent
|
||||
end
|
||||
|
||||
def diaspora_handle
|
||||
self.author.diaspora_handle
|
||||
end
|
||||
|
||||
def diaspora_handle= nh
|
||||
self.author = Webfinger.new(nh).fetch
|
||||
end
|
||||
|
||||
def not_already_participated
|
||||
return if poll.nil?
|
||||
|
||||
other_participations = PollParticipation.where(author_id: self.author.id, poll_id: self.poll.id).to_a-[self]
|
||||
if other_participations.present?
|
||||
self.errors.add(:poll, I18n.t("activerecord.errors.models.poll_participations.attributes.poll.already_participated"))
|
||||
end
|
||||
end
|
||||
|
||||
class Generator < Federated::Generator
|
||||
def self.federated_class
|
||||
PollParticipation
|
||||
end
|
||||
|
||||
def initialize(person, target, poll_answer)
|
||||
@poll_answer = poll_answer
|
||||
super(person, target)
|
||||
end
|
||||
|
||||
def relayable_options
|
||||
{:poll => @target.poll, :poll_answer => @poll_answer}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -72,6 +72,9 @@ class Post < ActiveRecord::Base
|
|||
def address
|
||||
end
|
||||
|
||||
def poll
|
||||
end
|
||||
|
||||
def self.excluding_blocks(user)
|
||||
people = user.blocks.map{|b| b.person_id}
|
||||
scope = scoped
|
||||
|
|
|
|||
|
|
@ -20,10 +20,13 @@ class StatusMessage < Post
|
|||
xml_attr :raw_message
|
||||
xml_attr :photos, :as => [Photo]
|
||||
xml_attr :location, :as => Location
|
||||
xml_attr :poll, :as => Poll
|
||||
|
||||
has_many :photos, :dependent => :destroy, :foreign_key => :status_message_guid, :primary_key => :guid
|
||||
|
||||
has_one :location
|
||||
has_one :poll, autosave: true
|
||||
|
||||
|
||||
# a StatusMessage is federated before its photos are so presence_of_content() fails erroneously if no text is present
|
||||
# therefore, we put the validation in a before_destory callback instead of a validation
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ module User::SocialActions
|
|||
Like::Generator.new(self, target).create!(opts)
|
||||
end
|
||||
|
||||
def participate_in_poll!(target, answer, opts={})
|
||||
find_or_create_participation!(target)
|
||||
PollParticipation::Generator.new(self, target, answer).create!(opts)
|
||||
end
|
||||
|
||||
def reshare!(target, opts={})
|
||||
find_or_create_participation!(target)
|
||||
reshare = build_post(:reshare, :root_guid => target.guid)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ class PostPresenter
|
|||
:root => root,
|
||||
:title => title,
|
||||
:address => @post.address,
|
||||
:poll => @post.poll(),
|
||||
:already_participated_in_poll => already_participated_in_poll,
|
||||
|
||||
:interactions => {
|
||||
:likes => [user_like].compact,
|
||||
|
|
@ -72,6 +74,14 @@ class PostPresenter
|
|||
@current_user.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def already_participated_in_poll
|
||||
if @post.poll
|
||||
@post.poll.already_participated?(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class PostInteractionPresenter
|
||||
|
|
|
|||
|
|
@ -28,12 +28,26 @@
|
|||
%span#publisher-images
|
||||
%span.markdownIndications
|
||||
!= t('shared.publisher.formatWithMarkdown', markdown_link: link_to(t('help.markdown'), 'https://diasporafoundation.org/formatting', target: :blank))
|
||||
#poll_creator.btn{:title => t('shared.publisher.poll.add_a_poll')}
|
||||
%i.entypo.bar-graph{:class => "publisher_image"}
|
||||
|
||||
#locator.btn{:title => t('shared.publisher.get_location')}
|
||||
= image_tag 'icons/marker.png', :alt => t('shared.publisher.get_location').titleize, :class => 'publisher_image'
|
||||
#file-upload.btn{:title => t('shared.publisher.upload_photos')}
|
||||
= image_tag 'icons/camera.png', :alt => t('shared.publisher.upload_photos').titleize, :class => 'publisher_image'
|
||||
= hidden_field :location, :coords
|
||||
#location_container
|
||||
#poll_creator_wrapper
|
||||
#poll_question_wrapper
|
||||
%input{:id => 'poll_question', :placeholder => t('shared.publisher.poll.question'), :name => 'poll_question'}
|
||||
.poll_answer
|
||||
%span{:class => 'poll_answer_input_wrapper'}
|
||||
%input{:class => 'poll_answer_input', :placeholder => t('shared.publisher.poll.option'), :name => 'poll_answers[]'}
|
||||
%a{:class => 'remove_poll_answer', :title => t('shared.publisher.poll.remove_poll_answer')}
|
||||
.icons-deletelabel
|
||||
#add_poll_answer_wrapper
|
||||
#add_poll_answer{:class => 'button creation'}
|
||||
= t('shared.publisher.poll.add_poll_answer')
|
||||
|
||||
- if publisher_public
|
||||
= hidden_field_tag 'aspect_ids[]', "public"
|
||||
|
|
|
|||
|
|
@ -22,8 +22,20 @@
|
|||
%ul#photodropzone
|
||||
.row-fluid#location_container
|
||||
= hidden_field :location, :coords
|
||||
.row-fluid#poll_creator_wrapper
|
||||
#poll_question_wrapper{:class => "input-block-level"}
|
||||
%input{:id => 'poll_question', :placeholder => t('shared.publisher.poll.question'), :name => 'poll_question', :class=> "form-control"}
|
||||
.poll_answer
|
||||
%input{:class => 'form-control poll_answer_input', :placeholder => t('shared.publisher.poll.option'), :name => 'poll_answers[]'}
|
||||
.remove_poll_answer.btn.btn-link{:title => t('shared.publisher.poll.remove_poll_answer')}
|
||||
%i.entypo.trash
|
||||
#add_poll_answer_wrapper
|
||||
#add_poll_answer{:class => 'btn btn-default'}
|
||||
= t('shared.publisher.poll.add_poll_answer')
|
||||
.row-fluid#button_container
|
||||
#publisher-images.pull-right
|
||||
#poll_creator.btn.btn-link{:title => t('shared.publisher.poll.add_a_poll')}
|
||||
%i.entypo.bar-graph
|
||||
#file-upload.btn.btn-link{:title => t('shared.publisher.upload_photos')}
|
||||
%i.entypo.camera.publisher_image
|
||||
#locator.btn.btn-link{:title => t('shared.publisher.get_location')}
|
||||
|
|
|
|||
|
|
@ -26,3 +26,11 @@
|
|||
attributes:
|
||||
from_id:
|
||||
taken: "is a duplicate of a pre-existing request."
|
||||
poll:
|
||||
attributes:
|
||||
poll_answers:
|
||||
not_enough_poll_answers: "Not enough poll options provided."
|
||||
poll_participation:
|
||||
attributes:
|
||||
poll:
|
||||
already_participated: "You've already participated in this poll!"
|
||||
|
|
|
|||
|
|
@ -26,3 +26,11 @@
|
|||
attributes:
|
||||
from_id:
|
||||
taken: "is a duplicate of a pre-existing request."
|
||||
poll:
|
||||
attributes:
|
||||
poll_answers:
|
||||
not_enough_poll_answers: "Not enough poll options provided."
|
||||
poll_participation:
|
||||
attributes:
|
||||
poll:
|
||||
already_participated: "You've already participated in this poll!"
|
||||
|
|
|
|||
|
|
@ -26,4 +26,11 @@
|
|||
attributes:
|
||||
from_id:
|
||||
taken: "is a duplicate of a pre-existing request."
|
||||
|
||||
poll:
|
||||
attributes:
|
||||
poll_answers:
|
||||
not_enough_poll_answers: "Not enough poll options provided."
|
||||
poll_participation:
|
||||
attributes:
|
||||
poll:
|
||||
already_participated: "You've already participated in this poll!"
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@ en:
|
|||
attributes:
|
||||
root_guid:
|
||||
taken: "That good, huh? You've already reshared that post!"
|
||||
poll:
|
||||
attributes:
|
||||
poll_answers:
|
||||
not_enough_poll_answers: "Not enough poll options provided."
|
||||
poll_participation:
|
||||
attributes:
|
||||
poll:
|
||||
already_participated: "You've already participated in this poll!"
|
||||
error_messages:
|
||||
helper:
|
||||
invalid_fields: "Invalid Fields"
|
||||
|
|
@ -1037,6 +1045,12 @@ en:
|
|||
hello: "Hey everyone, I'm #%{new_user_tag}. "
|
||||
i_like: "I'm interested in %{tags}. "
|
||||
invited_by: "Thanks for the invite, "
|
||||
poll:
|
||||
remove_poll_answer: "Remove option"
|
||||
add_poll_answer: "Add option"
|
||||
add_a_poll: "Add a poll"
|
||||
question: "Question"
|
||||
option: "Option 1"
|
||||
add_contact:
|
||||
enter_a_diaspora_username: "Enter a diaspora* username:"
|
||||
your_diaspora_username_is: "Your diaspora* username is: %{diaspora_handle}"
|
||||
|
|
|
|||
|
|
@ -172,3 +172,12 @@ en:
|
|||
reshared: "Reshared"
|
||||
comment: "Comment"
|
||||
home: "HOME"
|
||||
|
||||
poll:
|
||||
vote: "Vote"
|
||||
result: "Result"
|
||||
count:
|
||||
one: "1 vote so far"
|
||||
other: "<%=count%> votes so far"
|
||||
show_result: "Show result"
|
||||
close_result: "Hide result"
|
||||
|
|
@ -31,11 +31,15 @@ Diaspora::Application.routes.draw do
|
|||
get :interactions
|
||||
end
|
||||
|
||||
resources :poll_participations, :only => [:create]
|
||||
|
||||
resources :likes, :only => [:create, :destroy, :index ]
|
||||
resources :participations, :only => [:create, :destroy, :index]
|
||||
resources :comments, :only => [:new, :create, :destroy, :index]
|
||||
end
|
||||
|
||||
|
||||
|
||||
get 'p/:id' => 'posts#show', :as => 'short_post'
|
||||
get 'posts/:id/iframe' => 'posts#iframe', :as => 'iframe'
|
||||
|
||||
|
|
|
|||
38
db/migrate/20140308154022_create_polls.rb
Normal file
38
db/migrate/20140308154022_create_polls.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
class CreatePolls < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :polls do |t|
|
||||
t.string :question, :null => false
|
||||
t.belongs_to :status_message, :null => false
|
||||
t.boolean :status
|
||||
t.string :guid
|
||||
t.timestamps
|
||||
end
|
||||
add_index :polls, :status_message_id
|
||||
|
||||
create_table :poll_answers do |t|
|
||||
t.string :answer, :null => false
|
||||
t.belongs_to :poll, :null => false
|
||||
t.string :guid
|
||||
t.integer :vote_count, :default => 0
|
||||
end
|
||||
add_index :poll_answers, :poll_id
|
||||
|
||||
create_table :poll_participations do |t|
|
||||
t.belongs_to :poll_answer, :null => false
|
||||
t.belongs_to :author, :null => false
|
||||
t.belongs_to :poll, :null => false
|
||||
t.string :guid
|
||||
t.text :author_signature
|
||||
t.text :parent_author_signature
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
add_index :poll_participations, :poll_id
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :polls
|
||||
drop_table :poll_answers
|
||||
drop_table :poll_participations
|
||||
end
|
||||
end
|
||||
71
db/schema.rb
71
db/schema.rb
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20140222162826) do
|
||||
ActiveRecord::Schema.define(:version => 20140308154022) do
|
||||
|
||||
create_table "account_deletions", :force => true do |t|
|
||||
t.string "diaspora_handle"
|
||||
|
|
@ -283,6 +283,39 @@ ActiveRecord::Schema.define(:version => 20140222162826) do
|
|||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
create_table "poll_answers", :force => true do |t|
|
||||
t.string "answer", :null => false
|
||||
t.integer "poll_id", :null => false
|
||||
t.string "guid"
|
||||
t.integer "vote_count", :default => 0
|
||||
end
|
||||
|
||||
add_index "poll_answers", ["poll_id"], :name => "index_poll_answers_on_poll_id"
|
||||
|
||||
create_table "poll_participations", :force => true do |t|
|
||||
t.integer "poll_answer_id", :null => false
|
||||
t.integer "author_id", :null => false
|
||||
t.integer "poll_id", :null => false
|
||||
t.string "guid"
|
||||
t.text "author_signature"
|
||||
t.text "parent_author_signature"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
add_index "poll_participations", ["poll_id"], :name => "index_poll_participations_on_poll_id"
|
||||
|
||||
create_table "polls", :force => true do |t|
|
||||
t.string "question", :null => false
|
||||
t.integer "status_message_id", :null => false
|
||||
t.boolean "status"
|
||||
t.string "guid"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
add_index "polls", ["status_message_id"], :name => "index_polls_on_status_message_id"
|
||||
|
||||
create_table "post_reports", :force => true do |t|
|
||||
t.integer "post_id", :null => false
|
||||
t.string "user_id"
|
||||
|
|
@ -502,36 +535,36 @@ ActiveRecord::Schema.define(:version => 20140222162826) do
|
|||
add_index "users", ["invitation_token"], :name => "index_users_on_invitation_token"
|
||||
add_index "users", ["username"], :name => "index_users_on_username", :unique => true
|
||||
|
||||
add_foreign_key "aspect_memberships", "aspects", :name => "aspect_memberships_aspect_id_fk", :dependent => :delete
|
||||
add_foreign_key "aspect_memberships", "contacts", :name => "aspect_memberships_contact_id_fk", :dependent => :delete
|
||||
add_foreign_key "aspect_memberships", "aspects", name: "aspect_memberships_aspect_id_fk", dependent: :delete
|
||||
add_foreign_key "aspect_memberships", "contacts", name: "aspect_memberships_contact_id_fk", dependent: :delete
|
||||
|
||||
add_foreign_key "aspect_visibilities", "aspects", :name => "aspect_visibilities_aspect_id_fk", :dependent => :delete
|
||||
add_foreign_key "aspect_visibilities", "aspects", name: "aspect_visibilities_aspect_id_fk", dependent: :delete
|
||||
|
||||
add_foreign_key "comments", "people", :name => "comments_author_id_fk", :column => "author_id", :dependent => :delete
|
||||
add_foreign_key "comments", "people", name: "comments_author_id_fk", column: "author_id", dependent: :delete
|
||||
|
||||
add_foreign_key "contacts", "people", :name => "contacts_person_id_fk", :dependent => :delete
|
||||
add_foreign_key "contacts", "people", name: "contacts_person_id_fk", dependent: :delete
|
||||
|
||||
add_foreign_key "conversation_visibilities", "conversations", :name => "conversation_visibilities_conversation_id_fk", :dependent => :delete
|
||||
add_foreign_key "conversation_visibilities", "people", :name => "conversation_visibilities_person_id_fk", :dependent => :delete
|
||||
add_foreign_key "conversation_visibilities", "conversations", name: "conversation_visibilities_conversation_id_fk", dependent: :delete
|
||||
add_foreign_key "conversation_visibilities", "people", name: "conversation_visibilities_person_id_fk", dependent: :delete
|
||||
|
||||
add_foreign_key "conversations", "people", :name => "conversations_author_id_fk", :column => "author_id", :dependent => :delete
|
||||
add_foreign_key "conversations", "people", name: "conversations_author_id_fk", column: "author_id", dependent: :delete
|
||||
|
||||
add_foreign_key "invitations", "users", :name => "invitations_recipient_id_fk", :column => "recipient_id", :dependent => :delete
|
||||
add_foreign_key "invitations", "users", :name => "invitations_sender_id_fk", :column => "sender_id", :dependent => :delete
|
||||
add_foreign_key "invitations", "users", name: "invitations_recipient_id_fk", column: "recipient_id", dependent: :delete
|
||||
add_foreign_key "invitations", "users", name: "invitations_sender_id_fk", column: "sender_id", dependent: :delete
|
||||
|
||||
add_foreign_key "likes", "people", :name => "likes_author_id_fk", :column => "author_id", :dependent => :delete
|
||||
add_foreign_key "likes", "people", name: "likes_author_id_fk", column: "author_id", dependent: :delete
|
||||
|
||||
add_foreign_key "messages", "conversations", :name => "messages_conversation_id_fk", :dependent => :delete
|
||||
add_foreign_key "messages", "people", :name => "messages_author_id_fk", :column => "author_id", :dependent => :delete
|
||||
add_foreign_key "messages", "conversations", name: "messages_conversation_id_fk", dependent: :delete
|
||||
add_foreign_key "messages", "people", name: "messages_author_id_fk", column: "author_id", dependent: :delete
|
||||
|
||||
add_foreign_key "notification_actors", "notifications", :name => "notification_actors_notification_id_fk", :dependent => :delete
|
||||
add_foreign_key "notification_actors", "notifications", name: "notification_actors_notification_id_fk", dependent: :delete
|
||||
|
||||
add_foreign_key "posts", "people", :name => "posts_author_id_fk", :column => "author_id", :dependent => :delete
|
||||
add_foreign_key "posts", "people", name: "posts_author_id_fk", column: "author_id", dependent: :delete
|
||||
|
||||
add_foreign_key "profiles", "people", :name => "profiles_person_id_fk", :dependent => :delete
|
||||
add_foreign_key "profiles", "people", name: "profiles_person_id_fk", dependent: :delete
|
||||
|
||||
add_foreign_key "services", "users", :name => "services_user_id_fk", :dependent => :delete
|
||||
add_foreign_key "services", "users", name: "services_user_id_fk", dependent: :delete
|
||||
|
||||
add_foreign_key "share_visibilities", "contacts", :name => "post_visibilities_contact_id_fk", :dependent => :delete
|
||||
add_foreign_key "share_visibilities", "contacts", name: "post_visibilities_contact_id_fk", dependent: :delete
|
||||
|
||||
end
|
||||
|
|
|
|||
65
features/desktop/post_with_a_poll.feature
Normal file
65
features/desktop/post_with_a_poll.feature
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
@javascript
|
||||
Feature: posting with a poll
|
||||
|
||||
Background:
|
||||
Given following users exist:
|
||||
| username |
|
||||
| bob |
|
||||
And I sign in as "bob@bob.bob"
|
||||
And I am on the home page
|
||||
|
||||
Scenario: expanding the publisher
|
||||
Given "#poll_creator_wrapper" is hidden
|
||||
When I expand the publisher
|
||||
Then I should see an element "#poll_creator"
|
||||
|
||||
Scenario: expanding the poll creator
|
||||
Given "#poll_creator_wrapper" is hidden
|
||||
When I expand the publisher
|
||||
And I press the element "#poll_creator"
|
||||
Then I should see an element "#poll_creator_wrapper"
|
||||
|
||||
Scenario: adding option to poll
|
||||
Given "#poll_creator_wrapper" is hidden
|
||||
When I expand the publisher
|
||||
And I press the element "#poll_creator"
|
||||
And I press the element "#add_poll_answer"
|
||||
Then I should see 3 options
|
||||
|
||||
Scenario: delete an option
|
||||
Given "#poll_creator_wrapper" is hidden
|
||||
When I expand the publisher
|
||||
And I press the element "#poll_creator"
|
||||
And I delete the first option
|
||||
Then I should see 1 option
|
||||
And I should not see a remove icon
|
||||
|
||||
Scenario: post with an attached poll
|
||||
Given I expand the publisher
|
||||
And I press the element "#poll_creator"
|
||||
When I fill in the following:
|
||||
| status_message_fake_text | I am eating yogurt |
|
||||
| poll_question | What kind of yogurt do you like? |
|
||||
And I fill in the following for the options:
|
||||
| normal |
|
||||
| not normal |
|
||||
And I press "Share"
|
||||
Then I should see a ".poll_form" within ".stream_element"
|
||||
And I should see a "form" within ".stream_element"
|
||||
|
||||
Scenario: vote for an option
|
||||
Given I expand the publisher
|
||||
And I press the element "#poll_creator"
|
||||
When I fill in the following:
|
||||
| status_message_fake_text | I am eating yogurt |
|
||||
| poll_question | What kind of yogurt do you like? |
|
||||
And I fill in the following for the options:
|
||||
| normal |
|
||||
| not normal |
|
||||
And I press "Share"
|
||||
|
||||
And I check the first option
|
||||
And I press "Vote" within ".stream_element"
|
||||
Then I should see an element ".poll_progress_bar"
|
||||
And I should see an element ".percentage"
|
||||
And I should see "1 vote so far" within ".poll_statistic"
|
||||
32
features/step_definitions/post_with_poll_steps.rb
Normal file
32
features/step_definitions/post_with_poll_steps.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
Then /^I should see ([1-9]+) options?$/ do |number|
|
||||
find("#poll_creator_wrapper").all(".poll_answer").count.should eql(number.to_i)
|
||||
end
|
||||
|
||||
And /^I delete the first option$/ do
|
||||
find("#poll_creator_wrapper").all(".poll_answer .remove_poll_answer").first.click
|
||||
end
|
||||
|
||||
And /^I should not see a remove icon$/ do
|
||||
page.should_not have_css(".remove_poll_answer")
|
||||
end
|
||||
|
||||
When /^I fill in the following for the options:$/ do |table|
|
||||
i = 0
|
||||
table.raw.flatten.each do |value|
|
||||
all(".poll_answer_input")[i].set(value)
|
||||
i+=1
|
||||
end
|
||||
end
|
||||
|
||||
When /^I check the first option$/ do
|
||||
sleep 1
|
||||
first(".poll_form input").click
|
||||
end
|
||||
|
||||
And /^I press the element "([^"]*)"$/ do |selector|
|
||||
find(selector).click
|
||||
end
|
||||
|
||||
Then /^I should see an element "([^"]*)"$/ do |selector|
|
||||
page.should have_css(selector)
|
||||
end
|
||||
|
|
@ -11,8 +11,6 @@ module Federated
|
|||
FEDERATION_LOGGER.info("user:#{@user.id} dispatching #{relayable.class}:#{relayable.guid}")
|
||||
Postzord::Dispatcher.defer_build_and_post(@user, relayable)
|
||||
relayable
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,12 @@ FactoryGirl.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory(:status_message_with_poll, :parent => :status_message) do
|
||||
after(:build) do |sm|
|
||||
FactoryGirl.create(:poll, :status_message => sm)
|
||||
end
|
||||
end
|
||||
|
||||
factory(:status_message_with_photo, :parent => :status_message) do
|
||||
sequence(:text) { |n| "There are #{n} ninjas in this photo." }
|
||||
after(:build) do |sm|
|
||||
|
|
@ -107,6 +113,18 @@ FactoryGirl.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory(:poll) do
|
||||
sequence(:question) { |n| "What do you think about #{n} ninjas?" }
|
||||
after(:build) do |p|
|
||||
p.poll_answers << FactoryGirl.build(:poll_answer)
|
||||
p.poll_answers << FactoryGirl.build(:poll_answer)
|
||||
end
|
||||
end
|
||||
|
||||
factory(:poll_answer) do
|
||||
sequence(:answer) { |n| "#{n} questionmarks" }
|
||||
end
|
||||
|
||||
factory(:photo) do
|
||||
sequence(:random_string) {|n| SecureRandom.hex(10) }
|
||||
association :author, :factory => :person
|
||||
|
|
|
|||
45
spec/javascripts/app/views/poll_view_spec.js
Normal file
45
spec/javascripts/app/views/poll_view_spec.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
describe("app.views.Poll", function(){
|
||||
beforeEach(function() {
|
||||
loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
|
||||
this.view = new app.views.Poll({ "model" : factory.postWithPoll()});
|
||||
this.view.render();
|
||||
});
|
||||
|
||||
describe("setProgressBar", function(){
|
||||
it("sets the progress bar according to the voting result", function(){
|
||||
var percentage = (this.view.poll.poll_answers[0].vote_count / this.view.poll.participation_count)*100;
|
||||
expect(this.view.$('.poll_progress_bar:first').css('width')).toBe(this.view.progressBarFactor * percentage+"px");
|
||||
expect(this.view.$(".percentage:first").text()).toBe(" - " + percentage + "%");
|
||||
})
|
||||
});
|
||||
|
||||
describe("toggleResult", function(){
|
||||
it("toggles the progress bar and result", function(){
|
||||
expect(this.view.$('.poll_progress_bar_wrapper:first').css('display')).toBe("none");
|
||||
this.view.toggleResult(null);
|
||||
expect(this.view.$('.poll_progress_bar_wrapper:first').css('display')).toBe("block");
|
||||
})
|
||||
});
|
||||
|
||||
describe("updateCounter", function(){
|
||||
it("updates the counter after a vote", function(){
|
||||
var pc = this.view.poll.participation_count;
|
||||
var answerCount = this.view.poll.poll_answers[0].vote_count;
|
||||
this.view.updateCounter(1);
|
||||
expect(this.view.poll.participation_count).toBe(pc+1);
|
||||
expect(this.view.poll.poll_answers[0].vote_count).toBe(answerCount+1);
|
||||
})
|
||||
});
|
||||
|
||||
describe("vote", function(){
|
||||
it("checks the ajax call for voting", function(){
|
||||
spyOn($, "ajax");
|
||||
var radio = this.view.$('input[name="vote"]:first');
|
||||
radio.attr('checked', true);
|
||||
this.view.vote({'target' : radio});
|
||||
var obj = JSON.parse($.ajax.mostRecentCall.args[0].data);
|
||||
expect(obj.poll_id).toBe(this.view.poll.poll_id);
|
||||
expect(obj.poll_answer_id).toBe(this.view.poll.poll_answers[0].id);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
|
@ -277,6 +277,40 @@ describe("app.views.Publisher", function() {
|
|||
|
||||
});
|
||||
|
||||
context("poll", function(){
|
||||
beforeEach(function() {
|
||||
loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
|
||||
spec.loadFixture("aspects_index");
|
||||
$("#poll_creator_wrapper").hide(); //css not loaded? :-/
|
||||
this.view = new app.views.Publisher();
|
||||
});
|
||||
|
||||
describe('#showPollCreator', function(){
|
||||
it("Shows the poll creator", function(){
|
||||
expect($("#poll_creator_wrapper").is(":visible")).toBe(false);
|
||||
this.view.showPollCreator();
|
||||
expect($("#poll_creator_wrapper").is(":visible")).toBe(true);
|
||||
})
|
||||
});
|
||||
|
||||
describe("#addPollAnswer", function(){
|
||||
it("should add a poll answer if clicked", function(){
|
||||
expect($("#poll_creator_wrapper .poll_answer").length).toBe(2);
|
||||
this.view.addPollAnswer();
|
||||
expect($("#poll_creator_wrapper .poll_answer").length).toBe(3);
|
||||
})
|
||||
});
|
||||
|
||||
describe("#removePollAnswer", function(){
|
||||
it("should remove a poll answer if clicked", function(){
|
||||
var answer_count = $('.poll_answer').length;
|
||||
var evt = {'currentTarget' : $("#poll_creator_wrapper .poll_answer:first .remove_poll_answer")};
|
||||
this.view.removePollAnswer(evt);
|
||||
expect($("#poll_creator_wrapper .poll_answer").length).toBe(answer_count-1);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
context("locator", function() {
|
||||
beforeEach(function() {
|
||||
// should be jasmine helper
|
||||
|
|
|
|||
|
|
@ -124,11 +124,31 @@ factory = {
|
|||
return new app.models.Post(_.extend(defaultAttrs, overrides))
|
||||
},
|
||||
|
||||
postWithPoll : function(overrides) {
|
||||
defaultAttrs = _.extend(factory.postAttrs(), {"author" : this.author()});
|
||||
defaultAttrs = _.extend(defaultAttrs, {"already_participated_in_poll" : false});
|
||||
defaultAttrs = _.extend(defaultAttrs, {"poll" : factory.poll()});
|
||||
return new app.models.Post(_.extend(defaultAttrs, overrides));
|
||||
},
|
||||
|
||||
statusMessage : function(overrides){
|
||||
//intentionally doesn't have an author to mirror creation process, maybe we should change the creation process
|
||||
return new app.models.StatusMessage(_.extend(factory.postAttrs(), overrides))
|
||||
},
|
||||
|
||||
poll: function(overrides){
|
||||
return {
|
||||
"question" : "This is an awesome question",
|
||||
"created_at" : "2012-01-03T19:53:13Z",
|
||||
"author" : this.author(),
|
||||
"post_id" : 1,
|
||||
"poll_answers" : [{"answer" : "yes", "id" : 1, "vote_count" : 9}, {"answer" : "no", "id" : 2, "vote_count" : 1}],
|
||||
"guid" : this.guid(),
|
||||
"poll_id": this.id.next(),
|
||||
"participation_count" : 10
|
||||
}
|
||||
},
|
||||
|
||||
comment: function(overrides) {
|
||||
var defaultAttrs = {
|
||||
"text" : "This is an awesome comment!",
|
||||
|
|
|
|||
21
spec/models/poll_answer_spec.rb
Normal file
21
spec/models/poll_answer_spec.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe PollAnswer do
|
||||
before do
|
||||
@status = FactoryGirl.create(:status_message_with_poll)
|
||||
@user = alice
|
||||
@answer = @status.poll.poll_answers.first
|
||||
end
|
||||
|
||||
describe 'counter cache' do
|
||||
it 'increments the counter cache on the answer' do
|
||||
lambda {
|
||||
alice.participate_in_poll!(@status, @answer)
|
||||
}.should change{
|
||||
@answer.reload.vote_count
|
||||
}.by(1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
102
spec/models/poll_participation_spec.rb
Normal file
102
spec/models/poll_participation_spec.rb
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
require 'spec_helper'
|
||||
require Rails.root.join("spec", "shared_behaviors", "relayable")
|
||||
|
||||
describe PollParticipation do
|
||||
|
||||
before do
|
||||
@alices_aspect = alice.aspects.first
|
||||
@status = bob.post(:status_message, :text => "hello", :to => bob.aspects.first.id)
|
||||
@poll = Poll.new(:question => 'Who is in charge?')
|
||||
@poll.poll_answers.build(:answer => "a")
|
||||
@poll.poll_answers.build(:answer => "b")
|
||||
@status.poll = @poll
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
it 'forbids multiple participations in the same poll' do
|
||||
expect {
|
||||
2.times do |run|
|
||||
bob.participate_in_poll!(@status, @poll.poll_answers.first)
|
||||
end
|
||||
}.to raise_error
|
||||
end
|
||||
|
||||
it 'allows a one time participation in a poll' do
|
||||
expect {
|
||||
bob.participate_in_poll!(@status, @poll.poll_answers.first)
|
||||
}.to_not raise_error
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'xml' do
|
||||
before do
|
||||
@poll_participant = FactoryGirl.create(:user)
|
||||
@poll_participant_aspect = @poll_participant.aspects.create(:name => "bruisers")
|
||||
connect_users(alice, @alices_aspect, @poll_participant, @poll_participant_aspect)
|
||||
@poll = Poll.new(:question => "hi")
|
||||
@poll.poll_answers.build(:answer => "a")
|
||||
@poll.poll_answers.build(:answer => "b")
|
||||
@post = alice.post :status_message, :text => "hello", :to => @alices_aspect.id
|
||||
@post.poll = @poll
|
||||
@poll_participation = @poll_participant.participate_in_poll!(@post, @poll.poll_answers.first)
|
||||
@xml = @poll_participation.to_xml.to_s
|
||||
end
|
||||
|
||||
it 'serializes the class name' do
|
||||
@xml.include?(PollParticipation.name.underscore.to_s).should be_true
|
||||
end
|
||||
|
||||
it 'serializes the sender handle' do
|
||||
@xml.include?(@poll_participation.diaspora_handle).should be_true
|
||||
end
|
||||
|
||||
it 'serializes the poll_guid' do
|
||||
@xml.should include(@poll.guid)
|
||||
end
|
||||
|
||||
it 'serializes the poll_answer_guid' do
|
||||
@xml.should include(@poll_participation.poll_answer.guid)
|
||||
end
|
||||
|
||||
describe 'marshalling' do
|
||||
before do
|
||||
@marshalled_poll_participation = PollParticipation.from_xml(@xml)
|
||||
end
|
||||
|
||||
it 'marshals the author' do
|
||||
@marshalled_poll_participation.author.should == @poll_participant.person
|
||||
end
|
||||
|
||||
it 'marshals the answer' do
|
||||
@marshalled_poll_participation.poll_answer.should == @poll_participation.poll_answer
|
||||
end
|
||||
|
||||
it 'marshals the poll' do
|
||||
@marshalled_poll_participation.poll.should == @poll
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'it is relayable' do
|
||||
before do
|
||||
@local_luke, @local_leia, @remote_raphael = set_up_friends
|
||||
@remote_parent = FactoryGirl.build(:status_message_with_poll, :author => @remote_raphael)
|
||||
|
||||
@local_parent = @local_luke.post :status_message, :text => "hi", :to => @local_luke.aspects.first
|
||||
@poll2 = Poll.new(:question => 'Who is now in charge?')
|
||||
@poll2.poll_answers.build(:answer => "a")
|
||||
@poll2.poll_answers.build(:answer => "b")
|
||||
@local_parent.poll = @poll2
|
||||
|
||||
@object_by_parent_author = @local_luke.participate_in_poll!(@local_parent, @poll2.poll_answers.first)
|
||||
@object_by_recipient = @local_leia.participate_in_poll!(@local_parent, @poll2.poll_answers.first)
|
||||
@dup_object_by_parent_author = @object_by_parent_author.dup
|
||||
|
||||
@object_on_remote_parent = @local_luke.participate_in_poll!(@remote_parent, @remote_parent.poll.poll_answers.first)
|
||||
end
|
||||
|
||||
let(:build_object) { PollParticipation::Generator.new(alice, @status, @poll.poll_answers.first).build }
|
||||
it_should_behave_like 'it is relayable'
|
||||
end
|
||||
end
|
||||
20
spec/models/poll_spec.rb
Normal file
20
spec/models/poll_spec.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Poll do
|
||||
before do
|
||||
@poll = Poll.new(:question => "What do you think about apples?")
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
it 'should not create a poll when it has less than two answers' do
|
||||
@poll.poll_answers.build(:answer => '1')
|
||||
@poll.should_not be_valid
|
||||
end
|
||||
|
||||
it 'should create a poll when it has more than two answers' do
|
||||
@poll.poll_answers.build(:answer => '1')
|
||||
@poll.poll_answers.build(:answer => '2')
|
||||
@poll.should be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -350,6 +350,35 @@ STR
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a poll' do
|
||||
before do
|
||||
@message.poll = FactoryGirl.create(:poll, :status_message => @message)
|
||||
@xml = @message.to_xml.to_s
|
||||
end
|
||||
|
||||
it 'serializes the poll' do
|
||||
@xml.should include "poll"
|
||||
@xml.should include "question"
|
||||
@xml.should include "poll_answer"
|
||||
end
|
||||
|
||||
describe ".from_xml" do
|
||||
before do
|
||||
@marshalled = StatusMessage.from_xml(@xml)
|
||||
end
|
||||
|
||||
it 'marshals the poll' do
|
||||
@marshalled.poll.should be_present
|
||||
end
|
||||
|
||||
it 'marshals the poll answers' do
|
||||
@marshalled.poll.poll_answers.size.should == 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe '#after_dispatch' do
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe User::SocialActions do
|
|||
alice.participations.last.target.should == @status
|
||||
end
|
||||
|
||||
it "creates the like" do
|
||||
it "creates the comment" do
|
||||
lambda{ alice.comment!(@status, "bro") }.should change(Comment, :count).by(1)
|
||||
end
|
||||
|
||||
|
|
@ -83,4 +83,31 @@ describe User::SocialActions do
|
|||
@status.reload.likes.should == likes
|
||||
end
|
||||
end
|
||||
|
||||
describe 'User#participate_in_poll!' do
|
||||
before do
|
||||
@bobs_aspect = bob.aspects.where(:name => "generic").first
|
||||
@status = bob.post(:status_message, :text => "hello", :to => @bobs_aspect.id)
|
||||
@poll = FactoryGirl.create(:poll, :status_message => @status)
|
||||
@answer = @poll.poll_answers.first
|
||||
end
|
||||
|
||||
it "federates" do
|
||||
Participation::Generator.any_instance.stub(:create!)
|
||||
Postzord::Dispatcher.should_receive(:defer_build_and_post)
|
||||
alice.participate_in_poll!(@status, @answer)
|
||||
end
|
||||
|
||||
it "creates a partcipation" do
|
||||
lambda{ alice.participate_in_poll!(@status, @answer) }.should change(Participation, :count).by(1)
|
||||
end
|
||||
|
||||
it "creates the poll participation" do
|
||||
lambda{ alice.participate_in_poll!(@status, @answer) }.should change(PollParticipation, :count).by(1)
|
||||
end
|
||||
|
||||
it "sets the poll answer id" do
|
||||
alice.participate_in_poll!(@status, @answer).poll_answer.should == @answer
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue