Commit d26eed6e3c6c07d144112218f179ca595eaa8a24

Authored by Victor Costa
2 parents 8f8f1ad6 7e2363ea

Merge branch 'staging' into staging_rails4

Conflicts:
	app/helpers/forms_helper.rb
	app/views/cms/edit.html.erb
	lib/noosfero/api/entities.rb
	lib/noosfero/api/v1/comments.rb
	lib/noosfero/api/v1/search.rb
	test/unit/api/search_test.rb
Showing 44 changed files with 445 additions and 39 deletions   Show diff stats
app/helpers/article_helper.rb
@@ -15,6 +15,13 @@ module ArticleHelper @@ -15,6 +15,13 @@ module ArticleHelper
15 topic_creation(@article) + 15 topic_creation(@article) +
16 content_tag('h4', _('Options')) + 16 content_tag('h4', _('Options')) +
17 content_tag('div', 17 content_tag('div',
  18 + content_tag('div',
  19 + check_box(:article, :archived) +
  20 + content_tag('span', ' ', :class => 'access-archived-icon') +
  21 + content_tag('label', _('Read Only'), :for => 'article_archived_true') +
  22 + content_tag('span', _('Archive to avoid create comments, votes, actions in children articles...'), :class => 'access-note'),
  23 + :class => 'access-item'
  24 + ) +
18 (article.profile.has_members? ? 25 (article.profile.has_members? ?
19 content_tag( 26 content_tag(
20 'div', 27 'div',
@@ -63,13 +70,20 @@ module ArticleHelper @@ -63,13 +70,20 @@ module ArticleHelper
63 content_tag('div', 70 content_tag('div',
64 content_tag('div', 71 content_tag('div',
65 radio_button(:article, :published, true) + 72 radio_button(:article, :published, true) +
66 - content_tag('label', _('Public (visible to other people)'), :for => 'article_published_true') 73 + content_tag('span', ' ', :class => 'access-public-icon') +
  74 + content_tag('label', _('Public'), :for => 'article_published_true') +
  75 + content_tag('span', _('Visible to other people'), :class => 'access-note'),
  76 + :class => 'access-item'
67 ) + 77 ) +
68 content_tag('div', 78 content_tag('div',
69 radio_button(:article, :published, false) + 79 radio_button(:article, :published, false) +
70 - content_tag('label', _('Private'), :for => 'article_published_false', :id => "label_private") 80 + content_tag('span', ' ', :class => 'access-private-icon') +
  81 + content_tag('label', _('Private'), :for => 'article_published_false', :id => "label_private") +
  82 + content_tag('span', _('Limit visibility of this article'), :class => 'access-note'),
  83 + :class => 'access-item'
71 ) + 84 ) +
72 - privacity_exceptions(article, tokenized_children) 85 + privacity_exceptions(article, tokenized_children),
  86 + :class => 'access-itens'
73 ) 87 )
74 end 88 end
75 89
app/helpers/folder_helper.rb
@@ -59,7 +59,15 @@ module FolderHelper @@ -59,7 +59,15 @@ module FolderHelper
59 @article = article 59 @article = article
60 60
61 visibility_options(article,tokenized_children) + 61 visibility_options(article,tokenized_children) +
  62 + content_tag('h4', _('Options')) +
62 content_tag('div', 63 content_tag('div',
  64 + content_tag('div',
  65 + check_box(:article, :archived) +
  66 + content_tag('span', ' ', :class => 'access-archived-icon') +
  67 + content_tag('label', _('Read Only'), :for => 'article_archived_true') +
  68 + content_tag('span', _('Archive to avoid votes and create new children articles'), :class => 'access-note'),
  69 + :class => 'access-item'
  70 + ) +
63 hidden_field_tag('article[accept_comments]', 0) 71 hidden_field_tag('article[accept_comments]', 0)
64 ) 72 )
65 end 73 end
app/models/article.rb
@@ -9,7 +9,7 @@ class Article < ActiveRecord::Base @@ -9,7 +9,7 @@ class Article < ActiveRecord::Base
9 :highlighted, :notify_comments, :display_hits, :slug, 9 :highlighted, :notify_comments, :display_hits, :slug,
10 :external_feed_builder, :display_versions, :external_link, 10 :external_feed_builder, :display_versions, :external_link,
11 :author, :published_at, :person_followers, :show_to_followers, 11 :author, :published_at, :person_followers, :show_to_followers,
12 - :image_builder, :display_preview 12 + :image_builder, :display_preview, :archived
13 13
14 acts_as_having_image 14 acts_as_having_image
15 15
@@ -156,6 +156,8 @@ class Article < ActiveRecord::Base @@ -156,6 +156,8 @@ class Article < ActiveRecord::Base
156 validate :no_self_reference 156 validate :no_self_reference
157 validate :no_cyclical_reference, :if => 'parent_id.present?' 157 validate :no_cyclical_reference, :if => 'parent_id.present?'
158 158
  159 + validate :parent_archived?
  160 +
159 def no_self_reference 161 def no_self_reference
160 errors.add(:parent_id, _('self-reference is not allowed.')) if id && parent_id == id 162 errors.add(:parent_id, _('self-reference is not allowed.')) if id && parent_id == id
161 end 163 end
@@ -490,6 +492,10 @@ class Article < ActiveRecord::Base @@ -490,6 +492,10 @@ class Article < ActiveRecord::Base
490 end 492 end
491 end 493 end
492 494
  495 + def archived?
  496 + (self.parent && self.parent.archived) || self.archived
  497 + end
  498 +
493 def self.folder_types 499 def self.folder_types
494 ['Folder', 'Blog', 'Forum', 'Gallery'] 500 ['Folder', 'Blog', 'Forum', 'Gallery']
495 end 501 end
@@ -636,13 +642,21 @@ class Article < ActiveRecord::Base @@ -636,13 +642,21 @@ class Article < ActiveRecord::Base
636 end 642 end
637 643
638 def hit 644 def hit
639 - self.class.connection.execute('update articles set hits = hits + 1 where id = %d' % self.id.to_i)  
640 - self.hits += 1 645 + if !archived?
  646 + self.class.connection.execute('update articles set hits = hits + 1 where id = %d' % self.id.to_i)
  647 + self.hits += 1
  648 + end
641 end 649 end
642 650
643 def self.hit(articles) 651 def self.hit(articles)
644 - Article.where(:id => articles.map(&:id)).update_all('hits = hits + 1')  
645 - articles.each { |a| a.hits += 1 } 652 + ids = []
  653 + articles.each do |article|
  654 + if !article.archived?
  655 + ids << article.id
  656 + article.hits += 1
  657 + end
  658 + end
  659 + Article.where(:id => ids).update_all('hits = hits + 1') if !ids.empty?
646 end 660 end
647 661
648 def can_display_hits? 662 def can_display_hits?
@@ -856,4 +870,10 @@ class Article &lt; ActiveRecord::Base @@ -856,4 +870,10 @@ class Article &lt; ActiveRecord::Base
856 sanitizer.sanitize(text) 870 sanitizer.sanitize(text)
857 end 871 end
858 872
  873 + def parent_archived?
  874 + if self.parent_id_changed? && self.parent && self.parent.archived?
  875 + errors.add(:parent_folder, N_('is archived!!'))
  876 + end
  877 + end
  878 +
859 end 879 end
app/models/comment.rb
@@ -34,7 +34,9 @@ class Comment &lt; ActiveRecord::Base @@ -34,7 +34,9 @@ class Comment &lt; ActiveRecord::Base
34 rec.errors.add(:name, _('{fn} can only be informed for unauthenticated authors').fix_i18n) 34 rec.errors.add(:name, _('{fn} can only be informed for unauthenticated authors').fix_i18n)
35 end 35 end
36 end 36 end
37 - 37 +
  38 + validate :article_archived?
  39 +
38 acts_as_having_settings 40 acts_as_having_settings
39 41
40 xss_terminate :only => [ :body, :title, :name ], :on => 'validation' 42 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
@@ -210,4 +212,14 @@ class Comment &lt; ActiveRecord::Base @@ -210,4 +212,14 @@ class Comment &lt; ActiveRecord::Base
210 user.present? && user == author 212 user.present? && user == author
211 end 213 end
212 214
  215 + def archived?
  216 + self.article.archived? if self.article.present? && self.article.respond_to?(:archived?)
  217 + end
  218 +
  219 + protected
  220 +
  221 + def article_archived?
  222 + errors.add(:article, N_('associated with this comment is achived!')) if archived?
  223 + end
  224 +
213 end 225 end
app/models/folder.rb
@@ -66,4 +66,8 @@ class Folder &lt; Article @@ -66,4 +66,8 @@ class Folder &lt; Article
66 !self.has_posts? || self.gallery? 66 !self.has_posts? || self.gallery?
67 end 67 end
68 68
  69 + def archived?
  70 + self.archived
  71 + end
  72 +
69 end 73 end
app/models/user.rb
@@ -424,6 +424,12 @@ class User &lt; ActiveRecord::Base @@ -424,6 +424,12 @@ class User &lt; ActiveRecord::Base
424 @is_password_required = false 424 @is_password_required = false
425 end 425 end
426 426
  427 + def resend_activation_code
  428 + return if self.activated?
  429 + update_attribute(:activation_code, make_activation_code)
  430 + self.deliver_activation_code
  431 + end
  432 +
427 protected 433 protected
428 434
429 def normalize_email 435 def normalize_email
app/views/cms/_archived_warning.html.erb 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +<% if !article.errors.any? %>
  2 + <div class="text-warning">
  3 + <p>
  4 + <i class="icon"></i>
  5 + <%= _("Archived article! It's read-only") %>
  6 + </p>
  7 + </div>
  8 +<% end %>
app/views/cms/edit.html.erb
1 <%= error_messages_for 'article' %> 1 <%= error_messages_for 'article' %>
2 2
  3 +<% if @article.archived? %>
  4 + <%= render :partial => 'archived_warning', :locals => {:article => @article} %>
  5 +<% end %>
3 <div class='<%= (@article.display_media_panel? ? 'with_media_panel' : 'no_media_panel') %>'> 6 <div class='<%= (@article.display_media_panel? ? 'with_media_panel' : 'no_media_panel') %>'>
4 <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %> 7 <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %>
5 8
app/views/content_viewer/_article_toolbar.html.erb
@@ -29,7 +29,9 @@ @@ -29,7 +29,9 @@
29 <%= expirable_button @page, :locale, content, url %> 29 <%= expirable_button @page, :locale, content, url %>
30 <% end %> 30 <% end %>
31 31
32 - <%= modal_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : @page.parent))) unless remove_content_button(:new, @page) %> 32 + <% if !@page.archived? %>
  33 + <%= modal_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : @page.parent))) unless remove_content_button(:new, @page) %>
  34 + <% end %>
