Commit 17673f21873397a15449f70589758298b338621f
Committed by
Antonio Terceiro
1 parent
8c237770
Exists in
master
and in
23 other branches
Nested comments
(ActionItem1771)
Showing
19 changed files
with
689 additions
and
52 deletions
Show diff stats
app/controllers/public/content_viewer_controller.rb
| @@ -99,7 +99,7 @@ class ContentViewerController < ApplicationController | @@ -99,7 +99,7 @@ class ContentViewerController < ApplicationController | ||
| 99 | @images = @images.paginate(:per_page => per_page, :page => params[:npage]) unless params[:slideshow] | 99 | @images = @images.paginate(:per_page => per_page, :page => params[:npage]) unless params[:slideshow] |
| 100 | end | 100 | end |
| 101 | 101 | ||
| 102 | - @comments = @page.comments(true) | 102 | + @comments = @page.comments(true).as_thread |
| 103 | if params[:slideshow] | 103 | if params[:slideshow] |
| 104 | render :action => 'slideshow', :layout => 'slideshow' | 104 | render :action => 'slideshow', :layout => 'slideshow' |
| 105 | end | 105 | end |
| @@ -116,7 +116,7 @@ class ContentViewerController < ApplicationController | @@ -116,7 +116,7 @@ class ContentViewerController < ApplicationController | ||
| 116 | @comment = nil # clear the comment form | 116 | @comment = nil # clear the comment form |
| 117 | redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view] | 117 | redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view] |
| 118 | else | 118 | else |
| 119 | - @form_div = 'opened' | 119 | + @form_div = 'opened' if params[:comment][:reply_of_id].blank? |
| 120 | end | 120 | end |
| 121 | end | 121 | end |
| 122 | 122 |
app/models/comment.rb
| @@ -5,6 +5,8 @@ class Comment < ActiveRecord::Base | @@ -5,6 +5,8 @@ class Comment < ActiveRecord::Base | ||
| 5 | validates_presence_of :title, :body | 5 | validates_presence_of :title, :body |
| 6 | belongs_to :article, :counter_cache => true | 6 | belongs_to :article, :counter_cache => true |
| 7 | belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id' | 7 | belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id' |
| 8 | + has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy | ||
| 9 | + belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id' | ||
| 8 | 10 | ||
| 9 | # unauthenticated authors: | 11 | # unauthenticated authors: |
| 10 | validates_presence_of :name, :if => (lambda { |record| !record.email.blank? }) | 12 | validates_presence_of :name, :if => (lambda { |record| !record.email.blank? }) |
| @@ -46,7 +48,7 @@ class Comment < ActiveRecord::Base | @@ -46,7 +48,7 @@ class Comment < ActiveRecord::Base | ||
| 46 | end | 48 | end |
| 47 | 49 | ||
| 48 | def message | 50 | def message |
| 49 | - author_id ? _('(removed user)') : ('<br />' + _('(unauthenticated user)')) | 51 | + author_id ? _('(removed user)') : _('(unauthenticated user)') |
| 50 | end | 52 | end |
| 51 | 53 | ||
| 52 | def removed_user_image | 54 | def removed_user_image |
| @@ -73,6 +75,25 @@ class Comment < ActiveRecord::Base | @@ -73,6 +75,25 @@ class Comment < ActiveRecord::Base | ||
| 73 | end | 75 | end |
| 74 | end | 76 | end |
| 75 | 77 | ||
| 78 | + def replies | ||
| 79 | + @replies || children | ||
| 80 | + end | ||
| 81 | + | ||
| 82 | + def replies=(comments_list) | ||
| 83 | + @replies = comments_list | ||
| 84 | + end | ||
| 85 | + | ||
| 86 | + def self.as_thread | ||
| 87 | + result = {} | ||
| 88 | + root = [] | ||
| 89 | + all.each do |c| | ||
| 90 | + c.replies = [] | ||
| 91 | + result[c.id] ||= c | ||
| 92 | + c.reply_of_id.nil? ? root << c : result[c.reply_of_id].replies << c | ||
| 93 | + end | ||
| 94 | + root | ||
| 95 | + end | ||
| 96 | + | ||
| 76 | class Notifier < ActionMailer::Base | 97 | class Notifier < ActionMailer::Base |
| 77 | def mail(comment) | 98 | def mail(comment) |
| 78 | profile = comment.article.profile | 99 | profile = comment.article.profile |
app/views/content_viewer/_comment.rhtml
| 1 | -<%= content_tag('a', '', :name => comment.anchor) %> | ||
| 2 | -<div class="article-comment<%= ' comment-from-owner' if ( comment.author && (@page.profile.name == comment.author.name) ) %> comment-logged-<%= comment.author ? 'in' : 'out' %>"> | 1 | +<li id="<%= comment.anchor %>" class="article-comment"> |
| 2 | + <div class="article-comment-inner"> | ||
| 3 | + | ||
| 4 | + <div class="comment-content comment-logged-<%= comment.author ? 'in' : 'out' %> <%= 'comment-from-owner' if ( comment.author && (@page.profile.name == comment.author.name) ) %>"> | ||
| 3 | 5 | ||
| 4 | <% if comment.author %> | 6 | <% if comment.author %> |
| 5 | - <%= link_to content_tag( 'span', comment.author_name, :class => 'comment-info' ), comment.author.url, | 7 | + <%= link_to image_tag(profile_icon(comment.author, :minor)) + |
| 8 | + content_tag('span', comment.author_name, :class => 'comment-info'), | ||
| 9 | + comment.author.url, | ||
| 6 | :class => 'comment-picture', | 10 | :class => 'comment-picture', |
| 7 | - :style => 'background-image:url(%s)' % profile_icon(comment.author, :minor) | 11 | + :title => comment.author_name |
| 8 | %> | 12 | %> |
| 9 | <% else %> | 13 | <% else %> |
| 10 | <%# unauthenticated user: display gravatar icon %> | 14 | <%# unauthenticated user: display gravatar icon %> |
| 11 | - <% url_image = comment.author_id ? comment.removed_user_image : str_gravatar_url_for( comment.email, :size => 50 ) %> | ||
| 12 | - <%= content_tag 'span', content_tag('span', comment.author_name + comment.message, :class => 'comment-info'), | 15 | + <% url_image, status_class = comment.author_id ? [comment.removed_user_image, 'icon-user-removed'] : [str_gravatar_url_for( comment.email, :size => 50 ), 'icon-user-unknown'] %> |
| 16 | + <%= content_tag 'span', image_tag(url_image) + | ||
| 17 | + content_tag('span', comment.author_name, :class => 'comment-info') + | ||
| 18 | + content_tag('span', comment.message, :class => 'comment-user-status ' + status_class), | ||
| 13 | :class => 'comment-picture', | 19 | :class => 'comment-picture', |
| 14 | - :style => 'background-image:url(%s)' % url_image | 20 | + :title => '%s %s' % [comment.author_name, comment.message] |
| 15 | %> | 21 | %> |
| 16 | <% end %> | 22 | <% end %> |
| 17 | 23 | ||
| @@ -32,6 +38,35 @@ | @@ -32,6 +38,35 @@ | ||
| 32 | <%= txt2html comment.body %> | 38 | <%= txt2html comment.body %> |
| 33 | </div> | 39 | </div> |
| 34 | </div> | 40 | </div> |
| 41 | + | ||
| 42 | + <div class="comment_reply post_comment_box closed"> | ||
| 43 | + <% if @comment && @comment.errors.any? && @comment.reply_of_id.to_i == comment.id %> | ||
| 44 | + <%= error_messages_for :comment %> | ||
| 45 | + <script type="text/javascript"> | ||
| 46 | + jQuery(function() { | ||
| 47 | + document.location.href = '#<%= comment.anchor %>'; | ||
| 48 | + add_comment_reply_form('#comment-reply-to-<%= comment.id %>', <%= comment.id %>); | ||
| 49 | + }); | ||
| 50 | + </script> | ||
| 51 | + <% end %> | ||
| 52 | + <%= link_to_function _('Reply'), | ||
| 53 | + "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id, | ||
| 54 | + :class => 'comment-reply-link', | ||
| 55 | + :id => 'comment-reply-to-' + comment.id.to_s | ||
| 56 | + %> | ||
| 57 | + </div> | ||
| 58 | + | ||
| 59 | + <% end %> | ||
| 60 | + | ||
| 61 | + </div> | ||
| 62 | + | ||
| 63 | + <% unless comment.replies.blank? %> | ||
| 64 | + <ul class="comment-replies"> | ||
| 65 | + <% comment.replies.each do |reply| %> | ||
| 66 | + <%= render :partial => 'comment', :locals => { :comment => reply } %> | ||
| 67 | + <% end %> | ||
| 68 | + </ul> | ||
| 35 | <% end %> | 69 | <% end %> |
| 36 | 70 | ||
| 37 | -</div> | 71 | + </div> |
| 72 | +</li> |
app/views/content_viewer/_comment_form.rhtml
| 1 | -<% | ||
| 2 | - comment_form_id = 'comment_form'+ rand(9999).to_s | ||
| 3 | -%> | ||
| 4 | - | ||
| 5 | <% focus_on = logged_in? ? 'title' : 'name' %> | 1 | <% focus_on = logged_in? ? 'title' : 'name' %> |
| 6 | 2 | ||
| 7 | -<%= error_messages_for :comment %> | 3 | +<% if @comment && @comment.errors.any? && @comment.reply_of_id.blank? %> |
| 4 | + <%= error_messages_for :comment %> | ||
| 5 | + <script type="text/javascript">jQuery(function() { document.location.href = '#page-comment-form'; });</script> | ||
| 6 | +<% end %> | ||
| 8 | 7 | ||
| 9 | <% @form_div ||= 'closed' %> | 8 | <% @form_div ||= 'closed' %> |
| 10 | 9 | ||
| 11 | -<div | ||
| 12 | - class="post_comment_box <%= @form_div %>" | ||
| 13 | - onclick="f=$('<%= comment_form_id %>'); | ||
| 14 | - this.className = this.className.replace(/closed/,'opened'); | ||
| 15 | - f.commit.focus(); f['comment[<%= focus_on %>]'].focus(); | ||
| 16 | - this.onclick=null"> | 10 | +<div class="post_comment_box <%= @form_div %>"> |
| 17 | 11 | ||
| 18 | -<h4><%= content_tag('a', '', :name => 'comment_form') + _('Post a comment') %></h4> | 12 | +<h4 onclick="var d = jQuery(this).parent('.post_comment_box'); |
| 13 | + if (d.hasClass('closed')) { | ||
| 14 | + d.removeClass('closed'); | ||
| 15 | + d.addClass('opened'); | ||
| 16 | + d.find('input[name=comment[title]], textarea').val(''); | ||
| 17 | + d.find('.comment_form input[name=comment[<%= focus_on %>]]').focus(); | ||
| 18 | + }"> | ||
| 19 | + <%= content_tag('a', '', :name => 'comment_form') + _('Post a comment') %> | ||
| 20 | +</h4> | ||
| 19 | 21 | ||
| 20 | -<% form_tag( url_for(@page.view_url.merge({:only_path => true})), { :id => comment_form_id } ) do %> | 22 | +<% form_tag( url_for(@page.view_url.merge({:only_path => true})), { :class => 'comment_form' } ) do %> |
| 21 | <%= icaptcha_field() %> | 23 | <%= icaptcha_field() %> |
| 22 | <%= hidden_field_tag(:confirm, 'false') %> | 24 | <%= hidden_field_tag(:confirm, 'false') %> |
| 23 | 25 | ||
| @@ -37,7 +39,8 @@ | @@ -37,7 +39,8 @@ | ||
| 37 | <%= required labelled_form_field(_('Title'), text_field(:comment, :title)) %> | 39 | <%= required labelled_form_field(_('Title'), text_field(:comment, :title)) %> |
| 38 | <%= required labelled_form_field(_('Enter your comment'), text_area(:comment, :body, :rows => 5)) %> | 40 | <%= required labelled_form_field(_('Enter your comment'), text_area(:comment, :body, :rows => 5)) %> |
| 39 | <% button_bar do %> | 41 | <% button_bar do %> |
| 40 | - <%= submit_button('add', _('Post comment'), :onclick => "$('confirm').value = 'true'; this.disabled = true; this.form.submit(); return true;") %> | 42 | + <%= submit_button('add', _('Post comment'), :onclick => "this.form.confirm.value = 'true'; this.disabled = true; this.form.submit(); return true;") %> |
| 43 | + <%= button_to_function :cancel, _('Cancel'), "f=jQuery(this).parents('.post_comment_box'); f.removeClass('opened'); f.addClass('closed'); return false" %> | ||
| 41 | <% end %> | 44 | <% end %> |
| 42 | <% end %> | 45 | <% end %> |
| 43 | 46 |
app/views/content_viewer/view_page.rhtml
| @@ -120,14 +120,15 @@ | @@ -120,14 +120,15 @@ | ||
| 120 | </div> | 120 | </div> |
| 121 | <% end %> | 121 | <% end %> |
| 122 | 122 | ||
| 123 | -<div class="comments"> | ||
| 124 | - <a name="comments_list"></a> | 123 | +<div class="comments" id="comments_list"> |
| 125 | <% if @page.accept_comments? %> | 124 | <% if @page.accept_comments? %> |
| 126 | <h3 <%= 'class="no-comments-yet"' if @comments.size == 0 %>> | 125 | <h3 <%= 'class="no-comments-yet"' if @comments.size == 0 %>> |
| 127 | <%= number_of_comments(@page) %> | 126 | <%= number_of_comments(@page) %> |
| 128 | </h3> | 127 | </h3> |
| 129 | - <%= render :partial => 'comment', :collection => @comments %> | ||
| 130 | - <%= render :partial => 'comment_form' %> | 128 | + <ul class="article-comments-list"> |
| 129 | + <%= render :partial => 'comment', :collection => @comments %> | ||
| 130 | + </ul> | ||
| 131 | + <div id="page-comment-form"><%= render :partial => 'comment_form' %></div> | ||
| 131 | <% end %> | 132 | <% end %> |
| 132 | </div><!-- end class="comments" --> | 133 | </div><!-- end class="comments" --> |
| 133 | 134 |
| @@ -0,0 +1,90 @@ | @@ -0,0 +1,90 @@ | ||
| 1 | +Feature: comment | ||
| 2 | + As a visitor | ||
| 3 | + I want to reply comments | ||
| 4 | + | ||
| 5 | + Background: | ||
| 6 | + Given the following users | ||
| 7 | + | login | | ||
| 8 | + | booking | | ||
| 9 | + And the following articles | ||
| 10 | + | owner | name | | ||
| 11 | + | booking | article to comment | | ||
| 12 | + | booking | another article | | ||
| 13 | + And the following comments | ||
| 14 | + | article | author | title | body | | ||
| 15 | + | article to comment | booking | root comment | this comment is not a reply | | ||
| 16 | + | another article | booking | some comment | this is my very own comment | | ||
| 17 | + | ||
| 18 | + Scenario: not post a comment without javascript | ||
| 19 | + Given I am on /booking/article-to-comment | ||
| 20 | + When I follow "Reply" within ".comment-balloon" | ||
| 21 | + Then I should not see "Enter your comment" within "div.comment-balloon" | ||
| 22 | + | ||
| 23 | + Scenario: not show any reply form by default | ||
| 24 | + When I go to /booking/article-to-comment | ||
| 25 | + Then I should not see "Enter your comment" within "div.comment-balloon" | ||
| 26 | + And I should see "Reply" within "div.comment-balloon" | ||
| 27 | + | ||
| 28 | + @selenium | ||
| 29 | + Scenario: show error messages when make a blank comment reply | ||
| 30 | + Given I am logged in as "booking" | ||
| 31 | + And I go to /booking/article-to-comment | ||
| 32 | + And I follow "Reply" within ".comment-balloon" | ||
| 33 | + When I press "Post comment" within ".comment-balloon" | ||
| 34 | + Then I should see "Title can't be blank" within "div.comment_reply" | ||
| 35 | + And I should see "Body can't be blank" within "div.comment_reply" | ||
| 36 | + | ||
| 37 | + @selenium | ||
| 38 | + Scenario: not show any reply form by default | ||
| 39 | + When I go to /booking/article-to-comment | ||
| 40 | + Then I should not see "Enter your comment" within "div.comment-balloon" | ||
| 41 | + And I should see "Reply" within "div.comment-balloon" | ||
| 42 | + | ||
| 43 | + @selenium | ||
| 44 | + Scenario: render reply form | ||
| 45 | + Given I am on /booking/article-to-comment | ||
| 46 | + When I follow "Reply" within ".comment-balloon" | ||
| 47 | + Then I should see "Enter your comment" within "div.comment_reply.opened" | ||
| 48 | + | ||
| 49 | + @selenium | ||
| 50 | + Scenario: cancel comment reply | ||
| 51 | + Given I am on /booking/article-to-comment | ||
| 52 | + When I follow "Reply" within ".comment-balloon" | ||
| 53 | + And I follow "Cancel" within ".comment-balloon" | ||
| 54 | + Then I should see "Enter your comment" within "div.comment_reply.closed" | ||
| 55 | + | ||
| 56 | + @selenium | ||
| 57 | + Scenario: not render same reply form twice | ||
| 58 | + Given I am on /booking/article-to-comment | ||
| 59 | + When I follow "Reply" within ".comment-balloon" | ||
| 60 | + And I follow "Cancel" within ".comment-balloon" | ||
| 61 | + And I follow "Reply" within ".comment-balloon" | ||
| 62 | + Then there should be 1 "comment_form" within "comment_reply" | ||
| 63 | + And I should see "Enter your comment" within "div.comment_reply.opened" | ||
| 64 | + | ||
| 65 | + @selenium | ||
| 66 | + Scenario: reply a comment | ||
| 67 | + Given I am logged in as "booking" | ||
| 68 | + And I go to /booking/another-article | ||
| 69 | + And I follow "Reply" within ".comment-balloon" | ||
| 70 | + And I fill in "Title" within "comment-balloon" with "Hey ho, let's go!" | ||
| 71 | + And I fill in "Enter your comment" within "comment-balloon" with "Hey ho, let's go!" | ||
| 72 | + When I press "Post comment" within ".comment-balloon" | ||
| 73 | + Then I should see "Hey ho, let's go" within "ul.comment-replies" | ||
| 74 | + And there should be 1 "comment-replies" within "article-comment" | ||
| 75 | + | ||
| 76 | + @selenium | ||
| 77 | + Scenario: redirect to right place after reply a picture comment | ||
| 78 | + Given the following files | ||
| 79 | + | owner | file | mime | | ||
| 80 | + | booking | rails.png | image/png | | ||
| 81 | + And the following comment | ||
| 82 | + | article | author | title | body | | ||
| 83 | + | rails.png | booking | root comment | this comment is not a reply | | ||
| 84 | + Given I am logged in as "booking" | ||
| 85 | + And I go to /booking/rails.png?view=true | ||
| 86 | + And I follow "Reply" within ".comment-balloon" | ||
| 87 | + And I fill in "Title" within "comment-balloon" with "Hey ho, let's go!" | ||
| 88 | + And I fill in "Enter your comment" within "comment-balloon" with "Hey ho, let's go!" | ||
| 89 | + When I press "Post comment" within ".comment-balloon" | ||
| 90 | + Then I should be exactly on /booking/rails.png?view=true |
features/step_definitions/noosfero_steps.rb
| @@ -307,3 +307,13 @@ Given /^the articles of "(.+)" are moderated$/ do |organization| | @@ -307,3 +307,13 @@ Given /^the articles of "(.+)" are moderated$/ do |organization| | ||
| 307 | organization.moderated_articles = true | 307 | organization.moderated_articles = true |
| 308 | organization.save | 308 | organization.save |
| 309 | end | 309 | end |
| 310 | + | ||
| 311 | +Given /^the following comments?$/ do |table| | ||
| 312 | + table.hashes.each do |item| | ||
| 313 | + data = item.dup | ||
| 314 | + article = Article.find_by_name(data.delete("article")) | ||
| 315 | + author = Profile[data.delete("author")] | ||
| 316 | + comment = article.comments.build(:author => author, :title => data.delete("title"), :body => data.delete("body")) | ||
| 317 | + comment.save! | ||
| 318 | + end | ||
| 319 | +end |
features/step_definitions/selenium_steps.rb
| @@ -66,6 +66,20 @@ When /^I select window "([^\"]*)"$/ do |selector| | @@ -66,6 +66,20 @@ When /^I select window "([^\"]*)"$/ do |selector| | ||
| 66 | selenium.select_window(selector) | 66 | selenium.select_window(selector) |
| 67 | end | 67 | end |
| 68 | 68 | ||
| 69 | +When /^I fill in "([^\"]*)" within "([^\"]*)" with "([^\"]*)"$/ do |field_label, parent_class, value| | ||
| 70 | + selenium.type("xpath=//*[contains(@class, '#{parent_class}')]//*[@id=//label[contains(., '#{field_label}')]/@for]", value) | ||
| 71 | +end | ||
| 72 | + | ||
| 73 | +When /^I press "([^\"]*)" within "([^\"]*)"$/ do |button_value, selector| | ||
| 74 | + selenium.click("css=#{selector} input[value=#{button_value}]") | ||
| 75 | + selenium.wait_for_page_to_load(10000) | ||
| 76 | +end | ||
| 77 | + | ||
| 78 | +Then /^there should be ([1-9][0-9]*) "([^\"]*)" within "([^\"]*)"$/ do |number, child_class, parent_class| | ||
| 79 | + # Using xpath is the only way to count | ||
| 80 | + response.selenium.get_xpath_count("//*[contains(@class,'#{parent_class}')]//*[contains(@class,'#{child_class}')]").to_i.should be(number.to_i) | ||
| 81 | +end | ||
| 82 | + | ||
| 69 | #### Noosfero specific steps #### | 83 | #### Noosfero specific steps #### |
| 70 | 84 | ||
| 71 | Then /^the select for category "([^\"]*)" should be visible$/ do |name| | 85 | Then /^the select for category "([^\"]*)" should be visible$/ do |name| |
features/step_definitions/webrat_steps.rb
| @@ -125,8 +125,12 @@ Then /^I should see "([^\"]*)"$/ do |text| | @@ -125,8 +125,12 @@ Then /^I should see "([^\"]*)"$/ do |text| | ||
| 125 | end | 125 | end |
| 126 | 126 | ||
| 127 | Then /^I should see "([^\"]*)" within "([^\"]*)"$/ do |text, selector| | 127 | Then /^I should see "([^\"]*)" within "([^\"]*)"$/ do |text, selector| |
| 128 | - within(selector) do |content| | ||
| 129 | - content.should contain(text) | 128 | + if response.class.to_s == 'Webrat::SeleniumResponse' |
| 129 | + response.selenium.text('css=' + selector).should include(text) | ||
| 130 | + else | ||
| 131 | + within(selector) do |content| | ||
| 132 | + content.should contain(text) | ||
| 133 | + end | ||
| 130 | end | 134 | end |
| 131 | end | 135 | end |
| 132 | 136 | ||
| @@ -147,8 +151,12 @@ Then /^I should not see "([^\"]*)"$/ do |text| | @@ -147,8 +151,12 @@ Then /^I should not see "([^\"]*)"$/ do |text| | ||
| 147 | end | 151 | end |
| 148 | 152 | ||
| 149 | Then /^I should not see "([^\"]*)" within "([^\"]*)"$/ do |text, selector| | 153 | Then /^I should not see "([^\"]*)" within "([^\"]*)"$/ do |text, selector| |
| 150 | - within(selector) do |content| | ||
| 151 | - content.should_not contain(text) | 154 | + if response.class.to_s == 'Webrat::SeleniumResponse' |
| 155 | + response.selenium.text('css=' + selector).should_not include(text) | ||
| 156 | + else | ||
| 157 | + within(selector) do |content| | ||
| 158 | + content.should_not contain(text) | ||
| 159 | + end | ||
| 152 | end | 160 | end |
| 153 | end | 161 | end |
| 154 | 162 | ||
| @@ -187,4 +195,3 @@ end | @@ -187,4 +195,3 @@ end | ||
| 187 | Then /^show me the page$/ do | 195 | Then /^show me the page$/ do |
| 188 | save_and_open_page | 196 | save_and_open_page |
| 189 | end | 197 | end |
| 190 | - |
public/designs/icons/tango/style.css
| @@ -84,3 +84,5 @@ | @@ -84,3 +84,5 @@ | ||
| 84 | .icon-gallery { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) } | 84 | .icon-gallery { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) } |
| 85 | .icon-newgallery { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) } | 85 | .icon-newgallery { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) } |
| 86 | .icon-locale { background-image: url(Tango/16x16/apps/preferences-desktop-locale.png) } | 86 | .icon-locale { background-image: url(Tango/16x16/apps/preferences-desktop-locale.png) } |
| 87 | +.icon-user-removed { background-image: url(Tango/16x16/actions/gtk-cancel.png) } | ||
| 88 | +.icon-user-unknown { background-image: url(Tango/16x16/status/dialog-error.png) } |
public/designs/themes/base/style.css
| @@ -1111,22 +1111,22 @@ hr.pre-posts, hr.sep-posts { | @@ -1111,22 +1111,22 @@ hr.pre-posts, hr.sep-posts { | ||
| 1111 | background: url(imgs/comment-owner-bg-NO.png) 0% 0% no-repeat; | 1111 | background: url(imgs/comment-owner-bg-NO.png) 0% 0% no-repeat; |
| 1112 | } | 1112 | } |
| 1113 | 1113 | ||
| 1114 | - | ||
| 1115 | .comment-created-at { | 1114 | .comment-created-at { |
| 1116 | position: relative; | 1115 | position: relative; |
| 1117 | - padding-right: 10px; | 1116 | + padding-right: 9px; |
| 1118 | } | 1117 | } |
| 1118 | + | ||
| 1119 | .comment-from-owner .comment-created-at { | 1119 | .comment-from-owner .comment-created-at { |
| 1120 | color: #333; | 1120 | color: #333; |
| 1121 | } | 1121 | } |
| 1122 | 1122 | ||
| 1123 | - | ||
| 1124 | .article-comment .button-bar { | 1123 | .article-comment .button-bar { |
| 1125 | position: relative; | 1124 | position: relative; |
| 1126 | - top: 3px; | ||
| 1127 | - right: 1px; | 1125 | + top: 9px; |
| 1126 | + right: 8px; | ||
| 1128 | z-index: 10px; | 1127 | z-index: 10px; |
| 1129 | } | 1128 | } |
| 1129 | + | ||
| 1130 | .article-comment .button-bar a { | 1130 | .article-comment .button-bar a { |
| 1131 | position: relative; | 1131 | position: relative; |
| 1132 | } | 1132 | } |
| @@ -1136,8 +1136,6 @@ hr.pre-posts, hr.sep-posts { | @@ -1136,8 +1136,6 @@ hr.pre-posts, hr.sep-posts { | ||
| 1136 | padding: 7px 12px 3px 26px; | 1136 | padding: 7px 12px 3px 26px; |
| 1137 | } | 1137 | } |
| 1138 | 1138 | ||
| 1139 | - | ||
| 1140 | - | ||
| 1141 | /* ==> controllers.css <== */ | 1139 | /* ==> controllers.css <== */ |
| 1142 | 1140 | ||
| 1143 | /******** controller-friends action-friends-index ********/ | 1141 | /******** controller-friends action-friends-index ********/ |
178 Bytes
308 Bytes
public/javascripts/application.js
| @@ -649,3 +649,20 @@ jQuery(function($) { | @@ -649,3 +649,20 @@ jQuery(function($) { | ||
| 649 | }) | 649 | }) |
| 650 | } | 650 | } |
| 651 | }); | 651 | }); |
| 652 | + | ||
| 653 | +function add_comment_reply_form(button, comment_id) { | ||
| 654 | + var container = jQuery(button).parents('.comment_reply'); | ||
| 655 | + var f = container.find('.comment_form'); | ||
| 656 | + if (f.length == 0) { | ||
| 657 | + f = jQuery('#page-comment-form .comment_form').clone(); | ||
| 658 | + f.find('.fieldWithErrors').map(function() { jQuery(this).replaceWith(jQuery(this).contents()); }); | ||
| 659 | + f.prepend('<input type="hidden" name="comment[reply_of_id]" value="' + comment_id + '" />'); | ||
| 660 | + container.append(f); | ||
| 661 | + } | ||
| 662 | + if (container.hasClass('closed')) { | ||
| 663 | + container.removeClass('closed'); | ||
| 664 | + container.addClass('opened'); | ||
| 665 | + container.find('.comment_form input[type=text]:visible:first').focus(); | ||
| 666 | + } | ||
| 667 | + return f; | ||
| 668 | +} |
public/stylesheets/application.css
| @@ -1025,11 +1025,6 @@ code input { | @@ -1025,11 +1025,6 @@ code input { | ||
| 1025 | padding: 4px; | 1025 | padding: 4px; |
| 1026 | } | 1026 | } |
| 1027 | 1027 | ||
| 1028 | -.comment-from-owner { | ||
| 1029 | - border: 1px solid #888; | ||
| 1030 | - background: #eee; | ||
| 1031 | -} | ||
| 1032 | - | ||
| 1033 | #article .article-comment h4 { | 1028 | #article .article-comment h4 { |
| 1034 | font-size: 13px; | 1029 | font-size: 13px; |
| 1035 | margin: 0px; | 1030 | margin: 0px; |
| @@ -1042,7 +1037,6 @@ code input { | @@ -1042,7 +1037,6 @@ code input { | ||
| 1042 | width: 70px; | 1037 | width: 70px; |
| 1043 | background-repeat: no-repeat; | 1038 | background-repeat: no-repeat; |
| 1044 | background-position: 0% 0%; | 1039 | background-position: 0% 0%; |
| 1045 | - padding-top: 50px; | ||
| 1046 | } | 1040 | } |
| 1047 | 1041 | ||
| 1048 | a.comment-picture { | 1042 | a.comment-picture { |
| @@ -1061,8 +1055,8 @@ a.comment-picture { | @@ -1061,8 +1055,8 @@ a.comment-picture { | ||
| 1061 | display: inline; | 1055 | display: inline; |
| 1062 | } | 1056 | } |
| 1063 | 1057 | ||
| 1064 | -.comment-details { | ||
| 1065 | - margin-left: 100px; | 1058 | +.comment-info { |
| 1059 | + display: block; | ||
| 1066 | } | 1060 | } |
| 1067 | 1061 | ||
| 1068 | .comment-from-owner .comment-info { | 1062 | .comment-from-owner .comment-info { |
| @@ -1071,12 +1065,265 @@ a.comment-picture { | @@ -1071,12 +1065,265 @@ a.comment-picture { | ||
| 1071 | 1065 | ||
| 1072 | .comment-text { | 1066 | .comment-text { |
| 1073 | font-size: 11px; | 1067 | font-size: 11px; |
| 1068 | + padding-right: 10px; | ||
| 1074 | } | 1069 | } |
| 1075 | 1070 | ||
| 1076 | .comment-logged-out .comment-text { | 1071 | .comment-logged-out .comment-text { |
| 1077 | color: #888; | 1072 | color: #888; |
| 1078 | } | 1073 | } |
| 1079 | 1074 | ||
| 1075 | +.comment-created-at { | ||
| 1076 | + padding-right: 9px; | ||
| 1077 | +} | ||
| 1078 | + | ||
| 1079 | +#content .comment-from-owner input.button, | ||
| 1080 | +#content .comment-from-owner a.button { | ||
| 1081 | + border-color: #646464; | ||
| 1082 | +} | ||
| 1083 | + | ||
| 1084 | +.article-comment .button-bar { | ||
| 1085 | + top: 9px; | ||
| 1086 | + right: 8px; | ||
| 1087 | +} | ||
| 1088 | + | ||
| 1089 | +.msie7 .article-comments-list .comment-balloon { | ||
| 1090 | + margin-top: -15px; | ||
| 1091 | +} | ||
| 1092 | + | ||
| 1093 | +.article-comments-list .comment-balloon br { | ||
| 1094 | + line-height: 0; | ||
| 1095 | +} | ||
| 1096 | + | ||
| 1097 | +.article-comments-list .comment-balloon div#errorExplanation h2 { | ||
| 1098 | + margin-left: 20px; | ||
| 1099 | + font-size: 14px; | ||
| 1100 | +} | ||
| 1101 | + | ||
| 1102 | +.article-comments-list .comment-balloon div#errorExplanation p, | ||
| 1103 | +.article-comments-list .comment-balloon div#errorExplanation ul, | ||
| 1104 | +.article-comments-list .comment-balloon div#errorExplanation li { | ||
| 1105 | + margin: 0; | ||
| 1106 | + margin-left: 20px; | ||
| 1107 | + text-align: left; | ||
| 1108 | +} | ||
| 1109 | + | ||
| 1110 | +.article-comments-list .comment-balloon div#errorExplanation li { | ||
| 1111 | + list-style: circle; | ||
| 1112 | +} | ||
| 1113 | + | ||
| 1114 | +.comment-user-status { | ||
| 1115 | + font-size: 9px; | ||
| 1116 | + text-indent: -5000em; | ||
| 1117 | + width: 16px; | ||
| 1118 | + height: 16px; | ||
| 1119 | + display: block; | ||
| 1120 | + position: absolute; | ||
| 1121 | + top: 33px; | ||
| 1122 | + left: 33px; | ||
| 1123 | + background-repeat: no-repeat; | ||
| 1124 | +} | ||
| 1125 | + | ||
| 1126 | +#article .article-comments-list, | ||
| 1127 | +#article .article-comments-list ul, | ||
| 1128 | +#article .article-comments-list li { | ||
| 1129 | + padding: 0; | ||
| 1130 | + margin: 0; | ||
| 1131 | + margin-bottom: 10px; | ||
| 1132 | + list-style: none; | ||
| 1133 | +} | ||
| 1134 | + | ||
| 1135 | +.article-comment .button-bar { | ||
| 1136 | + margin: 0; | ||
| 1137 | +} | ||
| 1138 | + | ||
| 1139 | +.article-comment .comment-details { | ||
| 1140 | + margin: 0px; | ||
| 1141 | + padding: 7px 1px 3px 26px; | ||
| 1142 | +} | ||
| 1143 | + | ||
| 1144 | +#article .comment-reply-link { | ||
| 1145 | + font-size: 10px; | ||
| 1146 | + text-decoration: none; | ||
| 1147 | + color: #000; | ||
| 1148 | +} | ||
| 1149 | + | ||
| 1150 | +#article .comment-reply-link:hover { | ||
| 1151 | + text-decoration: underline; | ||
| 1152 | +} | ||
| 1153 | + | ||
| 1154 | +#article .opened .comment-reply-link { | ||
| 1155 | + visibility: hidden; | ||
| 1156 | +} | ||
| 1157 | + | ||
| 1158 | +.comment_form div.fieldWithErrors input { | ||
| 1159 | + border-color: #999; | ||
| 1160 | + border-width: 0 1px 1px 0; | ||
| 1161 | + background: transparent url("../images/input-bg.gif") no-repeat top left; | ||
| 1162 | +} | ||
| 1163 | + | ||
| 1164 | +.comment_form div.fieldWithErrors { | ||
| 1165 | + background: transparent; | ||
| 1166 | +} | ||
| 1167 | + | ||
| 1168 | +/* * * Comment Replies * * */ | ||
| 1169 | + | ||
| 1170 | +.comment-replies .comment-wrapper-1 { | ||
| 1171 | + margin-left: 45px; | ||
| 1172 | +} | ||
| 1173 | + | ||
| 1174 | +.comment-replies .comment-wrapper-1, | ||
| 1175 | +.comment-replies .comment-wrapper-2, | ||
| 1176 | +.comment-replies .comment-wrapper-3, | ||
| 1177 | +.comment-replies .comment-wrapper-4, | ||
| 1178 | +.comment-replies .comment-wrapper-5, | ||
| 1179 | +.comment-replies .comment-wrapper-6, | ||
| 1180 | +.comment-replies .comment-wrapper-7, | ||
| 1181 | +.comment-replies .comment-wrapper-8, | ||
| 1182 | +.comment-replies .comment-from-owner .comment-wrapper-1, | ||
| 1183 | +.comment-replies .comment-from-owner .comment-wrapper-2, | ||
| 1184 | +.comment-replies .comment-from-owner .comment-wrapper-3, | ||
| 1185 | +.comment-replies .comment-from-owner .comment-wrapper-4, | ||
| 1186 | +.comment-replies .comment-from-owner .comment-wrapper-5, | ||
| 1187 | +.comment-replies .comment-from-owner .comment-wrapper-6, | ||
| 1188 | +.comment-replies .comment-from-owner .comment-wrapper-7, | ||
| 1189 | +.comment-replies .comment-from-owner .comment-wrapper-8 { | ||
| 1190 | + background: transparent; | ||
| 1191 | +} | ||
| 1192 | + | ||
| 1193 | +.comment-replies .comment-from-owner.comment-content { | ||
| 1194 | + background: transparent url(/images/comment-reply-owner-bg.png) left top repeat-x; | ||
| 1195 | +} | ||
| 1196 | + | ||
| 1197 | +.comment-replies .comment-user-status { | ||
| 1198 | + top: 15px; | ||
| 1199 | + left: 15px; | ||
| 1200 | +} | ||
| 1201 | + | ||
| 1202 | +#article .article-comments-list .comment-replies li { | ||
| 1203 | + margin-bottom: 2px; | ||
| 1204 | +} | ||
| 1205 | + | ||
| 1206 | +.comment-replies .comment-balloon div#errorExplanation h2 { | ||
| 1207 | + margin-left: 0; | ||
| 1208 | +} | ||
| 1209 | + | ||
| 1210 | +.comment-replies .comment-text, | ||
| 1211 | +.comment-replies .comment-created-at { | ||
| 1212 | + padding-right: 7px; | ||
| 1213 | +} | ||
| 1214 | + | ||
| 1215 | +.comment-replies .comment-picture { | ||
| 1216 | + width: 40px; | ||
| 1217 | + height: 57px; | ||
| 1218 | + overflow: hidden; | ||
| 1219 | +} | ||
| 1220 | + | ||
| 1221 | +.comment-replies .comment-picture img { | ||
| 1222 | + width: 32px; | ||
| 1223 | + height: 32px; | ||
| 1224 | +} | ||
| 1225 | + | ||
| 1226 | +.article-comment .comment-replies .button-bar { | ||
| 1227 | + right: 7px; | ||
| 1228 | + top: 2px; | ||
| 1229 | +} | ||
| 1230 | + | ||
| 1231 | +.article-comment form .button-bar, | ||
| 1232 | +.article-comment .comment-replies form .button-bar { | ||
| 1233 | + right: 0; | ||
| 1234 | + top: 0; | ||
| 1235 | +} | ||
| 1236 | + | ||
| 1237 | +.comment-replies .comment-details { | ||
| 1238 | + padding-top: 0; | ||
| 1239 | + padding-left: 0; | ||
| 1240 | +} | ||
| 1241 | + | ||
| 1242 | +#article .article-comments-list .comment-replies { | ||
| 1243 | + padding-left: 74px; | ||
| 1244 | + margin-top: 2px; | ||
| 1245 | +} | ||
| 1246 | + | ||
| 1247 | +#article .comment-replies .comment-replies { | ||
| 1248 | + padding-left: 10px; | ||
| 1249 | +} | ||
| 1250 | + | ||
| 1251 | +#article .comment-replies .comment-replies .article-comment, | ||
| 1252 | +#article .comment-replies .comment-replies .article-comment-inner { | ||
| 1253 | + border-right: 0; | ||
| 1254 | + margin-right: -1px; | ||
| 1255 | + padding-right: 1px; | ||
| 1256 | +} | ||
| 1257 | + | ||
| 1258 | +.comment-replies .comment-content { | ||
| 1259 | + padding: 4px 0 0 4px; | ||
| 1260 | + -moz-border-radius: 3px; | ||
| 1261 | + -webkit-border-radius: 3px; | ||
| 1262 | + border-radius: 3px; | ||
| 1263 | +} | ||
| 1264 | + | ||
| 1265 | +#article .comment-replies .article-comment { | ||
| 1266 | + border: 1px solid #808080; | ||
| 1267 | + padding: 0; | ||
| 1268 | + margin-bottom: 10px; | ||
| 1269 | + background: transparent url(/images/black-alpha-pixel-5.png) left top repeat; | ||
| 1270 | + -moz-border-radius: 5px; | ||
| 1271 | + -webkit-border-radius: 5px; | ||
| 1272 | + border-radius: 5px; | ||
| 1273 | +} | ||
| 1274 | + | ||
| 1275 | +.comment-replies .article-comment-inner { | ||
| 1276 | + border: 1px solid #fff; | ||
| 1277 | + padding: 0; | ||
| 1278 | + -moz-border-radius: 4px; | ||
| 1279 | + -webkit-border-radius: 4px; | ||
| 1280 | + border-radius: 4px; | ||
| 1281 | +} | ||
| 1282 | + | ||
| 1283 | +#article .comment-replies .comment-replies .article-comment { | ||
| 1284 | + -moz-border-radius: 5px 0 0 5px; | ||
| 1285 | + -webkit-border-radius: 5px 0 0 5px; | ||
| 1286 | + border-radius: 5px 0 0 5px; | ||
| 1287 | +} | ||
| 1288 | + | ||
| 1289 | +.comment-replies .comment-replies .article-comment-inner { | ||
| 1290 | + -moz-border-radius: 4px 0 0 4px; | ||
| 1291 | + -webkit-border-radius: 4px 0 0 4px; | ||
| 1292 | + border-radius: 4px 0 0 4px; | ||
| 1293 | +} | ||
| 1294 | + | ||
| 1295 | +.comment-replies .comment-replies .comment-content { | ||
| 1296 | + -moz-border-radius: 3px 0 0 3px; | ||
| 1297 | + -webkit-border-radius: 3px 0 0 3px; | ||
| 1298 | + border-radius: 3px 0 0 3px; | ||
| 1299 | +} | ||
| 1300 | + | ||
| 1301 | +#content .comment-replies a.button, | ||
| 1302 | +#content .comment-replies input.button { | ||
| 1303 | + border-color: #808080; | ||
| 1304 | +} | ||
| 1305 | + | ||
| 1306 | +.comment-replies .comment_reply { | ||
| 1307 | + padding-right: 9px; | ||
| 1308 | +} | ||
| 1309 | + | ||
| 1310 | +.comment-replies .comment-info, | ||
| 1311 | +.comment-replies .comment-created-at, | ||
| 1312 | +.comment-replies .comment-logged-out .comment-text, | ||
| 1313 | +.comment-logged-out h4 { | ||
| 1314 | + color: #000; | ||
| 1315 | +} | ||
| 1316 | + | ||
| 1317 | +.comment-replies .comment-created-at { | ||
| 1318 | + opacity: 0.4; | ||
| 1319 | +} | ||
| 1320 | + | ||
| 1321 | +.comment-replies .comment-logged-out .comment-text, | ||
| 1322 | +.comment-logged-out .comment-picture, | ||
| 1323 | +.comment-logged-out h4 { | ||
| 1324 | + opacity: 0.6; | ||
| 1325 | +} | ||
| 1326 | + | ||
| 1080 | /* * * Comment Box * * */ | 1327 | /* * * Comment Box * * */ |
| 1081 | 1328 | ||
| 1082 | .post_comment_box { | 1329 | .post_comment_box { |
| @@ -1107,6 +1354,10 @@ a.comment-picture { | @@ -1107,6 +1354,10 @@ a.comment-picture { | ||
| 1107 | margin: -10px 0px 0px 0px; | 1354 | margin: -10px 0px 0px 0px; |
| 1108 | } | 1355 | } |
| 1109 | 1356 | ||
| 1357 | +.post_comment_box.opened form { | ||
| 1358 | + padding-bottom: 15px; | ||
| 1359 | +} | ||
| 1360 | + | ||
| 1110 | .post_comment_box .formfield * { | 1361 | .post_comment_box .formfield * { |
| 1111 | width: 99%; | 1362 | width: 99%; |
| 1112 | } | 1363 | } |
| @@ -1117,7 +1368,7 @@ a.comment-picture { | @@ -1117,7 +1368,7 @@ a.comment-picture { | ||
| 1117 | 1368 | ||
| 1118 | .post_comment_box input.button { | 1369 | .post_comment_box input.button { |
| 1119 | position: relative; | 1370 | position: relative; |
| 1120 | - float: none; | 1371 | + float: left; |
| 1121 | margin: auto; | 1372 | margin: auto; |
| 1122 | } | 1373 | } |
| 1123 | 1374 | ||
| @@ -1133,6 +1384,30 @@ a.comment-picture { | @@ -1133,6 +1384,30 @@ a.comment-picture { | ||
| 1133 | display: block; | 1384 | display: block; |
| 1134 | } | 1385 | } |
| 1135 | 1386 | ||
| 1387 | +.post_comment_box.comment_reply { | ||
| 1388 | + margin: 0; | ||
| 1389 | + text-align: right; | ||
| 1390 | + padding: 0 11px 5px 0; | ||
| 1391 | +} | ||
| 1392 | + | ||
| 1393 | +.comment_reply.post_comment_box.opened { | ||
| 1394 | + background: transparent; | ||
| 1395 | +} | ||
| 1396 | + | ||
| 1397 | +.comment_reply.post_comment_box form { | ||
| 1398 | + margin: 0; | ||
| 1399 | + padding-bottom: 27px; | ||
| 1400 | + padding-left: 26px; | ||
| 1401 | +} | ||
| 1402 | + | ||
| 1403 | +.comment-replies .comment_reply.post_comment_box form { | ||
| 1404 | + padding-left: 0; | ||
| 1405 | +} | ||
| 1406 | + | ||
| 1407 | +.post_comment_box.comment_reply #comment_title { | ||
| 1408 | + width: 100%; | ||
| 1409 | +} | ||
| 1410 | + | ||
| 1136 | /* * * addThis button * * */ | 1411 | /* * * addThis button * * */ |
| 1137 | 1412 | ||
| 1138 | .msie #addThis { | 1413 | .msie #addThis { |
test/factories.rb
| @@ -431,4 +431,9 @@ module Noosfero::Factory | @@ -431,4 +431,9 @@ module Noosfero::Factory | ||
| 431 | { :name => 'Sender', :email => 'sender@example.com', :article_name => 'Some title', :article_body => 'some body text', :article_abstract => 'some abstract text'} | 431 | { :name => 'Sender', :email => 'sender@example.com', :article_name => 'Some title', :article_body => 'some body text', :article_abstract => 'some abstract text'} |
| 432 | end | 432 | end |
| 433 | 433 | ||
| 434 | + def defaults_for_comment(params = {}) | ||
| 435 | + name = "comment_#{rand(1000)}" | ||
| 436 | + { :title => name, :body => "my own comment", :article_id => 1 }.merge(params) | ||
| 437 | + end | ||
| 438 | + | ||
| 434 | end | 439 | end |
test/functional/content_viewer_controller_test.rb
| @@ -242,7 +242,7 @@ class ContentViewerControllerTest < Test::Unit::TestCase | @@ -242,7 +242,7 @@ class ContentViewerControllerTest < Test::Unit::TestCase | ||
| 242 | 242 | ||
| 243 | get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ] | 243 | get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ] |
| 244 | 244 | ||
| 245 | - assert_tag :tag => 'form', :attributes => { :id => /^comment_form/, :action => '/person/article' } | 245 | + assert_tag :tag => 'form', :attributes => { :class => /^comment_form/, :action => '/person/article' } |
| 246 | end | 246 | end |
| 247 | 247 | ||
| 248 | should "display current article's tags" do | 248 | should "display current article's tags" do |
| @@ -928,7 +928,7 @@ class ContentViewerControllerTest < Test::Unit::TestCase | @@ -928,7 +928,7 @@ class ContentViewerControllerTest < Test::Unit::TestCase | ||
| 928 | 928 | ||
| 929 | get :view_page, :profile => profile.identifier, :page => article.explode_path | 929 | get :view_page, :profile => profile.identifier, :page => article.explode_path |
| 930 | 930 | ||
| 931 | - assert_tag :tag => 'span', :content => '(removed user)', :attributes => {:class => 'comment-info'} | 931 | + assert_tag :tag => 'span', :content => '(removed user)', :attributes => {:class => 'comment-user-status icon-user-removed'} |
| 932 | end | 932 | end |
| 933 | 933 | ||
| 934 | should 'show comment form opened on error' do | 934 | should 'show comment form opened on error' do |
| @@ -1245,4 +1245,75 @@ class ContentViewerControllerTest < Test::Unit::TestCase | @@ -1245,4 +1245,75 @@ class ContentViewerControllerTest < Test::Unit::TestCase | ||
| 1245 | assert_redirected_to :profile => @profile.identifier, :page => page.explode_path | 1245 | assert_redirected_to :profile => @profile.identifier, :page => page.explode_path |
| 1246 | end | 1246 | end |
| 1247 | 1247 | ||
| 1248 | + should 'display reply to comment button if authenticated' do | ||
| 1249 | + profile = create_user('testuser').person | ||
| 1250 | + article = profile.articles.build(:name => 'test') | ||
| 1251 | + article.save! | ||
| 1252 | + comment = article.comments.build(:author => profile, :title => 'a comment', :body => 'lalala') | ||
| 1253 | + comment.save! | ||
| 1254 | + login_as 'testuser' | ||
| 1255 | + get :view_page, :profile => 'testuser', :page => [ 'test' ] | ||
| 1256 | + assert_tag :tag => 'a', :attributes => { :class => /comment-reply-link/ } | ||
| 1257 | + end | ||
| 1258 | + | ||
| 1259 | + should 'display reply to comment button if not authenticated' do | ||
| 1260 | + profile = create_user('testuser').person | ||
| 1261 | + article = profile.articles.build(:name => 'test') | ||
| 1262 | + article.save! | ||
| 1263 | + comment = article.comments.build(:author => profile, :title => 'a comment', :body => 'lalala') | ||
| 1264 | + comment.save! | ||
| 1265 | + get :view_page, :profile => 'testuser', :page => [ 'test' ] | ||
| 1266 | + assert_tag :tag => 'a', :attributes => { :class => /comment-reply-link/ } | ||
| 1267 | + end | ||
| 1268 | + | ||
| 1269 | + should 'display replies if comment has replies' do | ||
| 1270 | + profile = create_user('testuser').person | ||
| 1271 | + article = profile.articles.build(:name => 'test') | ||
| 1272 | + article.save! | ||
| 1273 | + comment1 = article.comments.build(:author => profile, :title => 'a comment', :body => 'lalala') | ||
| 1274 | + comment1.save! | ||
| 1275 | + comment2 = article.comments.build(:author => profile, :title => 'a comment', :body => 'replying to lalala', :reply_of_id => comment1.id) | ||
| 1276 | + comment2.save! | ||
| 1277 | + get :view_page, :profile => 'testuser', :page => [ 'test' ] | ||
| 1278 | + assert_tag :tag => 'ul', :attributes => { :class => 'comment-replies' } | ||
| 1279 | + end | ||
| 1280 | + | ||
| 1281 | + should 'not display replies if comment does not have replies' do | ||
| 1282 | + profile = create_user('testuser').person | ||
| 1283 | + article = profile.articles.build(:name => 'test') | ||
| 1284 | + article.save! | ||
| 1285 | + comment = article.comments.build(:author => profile, :title => 'a comment', :body => 'lalala') | ||
| 1286 | + comment.save! | ||
| 1287 | + get :view_page, :profile => 'testuser', :page => [ 'test' ] | ||
| 1288 | + assert_no_tag :tag => 'ul', :attributes => { :class => 'comment-replies' } | ||
| 1289 | + end | ||
| 1290 | + | ||
| 1291 | + should 'show reply error' do | ||
| 1292 | + profile = create_user('testuser').person | ||
| 1293 | + article = profile.articles.build(:name => 'test') | ||
| 1294 | + article.save! | ||
| 1295 | + comment = article.comments.build(:author => profile, :title => 'root', :body => 'root') | ||
| 1296 | + comment.save! | ||
| 1297 | + login_as 'testuser' | ||
| 1298 | + post :view_page, :profile => profile.identifier, :page => ['test'], :comment => { :title => '', :body => '', :reply_of_id => comment.id }, :confirm => 'true' | ||
| 1299 | + assert_tag :tag => 'div', :attributes => { :class => /comment_reply/ }, :descendant => {:tag => 'div', :attributes => {:class => 'errorExplanation'} } | ||
| 1300 | + assert_no_tag :tag => 'div', :attributes => { :id => 'page-comment-form' }, :descendant => {:tag => 'div', :attributes => {:class => 'errorExplanation'} } | ||
| 1301 | + assert_tag :tag => 'div', :attributes => { :id => 'page-comment-form' }, :descendant => { :tag => 'div', :attributes => { :class => /post_comment_box closed/ } } | ||
| 1302 | + end | ||
| 1303 | + | ||
| 1304 | + should 'show comment error' do | ||
| 1305 | + profile = create_user('testuser').person | ||
| 1306 | + article = profile.articles.build(:name => 'test') | ||
| 1307 | + article.save! | ||
| 1308 | + comment1 = article.comments.build(:author => profile, :title => 'root', :body => 'root') | ||
| 1309 | + comment1.save! | ||
| 1310 | + comment2 = article.comments.build(:author => profile, :title => 'root', :body => 'root', :reply_of_id => comment1.id) | ||
| 1311 | + comment2.save! | ||
| 1312 | + login_as 'testuser' | ||
| 1313 | + post :view_page, :profile => profile.identifier, :page => ['test'], :comment => { :title => '', :body => '' }, :confirm => 'true' | ||
| 1314 | + assert_no_tag :tag => 'div', :attributes => { :class => /comment_reply/ }, :descendant => {:tag => 'div', :attributes => {:class => 'errorExplanation'} } | ||
| 1315 | + assert_tag :tag => 'div', :attributes => { :id => 'page-comment-form' }, :descendant => {:tag => 'div', :attributes => {:class => 'errorExplanation'} } | ||
| 1316 | + assert_tag :tag => 'div', :attributes => { :id => 'page-comment-form' }, :descendant => { :tag => 'div', :attributes => { :class => /post_comment_box opened/ } } | ||
| 1317 | + end | ||
| 1318 | + | ||
| 1248 | end | 1319 | end |
test/unit/comment_test.rb
| @@ -238,4 +238,83 @@ class CommentTest < Test::Unit::TestCase | @@ -238,4 +238,83 @@ class CommentTest < Test::Unit::TestCase | ||
| 238 | assert_equal owner, ta.target | 238 | assert_equal owner, ta.target |
| 239 | end | 239 | end |
| 240 | 240 | ||
| 241 | + should "get children of a comment" do | ||
| 242 | + c = fast_create(Comment) | ||
| 243 | + c1 = fast_create(Comment, :reply_of_id => c.id) | ||
| 244 | + c2 = fast_create(Comment) | ||
| 245 | + c3 = fast_create(Comment, :reply_of_id => c.id) | ||
| 246 | + assert_equal [c1,c3], c.children | ||
| 247 | + end | ||
| 248 | + | ||
| 249 | + should "get parent of a comment" do | ||
| 250 | + c = fast_create(Comment) | ||
| 251 | + c1 = fast_create(Comment, :reply_of_id => c.id) | ||
| 252 | + c2 = fast_create(Comment, :reply_of_id => c1.id) | ||
| 253 | + c3 = fast_create(Comment, :reply_of_id => c.id) | ||
| 254 | + c4 = fast_create(Comment) | ||
| 255 | + assert_equal c, c1.reply_of | ||
| 256 | + assert_equal c, c3.reply_of | ||
| 257 | + assert_equal c1, c2.reply_of | ||
| 258 | + assert_nil c4.reply_of | ||
| 259 | + end | ||
| 260 | + | ||
| 261 | + should 'destroy replies when comment is removed' do | ||
| 262 | + Comment.delete_all | ||
| 263 | + owner = create_user('testuser').person | ||
| 264 | + article = owner.articles.create!(:name => 'test', :body => '...') | ||
| 265 | + c = article.comments.create!(:article => article, :name => 'foo', :title => 'bar', :body => 'my comment', :email => 'cracker@test.org') | ||
| 266 | + c1 = article.comments.create!(:article => article, :name => 'foo', :title => 'bar', :body => 'my comment', :email => 'cracker@test.org', :reply_of_id => c.id) | ||
| 267 | + c2 = article.comments.create!(:article => article, :name => 'foo', :title => 'bar', :body => 'my comment', :email => 'cracker@test.org') | ||
| 268 | + c3 = article.comments.create!(:article => article, :name => 'foo', :title => 'bar', :body => 'my comment', :email => 'cracker@test.org', :reply_of_id => c.id) | ||
| 269 | + assert_equal 4, Comment.count | ||
| 270 | + c.destroy | ||
| 271 | + assert_equal [c2], Comment.all | ||
| 272 | + end | ||
| 273 | + | ||
| 274 | + should "get children if replies are not loaded" do | ||
| 275 | + c = fast_create(Comment) | ||
| 276 | + c1 = fast_create(Comment, :reply_of_id => c.id) | ||
| 277 | + c2 = fast_create(Comment) | ||
| 278 | + c3 = fast_create(Comment, :reply_of_id => c.id) | ||
| 279 | + assert_nil c.instance_variable_get('@replies') | ||
| 280 | + assert_equal [c1,c3], c.replies | ||
| 281 | + end | ||
| 282 | + | ||
| 283 | + should "get replies if they are loaded" do | ||
| 284 | + c = fast_create(Comment) | ||
| 285 | + c1 = fast_create(Comment, :reply_of_id => c.id) | ||
| 286 | + c2 = fast_create(Comment) | ||
| 287 | + c3 = fast_create(Comment, :reply_of_id => c.id) | ||
| 288 | + c.replies = [c2] | ||
| 289 | + assert_not_nil c.instance_variable_get('@replies') | ||
| 290 | + assert_equal [c2], c.replies | ||
| 291 | + end | ||
| 292 | + | ||
| 293 | + should "set replies" do | ||
| 294 | + c = fast_create(Comment) | ||
| 295 | + c1 = fast_create(Comment, :reply_of_id => c.id) | ||
| 296 | + c2 = fast_create(Comment) | ||
| 297 | + c3 = fast_create(Comment, :reply_of_id => c.id) | ||
| 298 | + c.replies = [] | ||
| 299 | + c.replies << c2 | ||
| 300 | + assert_equal [c2], c.instance_variable_get('@replies') | ||
| 301 | + assert_equal [c2], c.replies | ||
| 302 | + assert_equal [c1,c3], c.reload.children | ||
| 303 | + end | ||
| 304 | + | ||
| 305 | + should "return comments as a thread" do | ||
| 306 | + a = fast_create(Article) | ||
| 307 | + c0 = fast_create(Comment, :article_id => a.id) | ||
| 308 | + c1 = fast_create(Comment, :reply_of_id => c0.id, :article_id => a.id) | ||
| 309 | + c2 = fast_create(Comment, :reply_of_id => c1.id, :article_id => a.id) | ||
| 310 | + c3 = fast_create(Comment, :reply_of_id => c0.id, :article_id => a.id) | ||
| 311 | + c4 = fast_create(Comment, :article_id => a.id) | ||
| 312 | + result = a.comments.as_thread | ||
| 313 | + assert_equal c0.id, result[0].id | ||
| 314 | + assert_equal [c1.id, c3.id], result[0].replies.map(&:id) | ||
| 315 | + assert_equal [c2.id], result[0].replies[0].replies.map(&:id) | ||
| 316 | + assert_equal c4.id, result[1].id | ||
| 317 | + assert result[1].replies.empty? | ||
| 318 | + end | ||
| 319 | + | ||
| 241 | end | 320 | end |