diff --git a/lib/ext/vote.rb b/lib/ext/vote.rb new file mode 100644 index 0000000..3248555 --- /dev/null +++ b/lib/ext/vote.rb @@ -0,0 +1,15 @@ +require_dependency 'models/vote' + +class Vote + + validate :proposals_discussion_plugin_modify_vote + before_destroy :proposals_discussion_plugin_modify_vote + + def proposals_discussion_plugin_modify_vote + if voteable.kind_of?(ProposalsDiscussionPlugin::Proposal) && !voteable.allow_vote? + errors.add(:base, _("Can't vote in this discussion anymore.")) + false + end + end + +end diff --git a/lib/proposals_discussion_plugin/discussion.rb b/lib/proposals_discussion_plugin/discussion.rb index f3024ba..cff994a 100644 --- a/lib/proposals_discussion_plugin/discussion.rb +++ b/lib/proposals_discussion_plugin/discussion.rb @@ -19,8 +19,11 @@ class ProposalsDiscussionPlugin::Discussion < ProposalsDiscussionPlugin::Proposa settings_items :custom_body_label, :type => :string, :default => _('Body') settings_items :allow_topics, :type => :boolean, :default => false + settings_items :phase, :type => :string, :default => :proposals - attr_accessible :custom_body_label, :allow_topics + attr_accessible :custom_body_label, :allow_topics, :phase + + AVAILABLE_PHASES = {:proposals => _('Proposals'), :vote => 'Vote', :finish => 'Announcement'} def self.short_description _("Discussion") @@ -30,6 +33,14 @@ class ProposalsDiscussionPlugin::Discussion < ProposalsDiscussionPlugin::Proposa _('Container for topics.') end + def available_phases + AVAILABLE_PHASES + end + + def allow_new_proposals? + phase.to_sym == :proposals + end + def to_html(options = {}) discussion = self proc do diff --git a/lib/proposals_discussion_plugin/discussion_helper.rb b/lib/proposals_discussion_plugin/discussion_helper.rb new file mode 100644 index 0000000..606200a --- /dev/null +++ b/lib/proposals_discussion_plugin/discussion_helper.rb @@ -0,0 +1,22 @@ +module ProposalsDiscussionPlugin::DiscussionHelper + + def link_to_new_proposal(discussion) + return '' unless discussion.allow_new_proposals? + + url = {:parent_id => discussion.id, :profile => discussion.profile.identifier} + if discussion.allow_topics + url.merge!(:controller => 'proposals_discussion_plugin_myprofile', :action => 'select_topic') + else + url.merge!(:controller => 'cms', :action => 'new', :type => "ProposalsDiscussionPlugin::Proposal") + end + link_to _("Send your proposal!"), url_for(url), :class => 'button with-text icon-add' + end + + def discussion_phases(discussion) + discussion.available_phases.map do |phase| + active = discussion.phase.to_sym == phase.first ? ' active' : '' + content_tag 'span', phase.second, :class => "phase #{phase.first}#{active}" + end.join + end + +end diff --git a/lib/proposals_discussion_plugin/proposal.rb b/lib/proposals_discussion_plugin/proposal.rb index 1e34eb9..5db40b8 100644 --- a/lib/proposals_discussion_plugin/proposal.rb +++ b/lib/proposals_discussion_plugin/proposal.rb @@ -15,8 +15,18 @@ class ProposalsDiscussionPlugin::Proposal < TinyMceArticle validates_presence_of :abstract + validate :discussion_phase_proposals + + def discussion_phase_proposals + errors.add(:base, _("Can't create a proposal at this phase.")) unless discussion.allow_new_proposals? + end + + def allow_vote? + discussion.phase.to_sym != :finish + end + def discussion - parent.kind_of?(ProposalsDiscussionPlugin::Discussion) ? parent : parent.discussion + @discussion ||= parent.kind_of?(ProposalsDiscussionPlugin::Discussion) ? parent : parent.discussion end def to_html(options = {}) diff --git a/public/style.css b/public/style.css index 54d5e36..a553ffe 100644 --- a/public/style.css +++ b/public/style.css @@ -1,3 +1,30 @@ +.article-body-proposals-discussion-plugin_discussion .phases { + margin: 20px; + color: white; +} +.article-body-proposals-discussion-plugin_discussion .phases .phase { + padding: 8px; + background-color: gray; + min-width: 70px; + display: inline-block; + text-align: center; + font-size: 14px; + font-weight: bold; + opacity: 0.3; +} +.article-body-proposals-discussion-plugin_discussion .phases .proposals { + background-color: rgb(55, 186, 211); +} +.article-body-proposals-discussion-plugin_discussion .phases .vote { + background-color: rgb(236, 141, 52); +} +.article-body-proposals-discussion-plugin_discussion .phases .finish { + background-color: rgb(57, 175, 142); +} +.article-body-proposals-discussion-plugin_discussion .phases .active { + opacity: 1; +} + .proposals_list .proposal .abstract { color: rgb(160, 160, 160); margin-bottom: 15px; diff --git a/test/functional/cms_controller_test.rb b/test/functional/cms_controller_test.rb index a798792..28ce7f6 100644 --- a/test/functional/cms_controller_test.rb +++ b/test/functional/cms_controller_test.rb @@ -30,4 +30,9 @@ class CmsControllerTest < ActionController::TestCase assert_tag :tag => 'label', :attributes => {:class => 'formlabel'}, :content => 'My Custom Label' end + should 'display available phases when edit a proposal' do + get :edit, :id => discussion.id, :profile => profile.identifier + assert_tag :tag => 'select', :attributes => {:id => 'article_phase'} + end + end diff --git a/test/unit/discussion_helper_test.rb b/test/unit/discussion_helper_test.rb new file mode 100644 index 0000000..897d5de --- /dev/null +++ b/test/unit/discussion_helper_test.rb @@ -0,0 +1,28 @@ +require_relative '../test_helper' + +class DiscussionHelperTest < ActionView::TestCase + + def setup + @profile = fast_create(Community) + @discussion = ProposalsDiscussionPlugin::Discussion.create!(:name => 'discussion', :profile => @profile, :name => 'discussion') + end + + include ProposalsDiscussionPlugin::DiscussionHelper + + attr_reader :profile, :discussion + + should 'display new proposal link when discussion is in proposals phase' do + assert !link_to_new_proposal(discussion).blank? + end + + should 'not display new proposal link when discussion is in vote phase' do + discussion.update_attribute(:phase, :vote) + assert link_to_new_proposal(discussion).blank? + end + + should 'not display new proposal link when discussion is in finish phase' do + discussion.update_attribute(:phase, :finish) + assert link_to_new_proposal(discussion).blank? + end + +end diff --git a/test/unit/discussion_test.rb b/test/unit/discussion_test.rb index a1b05bd..d4bed28 100644 --- a/test/unit/discussion_test.rb +++ b/test/unit/discussion_test.rb @@ -34,4 +34,19 @@ class DiscussionTest < ActiveSupport::TestCase assert_equal 10, discussion.max_score end + should 'allow new proposals if discussion phase is proposals' do + discussion.phase = :proposals + assert discussion.allow_new_proposals? + end + + should 'not allow new proposals if discussion phase is vote' do + discussion.phase = :vote + assert !discussion.allow_new_proposals? + end + + should 'not allow new proposals if discussion phase is finish' do + discussion.phase = :finish + assert !discussion.allow_new_proposals? + end + end diff --git a/test/unit/proposal_test.rb b/test/unit/proposal_test.rb index b02785f..86f0fdc 100644 --- a/test/unit/proposal_test.rb +++ b/test/unit/proposal_test.rb @@ -5,11 +5,12 @@ class ProposalTest < ActiveSupport::TestCase def setup @profile = fast_create(Community) @person = fast_create(Person) - @proposal = ProposalsDiscussionPlugin::Proposal.new(:name => 'test', :profile => @profile) + @discussion = ProposalsDiscussionPlugin::Discussion.create!(:name => 'discussion', :profile => person, :name => 'discussion') + @proposal = ProposalsDiscussionPlugin::Proposal.new(:name => 'test', :abstract => 'abstract', :profile => @profile, :parent => @discussion) @proposal.created_by = @person end - attr_reader :profile, :proposal, :person + attr_reader :profile, :proposal, :person, :discussion should 'save a proposal' do proposal.abstract = 'abstract' @@ -17,6 +18,7 @@ class ProposalTest < ActiveSupport::TestCase end should 'do not save a proposal without abstract' do + proposal.abstract = nil proposal.save assert proposal.errors['abstract'].present? end @@ -42,7 +44,6 @@ class ProposalTest < ActiveSupport::TestCase end should 'return proposals by discussion' do - discussion = fast_create(ProposalsDiscussionPlugin::Discussion) topic = fast_create(ProposalsDiscussionPlugin::Topic, :parent_id => discussion.id) proposal1 = fast_create(ProposalsDiscussionPlugin::Proposal, :parent_id => topic.id) proposal2 = fast_create(ProposalsDiscussionPlugin::Proposal) @@ -51,8 +52,17 @@ class ProposalTest < ActiveSupport::TestCase assert_equivalent [proposal1, proposal3], ProposalsDiscussionPlugin::Proposal.from_discussion(discussion) end + should 'return discussion associated with a proposal' do + assert_equal discussion, proposal.discussion + end + + should 'return discussion associated with a proposal topic' do + topic = fast_create(ProposalsDiscussionPlugin::Topic, :parent_id => discussion.id) + proposal = fast_create(ProposalsDiscussionPlugin::Proposal, :parent_id => topic.id) + assert_equal discussion, proposal.discussion + end + should 'return normalized score' do - discussion = ProposalsDiscussionPlugin::Discussion.create!(:profile => person, :name => 'discussion') proposal1 = ProposalsDiscussionPlugin::Proposal.create!(:parent => discussion, :profile => profile, :name => "proposal1", :abstract => 'abstract') proposal2 = ProposalsDiscussionPlugin::Proposal.create!(:parent => discussion, :profile => profile, :name => "proposal2", :abstract => 'abstract') 10.times { Comment.create!(:source => proposal1, :body => "comment", :author => person) } @@ -61,4 +71,50 @@ class ProposalTest < ActiveSupport::TestCase assert_equal 0.5, proposal2.reload.normalized_score end + should 'create a new proposal if the current phase is proposals' do + discussion.update_attribute(:phase, :proposals) + assert proposal.save + end + + should 'do not create a new proposal if the current phase is vote' do + discussion.update_attribute(:phase, :vote) + assert !proposal.save + end + + should 'do not create a new proposal if the current phase is finish' do + discussion.update_attribute(:phase, :finish) + assert !proposal.save + end + + should 'do not create a new proposal if the current phase is invalid' do + discussion.update_attribute(:phase, '') + assert !proposal.save + end + + should 'do not update a proposal if a discussion is not in proposals phase' do + discussion.update_attribute(:phase, :vote) + proposal.body = "changed" + assert !proposal.save + end + + should 'allow update of proposals if a discussion is in proposals phase' do + proposal.body = "changed" + assert proposal.save + end + + should 'allow vote if discussion phase is vote' do + discussion.update_attribute(:phase, :vote) + assert proposal.allow_vote? + end + + should 'allow vote if discussion phase is proposals' do + discussion.update_attribute(:phase, :proposals) + assert proposal.allow_vote? + end + + should 'not allow vote if discussion phase is finish' do + discussion.update_attribute(:phase, :finish) + assert !proposal.allow_vote? + end + end diff --git a/test/unit/topic_test.rb b/test/unit/topic_test.rb index 4ffc07e..8b1a366 100644 --- a/test/unit/topic_test.rb +++ b/test/unit/topic_test.rb @@ -3,8 +3,9 @@ require File.dirname(__FILE__) + '/../test_helper' class TopicTest < ActiveSupport::TestCase def setup + @discussion = fast_create(ProposalsDiscussionPlugin::Discussion) @profile = fast_create(Community) - @topic = ProposalsDiscussionPlugin::Topic.new(:name => 'test', :profile => @profile) + @topic = ProposalsDiscussionPlugin::Topic.new(:name => 'test', :profile => @profile, :parent => @discussion) end attr_reader :profile, :topic diff --git a/test/unit/vote_test.rb b/test/unit/vote_test.rb new file mode 100644 index 0000000..5c3f8c5 --- /dev/null +++ b/test/unit/vote_test.rb @@ -0,0 +1,53 @@ +require_relative '../test_helper' + +class VoteTest < ActiveSupport::TestCase + + def setup + @person = fast_create(Person) + @profile = fast_create(Community) + @discussion = ProposalsDiscussionPlugin::Discussion.create!(:name => 'discussion', :profile => @person, :name => 'discussion') + @proposal = ProposalsDiscussionPlugin::Proposal.create!(:name => 'test', :abstract => 'abstract', :profile => @profile, :parent => @discussion) + end + + attr_reader :profile, :proposal, :person, :discussion + + should 'vote for articles that are not proposals' do + article = fast_create(Article) + vote = Vote.new(:voteable => article, :voter => person, :vote => 1) + assert vote.save + end + + should 'vote for a proposal of a discussion in proposals phase' do + proposal.discussion.phase = :proposals + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1) + assert vote.save + end + + should 'vote for a proposal of a discussion in vote phase' do + proposal.discussion.phase = :vote + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1) + assert vote.save + end + + should 'not vote for a proposal of a finished discussion' do + proposal.discussion.phase = :finish + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1) + assert !vote.save + end + + should 'not destroy a proposal vote of a finished discussion' do + proposal.discussion.phase = :vote + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1) + assert vote.save + proposal.discussion.phase = :finish + assert !vote.destroy + end + + should 'destroy a proposal vote of a discussion in vote phase' do + proposal.discussion.phase = :vote + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1) + assert vote.save + assert vote.destroy + end + +end diff --git a/views/cms/proposals_discussion_plugin/_discussion.html.erb b/views/cms/proposals_discussion_plugin/_discussion.html.erb index c9360bc..b1afd93 100644 --- a/views/cms/proposals_discussion_plugin/_discussion.html.erb +++ b/views/cms/proposals_discussion_plugin/_discussion.html.erb @@ -6,4 +6,5 @@ <%= labelled_form_field(_('Description:'), text_area(:article, :body, :rows => 3, :cols => 64)) %> <%= f.text_field(:custom_body_label) %> +<%= labelled_form_field _('Current Phase'), f.select(:phase, ProposalsDiscussionPlugin::Discussion::AVAILABLE_PHASES.map{|k,v| [v,k]} ) %> <%= labelled_form_field check_box(:article, :allow_topics) + _('Allow topics'), '' %> diff --git a/views/content_viewer/discussion.html.erb b/views/content_viewer/discussion.html.erb index d11ebf6..6ee1b52 100644 --- a/views/content_viewer/discussion.html.erb +++ b/views/content_viewer/discussion.html.erb @@ -1,3 +1,4 @@ +<% extend ProposalsDiscussionPlugin::DiscussionHelper %> <%= javascript_include_tag 'plugins/proposals_discussion/proposals_list.js' %> <%= add_rss_feed_to_head(discussion.name, discussion.feed.url) if discussion.feed %> @@ -6,6 +7,10 @@ <%= discussion.body %> +