33 35
34 <% content = content_tag('span', label_for_clone_article(@page)) %> 36 <% content = content_tag('span', label_for_clone_article(@page)) %>
35 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'new', :id => @page.id, :clone => true, :parent_id => (@page.folder? ? @page : @page.parent), :type => @page.class}) %> 37 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'new', :id => @page.id, :clone => true, :parent_id => (@page.folder? ? @page : @page.parent), :type => @page.class}) %>
@@ -64,6 +66,9 @@ @@ -64,6 +66,9 @@
64 <% end %> 66 <% end %>
65 <%= button_without_text(:feed, _('RSS feed'), @page.feed.url, :class => 'blog-feed-link') if @page.has_posts? && @page.feed %> 67 <%= button_without_text(:feed, _('RSS feed'), @page.feed.url, :class => 'blog-feed-link') if @page.has_posts? && @page.feed %>
66 <%= @plugins.dispatch(:article_header_extra_contents, @page).collect { |content| instance_exec(&content) }.join("") %> 68 <%= @plugins.dispatch(:article_header_extra_contents, @page).collect { |content| instance_exec(&content) }.join("") %>
  69 + <% if @page.archived? %>
  70 + <%= render :partial => 'cms/archived_warning', :locals => {:article => @page} %>
  71 + <% end %>
67 <%= render :partial => 'article_title', :locals => {:no_link => true} %> 72 <%= render :partial => 'article_title', :locals => {:no_link => true} %>
68 <%= article_translations(@page) %> 73 <%= article_translations(@page) %>
69 </div> 74 </div>
app/views/content_viewer/_publishing_info.html.erb
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 <div id='article-sub-header'> 16 <div id='article-sub-header'>
17 <% if @page.display_hits? %> 17 <% if @page.display_hits? %>
18 <div id="article-hits"> 18 <div id="article-hits">
19 - <%= n_('Viewed one time', 'Viewed %{num} times', @page.hits) % { :num => @page.hits } %> 19 + <%= n_('Viewed one time %{desc}', 'Viewed %{num} times %{desc}', @page.hits) % { :num => @page.hits, :desc => @page.archived? ? '<b>'+_('(Not countable anymore)')+'</b>' : '' } %>
20 </div> 20 </div>
21 <% end %> 21 <% end %>
22 22
app/views/content_viewer/view_page.html.erb
@@ -85,7 +85,7 @@ @@ -85,7 +85,7 @@
85 <% end %> 85 <% end %>
86 </ul> 86 </ul>
87 87
88 - <% if @page.accept_comments? %> 88 + <% if !@page.archived? || @page.accept_comments? %>
89 <div id='page-comment-form' class='page-comment-form'><%= render :partial => 'comment/comment_form', :locals =>{:url => {:controller => :comment, :action => :create}, :display_link => true, :cancel_triggers_hide => true}%></div> 89 <div id='page-comment-form' class='page-comment-form'><%= render :partial => 'comment/comment_form', :locals =>{:url => {:controller => :comment, :action => :create}, :display_link => true, :cancel_triggers_hide => true}%></div>
90 <% end %> 90 <% end %>
91 </div><!-- end class="comments" --> 91 </div><!-- end class="comments" -->
db/migrate/20151112135709_add_archived_to_articles.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class AddArchivedToArticles < ActiveRecord::Migration
  2 + def up
  3 + add_column :articles, :archived, :boolean, :default => false
  4 + end
  5 +
  6 + def down
  7 + remove_column :articles, :archived
  8 + end
  9 +end
lib/noosfero/api/entities.rb
@@ -205,7 +205,7 @@ module Noosfero @@ -205,7 +205,7 @@ module Noosfero
205 205
206 class UserLogin < User 206 class UserLogin < User
207 root 'users', 'user' 207 root 'users', 'user'
208 - expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'} 208 + expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'}, if: lambda {|object, options| object.activated? }
209 end 209 end
210 210
211 class Task < Entity 211 class Task < Entity
lib/noosfero/api/session.rb
@@ -130,6 +130,25 @@ module Noosfero @@ -130,6 +130,25 @@ module Noosfero
130 end 130 end
131 end 131 end
132 132
  133 + # Resend activation code.
  134 + #
  135 + # Parameters:
  136 + # value (required) - Email or login
  137 + # Example Request:
  138 + # POST /resend_activation_code?value=some@mail.com
  139 + post "/resend_activation_code" do
  140 + requestors = fetch_requestors(params[:value])
  141 + not_found! if requestors.blank?
  142 + remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
  143 + # test_captcha will render_api_error! and exit in case of any problem
  144 + # this return is just to improve the clarity of the execution path
  145 + return unless test_captcha(remote_ip, params, environment)
  146 + requestors.each do |requestor|
  147 + requestor.user.resend_activation_code
  148 + end
  149 + present requestors.map(&:user), :with => Entities::UserLogin
  150 + end
  151 +
133 params do 152 params do
134 requires :code, type: String, desc: _("Forgot password code") 153 requires :code, type: String, desc: _("Forgot password code")
135 end 154 end
lib/noosfero/api/v1/articles.rb
@@ -131,9 +131,8 @@ module Noosfero @@ -131,9 +131,8 @@ module Noosfero
131 failure [[403, 'Forbidden']] 131 failure [[403, 'Forbidden']]
132 named 'ArticleFollowers' 132 named 'ArticleFollowers'
133 end 133 end
134 -  
135 - #FIXME refactor this method  
136 get 'voted_by_me' do 134 get 'voted_by_me' do
  135 + #FIXME refactor this method
137 present_articles_paginated(current_person.votes.where(:voteable_type => 'Article').collect(&:voteable)) 136 present_articles_paginated(current_person.votes.where(:voteable_type => 'Article').collect(&:voteable))
138 end 137 end
139 138
@@ -150,8 +149,14 @@ module Noosfero @@ -150,8 +149,14 @@ module Noosfero
150 # FIXME verify allowed values 149 # FIXME verify allowed values
151 render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value) 150 render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value)
152 article = find_article(environment.articles, params[:id]) 151 article = find_article(environment.articles, params[:id])
153 - vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)  
154 - {:vote => vote.save} 152 +
  153 + begin
  154 + vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)
  155 + saved = vote.save!
  156 + {:vote => saved}
  157 + rescue ActiveRecord::RecordInvalid => e
  158 + render_api_error!(e.message, 400)
  159 + end
155 end 160 end
156 161
157 desc 'Return the children of a article identified by id' do 162 desc 'Return the children of a article identified by id' do
lib/noosfero/api/v1/comments.rb
@@ -31,7 +31,13 @@ module Noosfero @@ -31,7 +31,13 @@ module Noosfero
31 post ":id/comments" do 31 post ":id/comments" do
32 article = find_article(environment.articles, params[:id]) 32 article = find_article(environment.articles, params[:id])
33 options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article) 33 options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article)
34 - present Comment.create(options), :with => Entities::Comment, :current_person => current_person 34 + begin
  35 + comment = Comment.create(options)
  36 + comment.save!
  37 + rescue ActiveRecord::RecordInvalid => e
  38 + render_api_error!(e.message, 400)
  39 + end
  40 + present comment, :with => Entities::Comment, :current_person => current_person
35 end 41 end
36 end 42 end
37 43
lib/noosfero/api/v1/search.rb
@@ -5,6 +5,7 @@ module Noosfero @@ -5,6 +5,7 @@ module Noosfero
5 5
6 resource :search do 6 resource :search do
7 resource :article do 7 resource :article do
  8 + paginate per_page: 20, max_per_page: 200
8 get do 9 get do
9 # Security checks 10 # Security checks
10 sanitize_params_hash(params) 11 sanitize_params_hash(params)
@@ -13,24 +14,25 @@ module Noosfero @@ -13,24 +14,25 @@ module Noosfero
13 context = environment 14 context = environment
14 15
15 profile = environment.profiles.find(params[:profile_id]) if params[:profile_id] 16 profile = environment.profiles.find(params[:profile_id]) if params[:profile_id]
16 - scope = profile.nil? ? environment.articles.is_public : profile.articles.is_public 17 +
  18 + scope = profile.nil? ? environment.articles.public : profile.articles.public
  19 +
17 scope = scope.where(:type => params[:type]) if params[:type] && !(params[:type] == 'Article') 20 scope = scope.where(:type => params[:type]) if params[:type] && !(params[:type] == 'Article')
  21 +
18 scope = scope.where(:parent_id => params[:parent_id]) if params[:parent_id].present? 22 scope = scope.where(:parent_id => params[:parent_id]) if params[:parent_id].present?
  23 +
19 scope = scope.joins(:categories).where(:categories => {:id => params[:category_ids]}) if params[:category_ids].present? 24 scope = scope.joins(:categories).where(:categories => {:id => params[:category_ids]}) if params[:category_ids].present?
  25 +
20 query = params[:query] || "" 26 query = params[:query] || ""
21 order = "more_recent" 27 order = "more_recent"
22 28
23 options = {:filter => order, :template_id => params[:template_id]} 29 options = {:filter => order, :template_id => params[:template_id]}
24 30
25 - paginate_options = params.select{|k,v| [:page, :per_page].include?(k.to_sym)}.symbolize_keys  
26 - paginate_options.each_pair{|k,v| v=v.to_i}  
27 - paginate_options[:page]=1 if !paginate_options.keys.include?(:page)  
28 -  
29 search_result = find_by_contents(asset, context, scope, query, paginate_options, options) 31 search_result = find_by_contents(asset, context, scope, query, paginate_options, options)
30 32
31 articles = search_result[:results] 33 articles = search_result[:results]
32 34
33 - result = present_articles(articles) 35 + result = present_articles_paginated(articles)
34 36
35 result 37 result
36 end 38 end
lib/noosfero/vote_ext.rb
@@ -8,4 +8,15 @@ class Vote @@ -8,4 +8,15 @@ class Vote
8 voter.present? 8 voter.present?
9 end 9 end
10 10
  11 + validate :verify_target_archived
  12 +
  13 + def verify_target_archived
  14 + if voteable.kind_of?(Article) || voteable.kind_of?(Comment)
  15 + if voteable.archived?
  16 + errors.add(:base, _("The target is achived and can't accept votes"))
  17 + false
  18 + end
  19 + end
  20 + end
  21 +
11 end 22 end
plugins/dialoga
1 -Subproject commit b963a3fc7fb7df7853c97d5763dd2e11e35072e1 1 +Subproject commit d654ce48f466687dd01ae023b68122b0aaba7d3f
plugins/gamification
1 -Subproject commit 4d4afd7e7e60343336c20813dce47f327f8a729f 1 +Subproject commit bea83c2384fd561acb4cddf17454616dd91fabd7
plugins/pg_search/lib/ext/active_record.rb
@@ -4,7 +4,9 @@ class ActiveRecord::Base @@ -4,7 +4,9 @@ class ActiveRecord::Base
4 def self.pg_search_plugin_search(query) 4 def self.pg_search_plugin_search(query)
5 filtered_query = query.gsub(/[\|\(\)\\\/\s\[\]'"*%&!:]/,' ').split.map{|w| w += ":*"}.join('|') 5 filtered_query = query.gsub(/[\|\(\)\\\/\s\[\]'"*%&!:]/,' ').split.map{|w| w += ":*"}.join('|')
6 if defined?(self::SEARCHABLE_FIELDS) 6 if defined?(self::SEARCHABLE_FIELDS)
7 - where("to_tsvector('simple', #{pg_search_plugin_fields}) @@ to_tsquery('#{filtered_query}')") 7 + select("*,ts_rank(to_tsvector('simple', #{pg_search_plugin_fields}), to_tsquery('#{filtered_query}')) as rank").
  8 + where("to_tsvector('simple', #{pg_search_plugin_fields}) @@ to_tsquery('#{filtered_query}')").
  9 + order("rank DESC")
8 else 10 else
9 raise "No searchable fields defined for #{self.name}" 11 raise "No searchable fields defined for #{self.name}"
10 end 12 end
plugins/pg_search/test/unit/pg_search_plugin_test.rb
@@ -21,6 +21,13 @@ class PgSearchPluginTest &lt; ActiveSupport::TestCase @@ -21,6 +21,13 @@ class PgSearchPluginTest &lt; ActiveSupport::TestCase
21 assert_includes search(Profile, 'admin deb'), profile2 21 assert_includes search(Profile, 'admin deb'), profile2
22 end 22 end
23 23
  24 + should 'rank profiles based on the search entry' do
  25 + profile1 = fast_create(Profile, :identifier => 'profile1', :name => 'debugger')
  26 + profile2 = fast_create(Profile, :identifier => 'profile2', :name => 'profile admin debugger')
  27 + profile3 = fast_create(Profile, :identifier => 'profile3', :name => 'admin debugger')
  28 + assert_equal [profile2, profile3, profile1], search(Profile, 'profile admin deb')
  29 + end
  30 +
24 should 'locate profile escaping special characters' do 31 should 'locate profile escaping special characters' do
25 profile = fast_create(Profile, :name => 'John', :identifier => 'waterfall') 32 profile = fast_create(Profile, :name => 'John', :identifier => 'waterfall')
26 assert_includes search(Profile, ') ( /\/\/\/\/\ o_o oOo o_o /\/\/\/\/\ ) ((tx waterfall)'), profile 33 assert_includes search(Profile, ') ( /\/\/\/\/\ o_o oOo o_o /\/\/\/\/\ ) ((tx waterfall)'), profile
plugins/vote/lib/vote_plugin_helper.rb
@@ -2,8 +2,10 @@ module VotePluginHelper @@ -2,8 +2,10 @@ module VotePluginHelper
2 2
3 def vote_partial(target, like = true, load_voters=false) 3 def vote_partial(target, like = true, load_voters=false)
4 vote = like ? 1 : -1 4 vote = like ? 1 : -1
  5 +
5 like_action = like ? 'like' : 'dislike' 6 like_action = like ? 'like' : 'dislike'
6 type = target.kind_of?(Article) ? 'article' : target.kind_of?(Comment) ? 'comment' : nil 7 type = target.kind_of?(Article) ? 'article' : target.kind_of?(Comment) ? 'comment' : nil
  8 + disable_vote = target.archived? ? true : false
7 9
8 proc do 10 proc do
9 settings = Noosfero::Plugin::Settings.new(environment, VotePlugin) 11 settings = Noosfero::Plugin::Settings.new(environment, VotePlugin)
@@ -14,7 +16,7 @@ module VotePluginHelper @@ -14,7 +16,7 @@ module VotePluginHelper
14 active = user ? (like ? user.voted_for?(target) : user.voted_against?(target)) : false 16 active = user ? (like ? user.voted_for?(target) : user.voted_against?(target)) : false
15 count = like ? target.votes_for : target.votes_against 17 count = like ? target.votes_for : target.votes_against
16 18
17 - render(:partial => 'vote/vote', :locals => {:target => target, :active => active, :action => like_action, :count => count, :voters => voters, :vote => vote, :model => type }) 19 + render(:partial => 'vote/vote', :locals => {:target => target, :active => active, :action => like_action, :count => count, :voters => voters, :vote => vote, :model => type, :disable_vote => disable_vote })
18 else 20 else
19 "" 21 ""
20 end 22 end
plugins/vote/public/style.css
@@ -73,3 +73,12 @@ @@ -73,3 +73,12 @@
73 #article .action .vote-detail img { 73 #article .action .vote-detail img {
74 vertical-align: bottom; 74 vertical-align: bottom;
75 } 75 }
  76 +
  77 +.comments-action-bar span.disabled a,
  78 +.comments-action-bar span.disabled a:hover,
  79 +.vote-actions span.disabled a,
  80 +.vote-actions span.disabled a:hover {
  81 + opacity: 0.5;
  82 + pointer-events: none;
  83 + cursor: default;
  84 +}
plugins/vote/public/vote_actions.js
@@ -16,7 +16,9 @@ function openVotersDialog(div) { @@ -16,7 +16,9 @@ function openVotersDialog(div) {
16 clearTimeout(openEvent); 16 clearTimeout(openEvent);
17 var url = $(div).data('reload_url'); 17 var url = $(div).data('reload_url');
18 hideAllVoteDetail(); 18 hideAllVoteDetail();
19 - $.post(url); 19 + if(url && url != '#'){
  20 + $.post(url);
  21 + }
20 } 22 }
21 23
22 jQuery('body').live('click', function() { hideAllVoteDetail(); }); 24 jQuery('body').live('click', function() { hideAllVoteDetail(); });
plugins/vote/test/functional/vote_plugin_profile_controller_test.rb
@@ -32,6 +32,18 @@ class VotePluginProfileControllerTest &lt; ActionController::TestCase @@ -32,6 +32,18 @@ class VotePluginProfileControllerTest &lt; ActionController::TestCase
32 assert profile.votes.empty? 32 assert profile.votes.empty?
33 end 33 end
34 34
  35 + should 'not vote if a target is archived' do
  36 + article = Article.create!(:profile => profile, :name => 'Archived article', :archived => false)
  37 + comment = Comment.create!(:body => 'Comment test', :source => article, :author => profile)
  38 + xhr :post, :vote, :profile => profile.identifier, :id => article.id, :model => 'article', :vote => 1
  39 + assert !profile.votes.empty?
  40 +
  41 + article.update_attributes(:archived => true)
  42 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
  43 +
  44 + assert !profile.voted_for?(comment)
  45 + end
  46 +
35 should 'like comment' do 47 should 'like comment' do
36 xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1 48 xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
37 assert profile.voted_for?(comment) 49 assert profile.voted_for?(comment)
plugins/vote/views/vote/_vote.html.erb
1 <% 1 <%
2 -url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :vote, :id => target.id, :model => model, :vote => vote)  
3 -reload_url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :reload_vote, :id => target.id, :model => model, :vote => vote) 2 +if disable_vote
  3 + url = reload_url = '#'
  4 + class_action = 'disabled'
  5 +else
  6 + url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :vote, :id => target.id, :model => model, :vote => vote)
  7 + reload_url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :reload_vote, :id => target.id, :model => model, :vote => vote)
  8 +end
4 %> 9 %>
5 10
6 -<span id="vote_<%= model %>_<%= target.id %>_<%= vote %>" data-reload_url=<%= reload_url %> class="vote-action action <%= action %>-action"> 11 +<span id="vote_<%= model %>_<%= target.id %>_<%= vote %>" data-reload_url=<%= reload_url %> class="vote-action action <%= action %>-action <%= class_action %>">
7 12
8 <%= link_to content_tag(:span, count, :class=>'like-action-counter') + content_tag(:span, '', :class=>"action-icon #{action}"), url, :class => "#{active ? 'like-action-active':''} #{user ? '':'disabled'} require-login-popup" %> 13 <%= link_to content_tag(:span, count, :class=>'like-action-counter') + content_tag(:span, '', :class=>"action-icon #{action}"), url, :class => "#{active ? 'like-action-active':''} #{user ? '':'disabled'} require-login-popup" %>
9 14
po/noosfero.pot
@@ -5073,6 +5073,10 @@ msgid_plural &quot;Viewed %{num} times&quot; @@ -5073,6 +5073,10 @@ msgid_plural &quot;Viewed %{num} times&quot;
5073 msgstr[0] "" 5073 msgstr[0] ""
5074 msgstr[1] "" 5074 msgstr[1] ""
5075 5075
  5076 +#: app/views/content_viewer/_publishing_info.html.erb:19
  5077 +msgid "(Not countable anymore)"
  5078 +msgstr ""
  5079 +
5076 #: app/views/content_viewer/versions_diff.html.erb:5 5080 #: app/views/content_viewer/versions_diff.html.erb:5
5077 msgid "Changes on \"%s\"" 5081 msgid "Changes on \"%s\""
5078 msgstr "" 5082 msgstr ""
po/pt/noosfero.po
@@ -5231,11 +5231,15 @@ msgid &quot;Reply&quot; @@ -5231,11 +5231,15 @@ msgid &quot;Reply&quot;
5231 msgstr "Responder" 5231 msgstr "Responder"
5232 5232
5233 #: app/views/content_viewer/_publishing_info.html.erb:19 5233 #: app/views/content_viewer/_publishing_info.html.erb:19
5234 -msgid "Viewed one time"  
5235 -msgid_plural "Viewed %{num} times" 5234 +msgid "Viewed one time %{desc}"
  5235 +msgid_plural "Viewed %{num} times %{desc}"
5236 msgstr[0] "Visualizado uma vez" 5236 msgstr[0] "Visualizado uma vez"
5237 msgstr[1] "Visualizado %{num} vezes" 5237 msgstr[1] "Visualizado %{num} vezes"
5238 5238
  5239 +#: app/views/content_viewer/_publishing_info.html.erb:19
  5240 +msgid "(Not countable anymore)"
  5241 +msgstr "(Não mais contabilizado)"
  5242 +
5239 #: app/views/content_viewer/versions_diff.html.erb:5 5243 #: app/views/content_viewer/versions_diff.html.erb:5
5240 msgid "Changes on \"%s\"" 5244 msgid "Changes on \"%s\""
5241 msgstr "Mudanças em \"%s\"" 5245 msgstr "Mudanças em \"%s\""
public/images/icons-app/article-archived-icon.png 0 → 100644

166 Bytes

public/images/icons-app/article-private-icon.png 0 → 100644

273 Bytes

public/images/icons-app/article-public-icon.png 0 → 100644

570 Bytes

public/images/icons-app/warning-icon.png 0 → 100644

509 Bytes

public/javascripts/article.js
@@ -200,4 +200,10 @@ jQuery(function($) { @@ -200,4 +200,10 @@ jQuery(function($) {
200 200
201 $(".custom_privacy_option").click(show_hide_token_input); 201 $(".custom_privacy_option").click(show_hide_token_input);
202 202
  203 + //Workaround to pointer-events:none CSS3
  204 + $('a.disabled').click(function(e){
  205 + e.preventDefault();
  206 + return false;
  207 + });
  208 +
203 }); 209 });
public/stylesheets/cms.scss
1 @import 'cms/fetch-external-feed'; 1 @import 'cms/fetch-external-feed';
2 @import 'cms/media-panel'; 2 @import 'cms/media-panel';
  3 +@import 'cms/access-options';
3 4
4 table.cms-articles { 5 table.cms-articles {
5 table-layout: fixed; 6 table-layout: fixed;
public/stylesheets/cms/access-options.scss 0 → 100644
@@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
  1 +//Variables
  2 +$icons-size: 24px;
  3 +$warning-color: #e75e40;
  4 +$description-color: #666;
  5 +$access-types: public, private, archived;
  6 +
  7 +.text-warning {
  8 + text-align: center;
  9 + color: $warning-color;
  10 + font: {
  11 + size: 15px;
  12 + weight: bold;
  13 + }
  14 +
  15 + p i.icon {
  16 + display: inline-block;
  17 + font-size: inherit;
  18 + text-rendering: auto;
  19 + background: url('images/icons-app/warning-icon.png');
  20 + vertical-align: middle;
  21 + margin-top: -5px;
  22 + width: $icons-size;
  23 + height: $icons-size;
  24 + border: none;
  25 + }
  26 +}
  27 +
  28 +#edit-article-options {
  29 +
  30 + .access-itens .access-item {
  31 + input,label {
  32 + cursor: pointer;
  33 + }
  34 + }
  35 +
  36 + .access-item {
  37 + margin: 15px 0;
  38 + label {
  39 + font: {
  40 + size: 13px;
  41 + weight: bold;
  42 + }
  43 + }
  44 + }
  45 +
  46 + @each $access_type in $access-types {
  47 + .access-#{$access_type}-icon, .access-#{$access_type}-icon {
  48 + display: inline-block;
  49 + margin-right: 5px;
  50 + width: $icons-size;
  51 + height: $icons-size;
  52 + vertical-align: middle;
  53 + background-image: url('images/icons-app/article-#{$access_type}-icon.png');
  54 + }
  55 + }
  56 +
  57 + .access-note {
  58 + display: block;
  59 + margin: 0;
  60 + color: $description-color;
  61 + margin-left: 50px;
  62 + font: {
  63 + size: 12px;
  64 + weight: normal;
  65 + }
  66 + }
  67 +}
test/functional/cms_controller_test.rb
@@ -694,6 +694,34 @@ class CmsControllerTest &lt; ActionController::TestCase @@ -694,6 +694,34 @@ class CmsControllerTest &lt; ActionController::TestCase
694 end 694 end
695 end 695 end
696 696
  697 + should "marks a article like archived" do
  698 + article = create(Article, :profile => profile, :name => 'test', :published => true, :archived => false)
  699 +
  700 + post :edit, :profile => profile.identifier, :id => article.id, :article => {:archived => true}
  701 + get :edit, :profile => profile.identifier, :id => article.id
  702 + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[archived]', :id => 'article_archived', :checked => 'checked' }
  703 +
  704 + end
  705 +
  706 + should "try add children into archived folders" do
  707 + folder = create(Folder, :profile => profile, :name => 'test', :published => true, :archived => false)
  708 + article_child = create(Article, :profile => profile, :name => 'test child', :parent_id => folder.id, :published => true, :archived => false)
  709 +
  710 + get :edit, :profile => profile.identifier, :id => folder.id
  711 + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'article[archived]', :id => 'article_archived' }
  712 +
  713 + post :edit, :profile => profile.identifier, :id => folder.id, :article => {:archived => true}
  714 +
  715 + get :edit, :profile => profile.identifier, :id => article_child.id
  716 + assert_tag :tag => 'div', :attributes => { :class => 'text-warning'}
  717 +
  718 + err = assert_raises ActiveRecord::RecordInvalid do
  719 + another_article_child = create(Article, :profile => profile, :name => 'other test child', :parent_id => folder.id, :published => true, :archived => false)
  720 + end
  721 + assert_match 'Parent folder is archived', err.message
  722 +
  723 + end
  724 +
697 should 'be able to add image with alignment' do 725 should 'be able to add image with alignment' do
698 post :new, :type => 'TinyMceArticle', :profile => profile.identifier, :article => { :name => 'image-alignment', :body => "the text of the article with image <img src='#' align='right'/> right align..." } 726 post :new, :type => 'TinyMceArticle', :profile => profile.identifier, :article => { :name => 'image-alignment', :body => "the text of the article with image <img src='#' align='right'/> right align..." }
699 saved = TinyMceArticle.find_by_name('image-alignment') 727 saved = TinyMceArticle.find_by_name('image-alignment')
test/unit/api/articles_test.rb
@@ -138,6 +138,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -138,6 +138,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase
138 assert_equal true, json['vote'] 138 assert_equal true, json['vote']
139 end 139 end
140 140
  141 + should 'not perform a vote in a archived article' do
  142 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing", :archived => true)
  143 + @params[:value] = 1
  144 +
  145 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  146 + json = JSON.parse(last_response.body)
  147 +
  148 + assert_equal 400, last_response.status
  149 + end
  150 +
141 expose_attributes = %w(id body abstract created_at title author profile categories image votes_for votes_against setting position hits start_date end_date tag_list parent children children_count) 151 expose_attributes = %w(id body abstract created_at title author profile categories image votes_for votes_against setting position hits start_date end_date tag_list parent children children_count)
142 152
143 expose_attributes.each do |attr| 153 expose_attributes.each do |attr|
@@ -520,6 +530,14 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -520,6 +530,14 @@ class ArticlesTest &lt; ActiveSupport::TestCase
520 assert_equal 1, json['article']['hits'] 530 assert_equal 1, json['article']['hits']
521 end 531 end
522 532
  533 + should 'not update hit attribute of a specific child if a article is archived' do
  534 + folder = fast_create(Folder, :profile_id => user.person.id, :archived => true)
  535 + article = fast_create(Article, :parent_id => folder.id, :profile_id => user.person.id)
  536 + get "/api/v1/articles/#{folder.id}/children/#{article.id}?#{params.to_query}"
  537 + json = JSON.parse(last_response.body)
  538 + assert_equal 0, json['article']['hits']
  539 + end
  540 +
523 should 'list all events of a community in a given category' do 541 should 'list all events of a community in a given category' do
524 co = Community.create(identifier: 'my-community', name: 'name-my-community') 542 co = Community.create(identifier: 'my-community', name: 'name-my-community')
525 c1 = Category.create(environment: Environment.default, name: 'my-category') 543 c1 = Category.create(environment: Environment.default, name: 'my-category')
test/unit/api/comments_test.rb
@@ -66,6 +66,15 @@ class CommentsTest &lt; ActiveSupport::TestCase @@ -66,6 +66,15 @@ class CommentsTest &lt; ActiveSupport::TestCase
66 assert_equal body, json['comment']['body'] 66 assert_equal body, json['comment']['body']
67 end 67 end
68 68
  69 + should 'not comment an archived article' do
  70 + article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing", :archived => true)
  71 + body = 'My comment'
  72 + params.merge!({:body => body})
  73 +
  74 + post "/api/v1/articles/#{article.id}/comments?#{params.to_query}"
  75 + assert_equal 400, last_response.status
  76 + end
  77 +
69 should 'comment creation define the source' do 78 should 'comment creation define the source' do
70 amount = Comment.count 79 amount = Comment.count
71 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing") 80 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
test/unit/api/search_test.rb
@@ -30,8 +30,7 @@ class SearchTest &lt; ActiveSupport::TestCase @@ -30,8 +30,7 @@ class SearchTest &lt; ActiveSupport::TestCase
30 assert_empty json['articles'] 30 assert_empty json['articles']
31 end 31 end
32 32
33 - should 'not list articles of wrong type' do  
34 - Article.delete_all 33 + should 'do not list articles of wrong type' do
35 fast_create(Article, :profile_id => person.id) 34 fast_create(Article, :profile_id => person.id)
36 get "/api/v1/search/article?type=TinyMceArticle" 35 get "/api/v1/search/article?type=TinyMceArticle"
37 json = JSON.parse(last_response.body) 36 json = JSON.parse(last_response.body)
@@ -131,10 +130,8 @@ class SearchTest &lt; ActiveSupport::TestCase @@ -131,10 +130,8 @@ class SearchTest &lt; ActiveSupport::TestCase
131 article2.categories<< category2 130 article2.categories<< category2
132 get "/api/v1/search/article?category_ids[]=#{category1.id}&category_ids[]=#{category2.id}" 131 get "/api/v1/search/article?category_ids[]=#{category1.id}&category_ids[]=#{category2.id}"
133 json = JSON.parse(last_response.body) 132 json = JSON.parse(last_response.body)
134 - ids = [article1.id, article2.id]  
135 assert_equal 2, json['articles'].count 133 assert_equal 2, json['articles'].count
136 - assert_includes ids, json['articles'].first["id"]  
137 - assert_includes ids, json['articles'].last["id"] 134 + assert_equivalent [article1.id, article2.id], json['articles'].map{|a| a['id']}
138 end 135 end
139 136
140 end 137 end
test/unit/api/session_test.rb
@@ -198,4 +198,40 @@ class SessionTest &lt; ActiveSupport::TestCase @@ -198,4 +198,40 @@ class SessionTest &lt; ActiveSupport::TestCase
198 assert_equal 404, last_response.status 198 assert_equal 404, last_response.status
199 end 199 end
200 200
  201 + should 'not return private token when the registered user is inactive' do
  202 + params = {:login => "newuserapi", :password => "newuserapi", :password_confirmation => "newuserapi", :email => "newuserapi@email.com" }
  203 + post "/api/v1/register?#{params.to_query}"
  204 + assert_equal 201, last_response.status
  205 + json = JSON.parse(last_response.body)
  206 + assert !User['newuserapi'].activated?
  207 + assert !json['user']['activated']
  208 + assert !json['user']['private_token'].present?
  209 + end
  210 +
  211 + should 'resend activation code for an inactive user' do
  212 + user = create_user
  213 + params = {:value => user.login}
  214 + Delayed::Job.destroy_all
  215 + assert_difference 'ActionMailer::Base.deliveries.size' do
  216 + post "/api/v1/resend_activation_code?#{params.to_query}"
  217 + process_delayed_job_queue
  218 + end
  219 + json = JSON.parse(last_response.body)
  220 + assert !json['users'].first['activated']
  221 + assert_equal user.email, ActionMailer::Base.deliveries.last['to'].to_s
  222 + end
  223 +
  224 + should 'not resend activation code for an active user' do
  225 + user = create_user
  226 + params = {:value => user.login}
  227 + user.activate
  228 + Delayed::Job.destroy_all
  229 + assert_no_difference 'ActionMailer::Base.deliveries.size' do
  230 + post "/api/v1/resend_activation_code?#{params.to_query}"
  231 + process_delayed_job_queue
  232 + end
  233 + json = JSON.parse(last_response.body)
  234 + assert json['users'].first['activated']
  235 + end
  236 +
201 end 237 end
test/unit/article_test.rb
@@ -2179,10 +2179,21 @@ class ArticleTest &lt; ActiveSupport::TestCase @@ -2179,10 +2179,21 @@ class ArticleTest &lt; ActiveSupport::TestCase
2179 a3 = fast_create(Article) 2179 a3 = fast_create(Article)
2180 Article.hit([a1, a2, a3]) 2180 Article.hit([a1, a2, a3])
2181 Article.hit([a2, a3]) 2181 Article.hit([a2, a3])
  2182 +
2182 assert_equal [1, 2, 2], [a1.hits, a2.hits, a3.hits] 2183 assert_equal [1, 2, 2], [a1.hits, a2.hits, a3.hits]
2183 assert_equal [1, 2, 2], [a1.reload.hits, a2.reload.hits, a3.reload.hits] 2184 assert_equal [1, 2, 2], [a1.reload.hits, a2.reload.hits, a3.reload.hits]
2184 end 2185 end
2185 2186
  2187 + should 'not update hit attribute of archiveds articles' do
  2188 + a1 = fast_create(Article)
  2189 + a2 = fast_create(Article, :archived => true)
  2190 + a3 = fast_create(Article, :archived => true)
  2191 + Article.hit([a1, a2, a3])
  2192 +
  2193 + assert_equal [1, 0, 0], [a1.hits, a2.hits, a3.hits]
  2194 + assert_equal [1, 0, 0], [a1.reload.hits, a2.reload.hits, a3.reload.hits]
  2195 + end
  2196 +
2186 should 'vote in a article' do 2197 should 'vote in a article' do
2187 article = create(Article, :name => 'Test', :profile => profile, :last_changed_by => nil) 2198 article = create(Article, :name => 'Test', :profile => profile, :last_changed_by => nil)
2188 profile.vote(article, 5) 2199 profile.vote(article, 5)
@@ -2241,4 +2252,25 @@ class ArticleTest &lt; ActiveSupport::TestCase @@ -2241,4 +2252,25 @@ class ArticleTest &lt; ActiveSupport::TestCase
2241 assert !a.display_preview? 2252 assert !a.display_preview?
2242 end 2253 end
2243 2254
  2255 + should 'check if a article is archived' do
  2256 + folder = Folder.create!(:name => 'Parent Archived', :profile => profile)
  2257 + a1 = Article.create!(:name => 'Test', :profile => profile, :parent_id => folder.id, :archived => false)
  2258 + a2 = Article.create!(:name => 'Test 2', :profile => profile, :archived => true)
  2259 + folder.update_attributes(:archived => true)
  2260 + a1.reload
  2261 +
  2262 + assert a1.archived?
  2263 + assert a2.archived?
  2264 + end
  2265 +
  2266 + should 'try add a child article to a archived folder' do
  2267 + folder = Folder.create!(:name => 'Parent Archived', :profile => profile, :archived => true)
  2268 +
  2269 + err = assert_raises ActiveRecord::RecordInvalid do
  2270 + a1 = Article.create!(:name => 'Test', :profile => profile, :parent_id => folder.id, :archived => false)
  2271 + end
  2272 +
  2273 + assert_match 'Parent folder is archived', err.message
  2274 + end
  2275 +
2244 end 2276 end
test/unit/comment_test.rb
@@ -95,6 +95,17 @@ class CommentTest &lt; ActiveSupport::TestCase @@ -95,6 +95,17 @@ class CommentTest &lt; ActiveSupport::TestCase
95 assert_equal cc + 1, ActionTracker::Record.find(activity.id).comments_count 95 assert_equal cc + 1, ActionTracker::Record.find(activity.id).comments_count
96 end 96 end
97 97
  98 + should 'try add a comment to a archived article' do
  99 + person = fast_create(Person)
  100 + article = Article.create!(:name => 'Test', :profile => person, :archived => true)
  101 +
  102 + err = assert_raises ActiveRecord::RecordInvalid do
  103 + comment = create(Comment, :source => article, :author_id => person.id)
  104 + end
  105 +
  106 + assert_match 'Article associated with this comment is achived', err.message
  107 + end
  108 +
98 should 'provide author name for authenticated authors' do 109 should 'provide author name for authenticated authors' do
99 owner = create_user('testuser').person 110 owner = create_user('testuser').person
100 assert_equal 'testuser', build(Comment, :author => owner).author_name 111 assert_equal 'testuser', build(Comment, :author => owner).author_name
test/unit/user_test.rb
@@ -756,6 +756,28 @@ class UserTest &lt; ActiveSupport::TestCase @@ -756,6 +756,28 @@ class UserTest &lt; ActiveSupport::TestCase
756 assert_equal 'quire', user.person.name 756 assert_equal 'quire', user.person.name
757 end 757 end
758 758
  759 + should 'deliver e-mail with activation code when resend was requested and user was not activated' do
  760 + user = new_user :email => 'pending@activation.com'
  761 + activation_code = user.activation_code
  762 + Delayed::Job.destroy_all
  763 + assert_difference 'ActionMailer::Base.deliveries.size', 1 do
  764 + user.resend_activation_code
  765 + process_delayed_job_queue
  766 + end
  767 + assert_not_equal activation_code, user.reload.activation_code
  768 + assert_equal 'pending@activation.com', ActionMailer::Base.deliveries.last['to'].to_s
  769 + end
  770 +
  771 + should 'not deliver e-mail with activation code when resend was requested and user was activated' do
  772 + user = new_user :email => 'pending@activation.com'
  773 + user.activate
  774 + Delayed::Job.destroy_all
  775 + assert_no_difference 'ActionMailer::Base.deliveries.size' do
  776 + user.resend_activation_code
  777 + process_delayed_job_queue
  778 + end
  779 + end
  780 +
759 protected 781 protected
760 def new_user(options = {}) 782 def new_user(options = {})
761 user = User.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options)) 783 user = User.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options))