Commit ba61c659230154f74b5a7e22c07d447b330fae5c

Authored by Rodrigo Souto
2 parents 8748da0c a1c06b2b

Merge commit 'refs/merge-requests/283' of git://gitorious.org/noosfero/noosfero …

…into merge-requests/283

Conflicts:
	test/unit/comment_test.rb
Showing 34 changed files with 1075 additions and 58 deletions   Show diff stats
app/controllers/public/content_viewer_controller.rb
... ... @@ -98,10 +98,13 @@ class ContentViewerController < ApplicationController
98 98 session[:notice] = _("Notification of new comments to '%s' was successfully canceled") % params[:email]
99 99 end
100 100 end
  101 +
  102 + @comments = @plugins.dispatch_first(:load_comments, @page)
  103 + @comments ||= @page.comments.without_spam.as_thread
  104 + @comments = [@comments] unless @comments.is_a?(Array)
  105 +
  106 + @comments_count = Comment.count_thread(@comments)
101 107  
102   - comments = @page.comments.without_spam
103   - @comments = comments.as_thread
104   - @comments_count = comments.count
105 108 if params[:slideshow]
106 109 render :action => 'slideshow', :layout => 'slideshow'
107 110 end
... ...
app/helpers/application_helper.rb
... ... @@ -947,10 +947,7 @@ module ApplicationHelper
947 947 options.merge!(:page => params[:npage])
948 948 content = article.to_html(options)
949 949 content = content.kind_of?(Proc) ? self.instance_eval(&content).html_safe : content.html_safe
950   - @plugins && @plugins.each do |plugin|
951   - content = plugin.parse_content(content)
952   - end
953   - content
  950 + filter_html(content, article)
954 951 end
955 952  
956 953 # Please, use link_to by default!
... ... @@ -1407,6 +1404,38 @@ module ApplicationHelper
1407 1404 @no_design_blocks = true
1408 1405 end
1409 1406  
  1407 + def filter_html(html, source)
  1408 + if @plugins
  1409 + html = convert_macro(html, source)
  1410 + end
  1411 + html
  1412 + end
  1413 +
  1414 + def convert_macro(html, source)
  1415 + doc = Hpricot(html)
  1416 + while element = doc.search('.macro').first
  1417 + macro_name = element['data-macro']
  1418 + method_name = "macro_#{macro_name}"
  1419 + attrs = collect_macro_attributes(element)
  1420 + plugin_instance = Environment.macros[environment.id][method_name]
  1421 + if plugin_instance
  1422 + result = plugin_instance.send(method_name, attrs, element.inner_html, source)
  1423 + element.inner_html = result.kind_of?(Proc) ? self.instance_eval(&result) : result
  1424 + element['class'] = "parsed-macro #{macro_name}"
  1425 + else
  1426 + element.inner_html = _("Unsupported macro %s!") % macro_name
  1427 + element['class'] = "failed-macro #{macro_name}"
  1428 + end
  1429 + attrs.each {|key, value| element.remove_attribute("data-macro-#{key}")}
  1430 + end
  1431 + doc.html
  1432 + end
  1433 +
  1434 + def collect_macro_attributes(element)
  1435 + element.attributes.to_hash.select {|key, value| key[0..10] == 'data-macro-'}.
  1436 + inject({}){|result, a| result.merge({a[0][11..-1] => a[1]})}.with_indifferent_access
  1437 + end
  1438 +
1410 1439 def default_folder_for_image_upload(profile)
1411 1440 default_folder = profile.folders.find_by_type('Gallery')
1412 1441 default_folder = profile.folders.find_by_type('Folder') if default_folder.nil?
... ...
app/helpers/boxes_helper.rb
... ... @@ -99,8 +99,8 @@ module BoxesHelper
99 99 unless block.visible?
100 100 options[:title] = _("This block is invisible. Your visitors will not see it.")
101 101 end
102   - @controller.send(:content_editor?) || @plugins.each do |plugin|
103   - result = plugin.parse_content(result)
  102 + if @controller.send(:content_editor?)
  103 + result = filter_html(result, block)
104 104 end
105 105 box_decorator.block_target(block.box, block) +
106 106 content_tag('div',
... ...
app/helpers/macros_helper.rb 0 → 100644
... ... @@ -0,0 +1,96 @@
  1 +module MacrosHelper
  2 +
  3 + def macros_in_menu
  4 + Environment.macros[@environment.id].reject{ |macro_name, plugin_instance| macro_configuration(macro_name)[:icon_path] }
  5 + end
  6 +
  7 + def macros_with_buttons
  8 + Environment.macros[@environment.id].reject{ |macro_name, plugin_instance| !macro_configuration(macro_name)[:icon_path] }
  9 + end
  10 +
  11 + def macro_configuration(macro_name)
  12 + plugin_instance = Environment.macros[@environment.id][macro_name]
  13 + plugin_instance.send("config_#{macro_name}")
  14 + end
  15 +
  16 + def macro_title(macro_name)
  17 + macro_configuration(macro_name)[:title] || macro_name.to_s.humanize
  18 + end
  19 +
  20 + def generate_macro_config_dialog(macro_name)
  21 + if macro_configuration(macro_name)[:skip_dialog]
  22 + "function(){#{macro_generator(macro_name)}}"
  23 + else
  24 + "function(){
  25 + jQuery('<div>'+#{macro_configuration_dialog(macro_name).to_json}+'</div>').dialog({
  26 + title: #{macro_title(macro_name).to_json},
  27 + modal: true,
  28 + buttons: [
  29 + {text: #{_('Ok').to_json}, click: function(){
  30 + tinyMCE.activeEditor.execCommand('mceInsertContent', false,
  31 + (function(dialog){ #{macro_generator(macro_name)} })(this));
  32 + jQuery(this).dialog('close');
  33 + }},
  34 + {text: #{_('Cancel').to_json}, click: function(){jQuery(this).dialog('close');}}
  35 + ]
  36 + });
  37 + }"
  38 + end
  39 + end
  40 +
  41 + def include_macro_js_files
  42 + plugins_javascripts = []
  43 + Environment.macros[environment.id].map do |macro_name, plugin_instance|
  44 + if macro_configuration(macro_name)[:js_files]
  45 + macro_configuration(macro_name)[:js_files].map { |js| plugins_javascripts << plugin_instance.class.public_path(js) }
  46 + end
  47 + end
  48 + javascript_include_tag(plugins_javascripts, :cache => 'cache/plugins-' + Digest::MD5.hexdigest(plugins_javascripts.to_s)) unless plugins_javascripts.empty?
  49 + end
  50 +
  51 + def macro_css_files
  52 + plugins_css = []
  53 + Environment.macros[environment.id].map do |macro_name, plugin_instance|
  54 + if macro_configuration(macro_name)[:css_files]
  55 + macro_configuration(macro_name)[:css_files].map { |css| plugins_css << plugin_instance.class.public_path(css) }
  56 + end
  57 + end
  58 + plugins_css.join(',')
  59 + end
  60 +
  61 + protected
  62 +
  63 + def macro_generator(macro_name)
  64 + if macro_configuration(macro_name)[:generator]
  65 + macro_configuration(macro_name)[:generator]
  66 + else
  67 + macro_default_generator(macro_name)
  68 + end
  69 + end
  70 +
  71 + def macro_default_generator(macro_name)
  72 + code = "var params = {};"
  73 + configuration = macro_configuration(macro_name)
  74 + configuration[:params].map do |field|
  75 + code += "params.#{field[:name]} = jQuery('*[name=#{field[:name]}]', dialog).val();"
  76 + end
  77 + code + "
  78 + var html = jQuery('<div class=\"macro mceNonEditable\" data-macro=\"#{macro_name[6..-1]}\">'+#{macro_title(macro_name).to_json}+'</div>')[0];
  79 + for(key in params) html.setAttribute('data-macro-'+key,params[key]);
  80 + return html.outerHTML;
  81 + "
  82 + end
  83 +
  84 + def macro_configuration_dialog(macro_name)
  85 + macro_configuration(macro_name)[:params].map do |field|
  86 + label_name = field[:label] || field[:name].to_s.humanize
  87 + case field[:type]
  88 + when 'text'
  89 + labelled_form_field(label_name, text_field_tag(field[:name], field[:default]))
  90 + when 'select'
  91 + labelled_form_field(label_name, select_tag(field[:name], options_for_select(field[:values], field[:default])))
  92 + end
  93 + end.join("\n")
  94 + end
  95 +
  96 +end
... ...
app/models/comment.rb
... ... @@ -152,6 +152,14 @@ class Comment &lt; ActiveRecord::Base
152 152 @replies = comments_list
153 153 end
154 154  
  155 + def self.count_thread(comments)
  156 + count = comments.length
  157 + comments.each do |c|
  158 + count+=count_thread(c.replies)
  159 + end
  160 + count
  161 + end
  162 +
155 163 def self.as_thread
156 164 result = {}
157 165 root = []
... ...
app/models/environment.rb
... ... @@ -16,6 +16,10 @@ class Environment &lt; ActiveRecord::Base
16 16 filename
17 17 end
18 18  
  19 + class << self
  20 + attr_accessor :macros
  21 + end
  22 +
19 23 PERMISSIONS['Environment'] = {
20 24 'view_environment_admin_panel' => N_('View environment admin panel'),
21 25 'edit_environment_features' => N_('Edit environment features'),
... ...
app/views/comment/_comment_form.rhtml
... ... @@ -79,6 +79,8 @@ function check_captcha(button, confirm_action) {
79 79 <%= hidden_field_tag(:view, params[:view])%>
80 80 <%= f.hidden_field(:reply_of_id) %>
81 81  
  82 + <%= @plugins.dispatch(:comment_form_extra_contents, local_assigns).collect { |content| instance_eval(&content) }.join("") %>
  83 +
82 84 <% button_bar do %>
83 85 <%= submit_button('add', _('Post comment'), :onclick => "if(check_captcha(this)) { save_comment(this) } else { check_captcha(this, save_comment)};return false;") %>
84 86 <% if !edition_mode %>
... ...
app/views/content_viewer/view_page.rhtml
... ... @@ -90,7 +90,7 @@
90 90  
91 91 <% if @page.accept_comments? || @comments_count > 0 %>
92 92 <h3 <%= 'class="no-comments-yet"' if @comments_count == 0 %>>
93   - <%= number_of_comments(@page) %>
  93 + <%= display_number_of_comments(@comments_count) %>
94 94 </h3>
95 95 <% end %>
96 96  
... ...
app/views/shared/tiny_mce.rhtml
  1 +<% extend MacrosHelper %>
1 2 <%= javascript_include_tag 'tinymce/jscripts/tiny_mce/tiny_mce.js' %>
  3 +<%= include_macro_js_files %>
2 4 <script type="text/javascript">
3   - var myplugins = "searchreplace,print,table,contextmenu";
  5 + var myplugins = "searchreplace,print,table,contextmenu,-macrosPlugin";
4 6 var first_line, second_line;
5 7 var mode = '<%= mode ||= false %>'
6 8 <% if mode %>
... ... @@ -8,36 +10,80 @@
8 10 second_line = ""
9 11 <% else %>
10 12 first_line = "print,separator,copy,paste,separator,undo,redo,separator,search,replace,separator,forecolor,fontsizeselect,formatselect"
11   - second_line = "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code"
  13 + second_line = "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code,macros"
  14 + <% macros_with_buttons.each do |macro_name, plugin_instance| %>
  15 + second_line += ',<%=macro_name %>'
  16 + <% end %>
12 17 <% end %>
13 18  
14 19 if (tinymce.isIE) {
15 20 // the paste plugin is only useful in Internet Explorer
16 21 myplugins = "paste," + myplugins;
17 22 }
  23 +
  24 +tinymce.create('tinymce.plugins.MacrosPlugin', {
  25 + createControl: function(n, cm) {
  26 + switch (n) {
  27 + case 'macros':
  28 + <% unless macros_in_menu.empty? %>
  29 + var c = cm.createMenuButton('macros', {
  30 + title : 'Macros',
  31 + image : '/designs/icons/tango/Tango/16x16/emblems/emblem-system.png',
  32 + icons : false
  33 + });
  34 +
  35 + <% macros_in_menu.each do |macro_name, plugin_instance| %>
  36 + c.onRenderMenu.add(function(c, m) {
  37 + m.add({
  38 + title: <%= macro_title(macro_name).to_json %>,
  39 + onclick: <%= generate_macro_config_dialog(macro_name) %>
  40 + });
  41 + });
  42 + <% end %>
  43 +
  44 + // Return the new menu button instance
  45 + return c;
  46 + <% end %>
  47 + }
  48 + return null;
  49 + }
  50 +});
  51 +
  52 +// Register plugin with a short name
  53 +tinymce.PluginManager.add('macrosPlugin', tinymce.plugins.MacrosPlugin);
  54 +
18 55 tinyMCE.init({
19   - mode : "textareas",
20   - editor_selector : "mceEditor",
21   - theme : "advanced",
22   - relative_urls : false,
23   - remove_script_host : false,
24   - document_base_url : <%= environment.top_url.to_json %>,
25   - plugins: myplugins,
26   - theme_advanced_toolbar_location : "top",
27   - theme_advanced_layout_manager: 'SimpleLayout',
28   - theme_advanced_buttons1 : first_line,
29   - theme_advanced_buttons2 : second_line,
30   - theme_advanced_buttons3 : "",
31   - theme_advanced_blockformats :"p,address,pre,h2,h3,h4,h5,h6",
32   - paste_auto_cleanup_on_paste : true,
33   - paste_insert_word_content_callback : "convertWord",
34   - paste_use_dialog: false,
35   - apply_source_formatting : true,
36   - extended_valid_elements : "applet[style|archive|codebase|code|height|width],comment,iframe[src|style|allowtransparency|frameborder|width|height|scrolling],embed[title|src|type|height|width]",
37   - content_css: '/stylesheets/tinymce.css',
38   - language: <%= tinymce_language.inspect %>,
39   - entity_encoding: 'raw'
40   - });
  56 + mode : "textareas",
  57 + editor_selector : "mceEditor",
  58 + theme : "advanced",
  59 + relative_urls : false,
  60 + remove_script_host : false,
  61 + document_base_url : <%= environment.top_url.to_json %>,
  62 + plugins: myplugins,
  63 + theme_advanced_toolbar_location : "top",
  64 + theme_advanced_layout_manager: 'SimpleLayout',
  65 + theme_advanced_buttons1 : first_line,
  66 + theme_advanced_buttons2 : second_line,
  67 + theme_advanced_buttons3 : "",
  68 + theme_advanced_blockformats :"p,address,pre,h2,h3,h4,h5,h6",
  69 + paste_auto_cleanup_on_paste : true,
  70 + paste_insert_word_content_callback : "convertWord",
  71 + paste_use_dialog: false,
  72 + apply_source_formatting : true,
  73 + extended_valid_elements : "applet[style|archive|codebase|code|height|width],comment,iframe[src|style|allowtransparency|frameborder|width|height|scrolling],embed[title|src|type|height|width]",
  74 + content_css: '/stylesheets/tinymce.css,<%= macro_css_files %>',
  75 + language: <%= tinymce_language.inspect %>,
  76 + entity_encoding: 'raw',
  77 + setup : function(ed) {
  78 + <% macros_with_buttons.each do |macro_name, plugin_instance| %>
  79 + ed.addButton('<%= macro_name %>', {
  80 + title: <%= macro_title(macro_name).to_json %>,
  81 + onclick: <%= generate_macro_config_dialog(macro_name) %>,
  82 + image : '<%= macro_configuration(macro_name)[:icon_path]%>'
  83 + });
  84 + <% end %>
  85 + }
  86 +});
41 87  
42 88 function convertWord(type, content) {
43 89 switch (type) {
... ...
db/schema.rb
... ... @@ -131,7 +131,6 @@ ActiveRecord::Schema.define(:version =&gt; 20130606110602) do
131 131 t.integer "license_id"
132 132 end
133 133  
134   - add_index "articles", ["name"], :name => "index_articles_on_name"
135 134 add_index "articles", ["parent_id"], :name => "index_articles_on_parent_id"
136 135 add_index "articles", ["profile_id"], :name => "index_articles_on_profile_id"
137 136 add_index "articles", ["slug"], :name => "index_articles_on_slug"
... ... @@ -221,6 +220,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130606110602) do
221 220 t.string "source_type"
222 221 t.string "user_agent"
223 222 t.string "referrer"
  223 + t.integer "group_id"
224 224 end
225 225  
226 226 add_index "comments", ["source_id", "spam"], :name => "index_comments_on_source_id_and_spam"
... ...
lib/noosfero/plugin.rb
... ... @@ -5,6 +5,13 @@ class Noosfero::Plugin
5 5  
6 6 attr_accessor :context
7 7  
  8 + def initialize(context=nil)
  9 + self.context = context
  10 + macro_methods.each do |method|
  11 + Environment.macros[context.environment.id][method] = self unless context.nil?
  12 + end
  13 + end
  14 +
8 15 class << self
9 16  
10 17 def klass(dir)
... ... @@ -242,8 +249,8 @@ class Noosfero::Plugin
242 249  
243 250 # -> Parse and possibly make changes of content (article, block, etc) during HTML rendering
244 251 # returns = content as string after parser and changes
245   - def parse_content(raw_content)
246   - raw_content
  252 + def parse_content(args)
  253 + args
247 254 end
248 255  
249 256 # -> Adds links to the admin panel
... ... @@ -279,6 +286,18 @@ class Noosfero::Plugin
279 286 def filter_comment(comment)
280 287 end
281 288  
  289 + # Define custom logic to load the article comments.
  290 + #
  291 + # Example:
  292 + #
  293 + # def load_comments(article)
  294 + # article.comments.find(:all, :conditions => ['spam IS NULL OR spam = ?', false])
  295 + # end
  296 + #
  297 + def load_comments(article)
  298 + nil
  299 + end
  300 +
282 301 # This method is called by the CommentHandler background job before sending
283 302 # the notification email. If the comment is marked as spam (i.e. by calling
284 303 # <tt>comment.spam!</tt>), then the notification email will *not* be sent.
... ... @@ -355,7 +374,7 @@ class Noosfero::Plugin
355 374 def profile_info_extra_contents
356 375 nil
357 376 end
358   -
  377 +
359 378 # -> Removes the invite friend button from the friends controller
360 379 # returns = boolean
361 380 def remove_invite_friends_button
... ... @@ -417,6 +436,18 @@ class Noosfero::Plugin
417 436 def login_extra_contents
418 437 nil
419 438 end
  439 +
  440 + # -> Adds adicional content to comment form
  441 + # returns = lambda block that creates html code
  442 + def comment_form_extra_contents(args)
  443 + nil
  444 + end
  445 +
  446 + # -> Register macro methods in environment
  447 + # returns = ['method1', 'method2', ...]
  448 + def macro_methods
  449 + []
  450 + end
420 451  
421 452 # -> Finds objects by their contents
422 453 # returns = {:results => [a, b, c, ...], ...}
... ... @@ -504,6 +535,11 @@ class Noosfero::Plugin
504 535 # returns = string with reason of expiration
505 536 elsif method.to_s =~ /^content_expire_(#{content_actions.join('|')})$/
506 537 nil
  538 + # -> Answers to a specific macro
  539 + # expects: params, inner_html, content
  540 + # returns = html_code
  541 + elsif method.to_s =~ /^macro_(.+)$/
  542 + nil
507 543 else
508 544 super
509 545 end
... ...
lib/noosfero/plugin/manager.rb
... ... @@ -6,6 +6,7 @@ class Noosfero::Plugin::Manager
6 6 def initialize(environment, context)
7 7 @environment = environment
8 8 @context = context
  9 + Environment.macros = {environment.id => {}} unless environment.nil?
9 10 end
10 11  
11 12 delegate :each, :to => :enabled_plugins
... ... @@ -31,6 +32,15 @@ class Noosfero::Plugin::Manager
31 32 map { |plugin| plugin.send(event, *args) }.compact
32 33 end
33 34  
  35 + def dispatch_first(event, *args)
  36 + value = nil
  37 + map do |plugin|
  38 + value = plugin.send(event, *args)
  39 + break if value
  40 + end
  41 + value
  42 + end
  43 +
34 44 alias :dispatch_scopes :dispatch_without_flatten
35 45  
36 46 def first(event, *args)
... ... @@ -55,9 +65,7 @@ class Noosfero::Plugin::Manager
55 65  
56 66 def enabled_plugins
57 67 @enabled_plugins ||= (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin|
58   - p = plugin.constantize.new
59   - p.context = context
60   - p
  68 + plugin.constantize.new(context)
61 69 end
62 70 end
63 71  
... ...
plugins/comment_group_macro/controllers/profile/comment_group_macro_plugin_profile_controller.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class CommentGroupMacroPluginProfileController < ProfileController
  2 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  3 +
  4 + def view_comments
  5 + article_id = params[:article_id]
  6 + group_id = params[:group_id]
  7 +
  8 + article = profile.articles.find(article_id)
  9 + comments = article.group_comments.without_spam.in_group(group_id)
  10 + render :update do |page|
  11 + page.replace_html "comments_list_group_#{group_id}", :partial => 'comment/comment.rhtml', :collection => comments.as_thread
  12 + page.replace_html "comment-count-#{group_id}", comments.count
  13 + end
  14 + end
  15 +
  16 +end
... ...
plugins/comment_group_macro/controllers/public/comment_group_macro_plugin_public_controller.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class CommentGroupMacroPluginPublicController < PublicController
  2 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  3 +
  4 + def comment_group
  5 + render :json => { :group_id => Comment.find(params[:id]).group_id }
  6 + end
  7 +
  8 +end
... ...
plugins/comment_group_macro/db/migrate/20121220201629_add_group_id_to_comment.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class AddGroupIdToComment < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :comments, :group_id, :integer
  4 + end
  5 +
  6 + def self.down
  7 + remove_column :comments, :group_id
  8 + end
  9 +end
... ...
plugins/comment_group_macro/lib/comment_group_macro_plugin.rb 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +require_dependency 'comment_group_macro_plugin/ext/article'
  2 +require_dependency 'comment_group_macro_plugin/ext/comment'
  3 +
  4 +#FIXME See a better way to generalize this parameter.
  5 +ActionView::Base.sanitized_allowed_attributes += ['data-macro', 'data-macro-group_id']
  6 +
  7 +class CommentGroupMacroPlugin < Noosfero::Plugin
  8 +
  9 + def self.plugin_name
  10 + "Comment Group"
  11 + end
  12 +
  13 + def self.plugin_description
  14 + _("A plugin that display comment groups.")
  15 + end
  16 +
  17 + def load_comments(article)
  18 + article.comments.without_spam.without_group.as_thread
  19 + end
  20 +
  21 + #FIXME make this test
  22 + def macro_display_comments(params, inner_html, source)
  23 + group_id = params[:group_id].to_i
  24 + article = source
  25 + count = article.group_comments.without_spam.in_group(group_id).count
  26 +
  27 + lambda {render :partial => 'plugins/comment_group_macro/views/comment_group.rhtml', :locals => {:group_id => group_id, :article_id => article.id, :inner_html => inner_html, :count => count, :profile_identifier => article.profile.identifier }}
  28 + end
  29 +
  30 + def macro_methods
  31 + 'macro_display_comments'
  32 + end
  33 +
  34 + def config_macro_display_comments
  35 + { :params => [], :skip_dialog => true, :generator => 'makeCommentable();', :js_files => 'comment_group.js', :icon_path => '/designs/icons/tango/Tango/16x16/emblems/emblem-system.png', :css_files => 'comment_group.css' }
  36 + end
  37 +
  38 + def comment_form_extra_contents(args)
  39 + comment = args[:comment]
  40 + group_id = comment.group_id || args[:group_id]
  41 + lambda {
  42 + hidden_field_tag('comment[group_id]', group_id) if group_id
  43 + }
  44 + end
  45 +
  46 + def js_files
  47 + 'comment_group_macro.js'
  48 + end
  49 +
  50 +end
... ...
plugins/comment_group_macro/lib/comment_group_macro_plugin/ext/article.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +require_dependency 'article'
  2 +
  3 +class Article
  4 +
  5 + #FIXME make this test
  6 + has_many :group_comments, :class_name => 'Comment', :foreign_key => 'source_id', :dependent => :destroy, :order => 'created_at asc', :conditions => [ 'group_id IS NOT NULL']
  7 +
  8 + #FIXME make this test
  9 + validate :not_empty_group_comments_removed
  10 +
  11 + #FIXME make this test
  12 + def not_empty_group_comments_removed
  13 + if body
  14 + groups_with_comments = group_comments.collect {|comment| comment.group_id}.uniq
  15 + groups = Hpricot(body.to_s).search('.macro').collect{|element| element['data-macro-group_id'].to_i}
  16 + errors.add_to_base(N_('Not empty group comment cannot be removed')) unless (groups_with_comments-groups).empty?
  17 + end
  18 + end
  19 +
  20 +end
  21 +
... ...
plugins/comment_group_macro/lib/comment_group_macro_plugin/ext/comment.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +require_dependency 'comment'
  2 +
  3 +class Comment
  4 +
  5 + named_scope :without_group, :conditions => {:group_id => nil }
  6 +
  7 + named_scope :in_group, lambda { |group_id| {
  8 + :conditions => ['group_id = ?', group_id]
  9 + }
  10 + }
  11 +
  12 +end
... ...
plugins/comment_group_macro/public/comment_group.css 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +div.article_comments {
  2 + background-color:#dddddd;
  3 + border-style: solid;
  4 + border-color:#bbbbbb;
  5 + border-width:2px;
  6 +}
... ...
plugins/comment_group_macro/public/comment_group.js 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +function getNextGroupId() {
  2 + max = -1;
  3 + groups = jQuery('#article_body_ifr').contents().find('.article_comments');
  4 + groups.each(function(key, value) {
  5 + value = jQuery(value).attr('data-macro-group_id');
  6 + if(value>max) max = parseInt(value);
  7 + });
  8 + return max+1;
  9 +}
  10 +
  11 +function makeCommentable() {
  12 + tinyMCE.activeEditor.focus();
  13 + start = jQuery(tinyMCE.activeEditor.selection.getStart()).closest('p');
  14 + end = jQuery(tinyMCE.activeEditor.selection.getEnd()).closest('p');
  15 +
  16 + //text = start.parent().children();
  17 + text = jQuery('#article_body_ifr').contents().find('*');
  18 + selection = text.slice(text.index(start), text.index(end)+1);
  19 +
  20 + hasTag = false;
  21 + selection.each(function(key, value) {
  22 + commentTag = jQuery(value).closest('.article_comments');
  23 + if(commentTag.length) {
  24 + commentTag.children().unwrap('<div class=\"article_comments\"/>');
  25 + hasTag = true;
  26 + }
  27 + });
  28 +
  29 + if(!hasTag) {
  30 + tags = start.siblings().add(start);
  31 + tags = tags.slice(tags.index(start), tags.index(end)>=0?tags.index(end)+1:tags.index(start)+1);
  32 + tags.wrapAll('<div class=\"macro article_comments\" data-macro=\"display_comments\" data-macro-group_id=\"'+getNextGroupId()+'\"/>');
  33 +
  34 + contents = jQuery('#article_body_ifr').contents();
  35 + lastP = contents.find('p.article_comments_last_paragraph');
  36 + if(lastP.text().trim().length > 0) {
  37 + lastP.removeClass('article_comments_last_paragraph');
  38 + } else {
  39 + lastP.remove();
  40 + }
  41 + lastDiv = contents.find('div.article_comments').last();
  42 + if(lastDiv.next().length==0) {
  43 + lastDiv.after("<p class='article_comments_last_paragraph'>&nbsp;</p>");
  44 + }
  45 + }
  46 +}
  47 +
... ...
plugins/comment_group_macro/public/comment_group_macro.js 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +var comment_group_anchor;
  2 +jQuery(document).ready(function($) {
  3 + var anchor = window.location.hash;
  4 + if(anchor.length==0) return;
  5 +
  6 + var val = anchor.split('-'); //anchor format = #comment-\d+
  7 + if(val.length!=2 || val[0]!='#comment') return;
  8 + if($('div[data-macro=display_comments]').length==0) return; //display_comments div must exists
  9 + var comment_id = val[1];
  10 + if(!/^\d+$/.test(comment_id)) return; //test for integer
  11 +
  12 + comment_group_anchor = anchor;
  13 + var url = '/plugin/comment_group_macro/public/comment_group/'+comment_id;
  14 + $.getJSON(url, function(data) {
  15 + if(data.group_id!=null) {
  16 + var button = $('div.comment_group_'+ data.group_id + ' a');
  17 + button.click();
  18 + $.scrollTo(button);
  19 + }
  20 + });
  21 +});
  22 +
  23 +function toggleGroup(group) {
  24 + var div = jQuery('div.comments_list_toggle_group_'+group);
  25 + var visible = div.is(':visible');
  26 + if(!visible)
  27 + jQuery('div.comment-group-loading-'+group).addClass('comment-button-loading');
  28 +
  29 + div.toggle('fast');
  30 + return visible;
  31 +}
  32 +
  33 +function loadCompleted(group) {
  34 + jQuery('div.comment-group-loading-'+group).removeClass('comment-button-loading')
  35 + if(comment_group_anchor) {
  36 + jQuery.scrollTo(jQuery(comment_group_anchor));
  37 + comment_group_anchor = null;
  38 + }
  39 +}
... ...
plugins/comment_group_macro/public/images/comments.gif 0 → 100644

1.66 KB

plugins/comment_group_macro/test/functional/comment_group_macro_plugin_profile_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/profile/comment_group_macro_plugin_profile_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class CommentGroupMacroPluginProfileController; def rescue_action(e) raise e end; end
  6 +
  7 +class CommentGroupMacroPluginProfileControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @controller = CommentGroupMacroPluginProfileController.new
  11 + @request = ActionController::TestRequest.new
  12 + @response = ActionController::TestResponse.new
  13 +
  14 + @profile = create_user('testuser').person
  15 + @article = profile.articles.build(:name => 'test')
  16 + @article.save!
  17 + end
  18 + attr_reader :article
  19 + attr_reader :profile
  20 +
  21 + should 'be able to show group comments' do
  22 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  23 + xhr :get, :view_comments, :profile => @profile.identifier, :article_id => article.id, :group_id => 0
  24 + assert_template 'comment/_comment.rhtml'
  25 + assert_match /comments_list_group_0/, @response.body
  26 + assert_match /\"comment-count-0\", \"1\"/, @response.body
  27 + end
  28 +
  29 + should 'do not show global comments' do
  30 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'global comment', :body => 'global', :group_id => nil)
  31 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  32 + xhr :get, :view_comments, :profile => @profile.identifier, :article_id => article.id, :group_id => 0
  33 + assert_template 'comment/_comment.rhtml'
  34 + assert_match /comments_list_group_0/, @response.body
  35 + assert_match /\"comment-count-0\", \"1\"/, @response.body
  36 + end
  37 +
  38 +end
... ...
plugins/comment_group_macro/test/functional/comment_group_macro_plugin_public_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,33 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/public/comment_group_macro_plugin_public_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class CommentGroupMacroPluginPublicController; def rescue_action(e) raise e end; end
  6 +
  7 +class CommentGroupMacroPluginPublicControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @controller = CommentGroupMacroPluginPublicController.new
  11 + @request = ActionController::TestRequest.new
  12 + @response = ActionController::TestResponse.new
  13 +
  14 + @profile = create_user('testuser').person
  15 + @article = profile.articles.build(:name => 'test')
  16 + @article.save!
  17 + end
  18 + attr_reader :article
  19 + attr_reader :profile
  20 +
  21 + should 'be able to return group_id for a comment' do
  22 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  23 + xhr :get, :comment_group, :id => comment.id
  24 + assert_match /\{\"group_id\":0\}/, @response.body
  25 + end
  26 +
  27 + should 'return group_id=null for a global comment' do
  28 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala' )
  29 + xhr :get, :comment_group, :id => comment.id
  30 + assert_match /\{\"group_id\":null\}/, @response.body
  31 + end
  32 +
  33 +end
... ...
plugins/comment_group_macro/test/unit/comment_group_macro_plugin_test.rb 0 → 100644
... ... @@ -0,0 +1,81 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class CommentGroupMacroPluginTest < ActiveSupport::TestCase
  4 +
  5 + include Noosfero::Plugin::HotSpot
  6 +
  7 + def setup
  8 + @environment = Environment.default
  9 + end
  10 +
  11 + attr_reader :environment
  12 +
  13 + should 'register comment_group_macro in environment' do
  14 + Environment.macros = {}
  15 + Environment.macros[environment.id] = {}
  16 + macros = Environment.macros[environment.id]
  17 + context = mock()
  18 + context.stubs(:environment).returns(environment)
  19 + plugin = CommentGroupMacroPlugin.new(context)
  20 + assert_equal ['macro_display_comments'], macros.keys
  21 + end
  22 +
  23 + should 'load_comments returns all the comments wihout group of an article passed as parameter' do
  24 + article = fast_create(Article)
  25 + c1 = fast_create(Comment, :source_id => article.id, :group_id => 1)
  26 + c2 = fast_create(Comment, :source_id => article.id)
  27 + c3 = fast_create(Comment, :source_id => article.id)
  28 +
  29 + plugin = CommentGroupMacroPlugin.new
  30 + assert_equal [], [c2, c3] - plugin.load_comments(article)
  31 + assert_equal [], plugin.load_comments(article) - [c2, c3]
  32 + end
  33 +
  34 + should 'load_comments not returns spam comments' do
  35 + article = fast_create(Article)
  36 + c1 = fast_create(Comment, :source_id => article.id, :group_id => 1)
  37 + c2 = fast_create(Comment, :source_id => article.id)
  38 + c3 = fast_create(Comment, :source_id => article.id, :spam => true)
  39 +
  40 + plugin = CommentGroupMacroPlugin.new
  41 + assert_equal [], [c2] - plugin.load_comments(article)
  42 + assert_equal [], plugin.load_comments(article) - [c2]
  43 + end
  44 +
  45 + should 'load_comments returns only root comments of article' do
  46 + article = fast_create(Article)
  47 + c1 = fast_create(Comment, :source_id => article.id, :group_id => 1)
  48 + c2 = fast_create(Comment, :source_id => article.id)
  49 + c3 = fast_create(Comment, :source_id => article.id, :reply_of_id => c2.id)
  50 +
  51 + plugin = CommentGroupMacroPlugin.new
  52 + assert_equal [], [c2] - plugin.load_comments(article)
  53 + assert_equal [], plugin.load_comments(article) - [c2]
  54 + end
  55 +
  56 + should 'params of macro display comments configuration be an empty array' do
  57 + plugin = CommentGroupMacroPlugin.new
  58 + assert_equal [], plugin.config_macro_display_comments[:params]
  59 + end
  60 +
  61 + should 'skip_dialog of macro display comments configuration be true' do
  62 + plugin = CommentGroupMacroPlugin.new
  63 + assert plugin.config_macro_display_comments[:skip_dialog]
  64 + end
  65 +
  66 + should 'generator of macro display comments configuration be the makeCommentable function' do
  67 + plugin = CommentGroupMacroPlugin.new
  68 + assert_equal 'makeCommentable();', plugin.config_macro_display_comments[:generator]
  69 + end
  70 +
  71 + should 'js_files of macro display comments configuration return comment_group.js' do
  72 + plugin = CommentGroupMacroPlugin.new
  73 + assert_equal 'comment_group.js', plugin.config_macro_display_comments[:js_files]
  74 + end
  75 +
  76 + should 'css_files of macro display comments configuration return comment_group.css' do
  77 + plugin = CommentGroupMacroPlugin.new
  78 + assert_equal 'comment_group.css', plugin.config_macro_display_comments[:css_files]
  79 + end
  80 +
  81 +end
... ...
plugins/comment_group_macro/views/_comment_group.rhtml 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<div class="comments">
  2 + <div class="comment_group_<%= group_id %>" style="float: left">
  3 + <div style="float: left">
  4 + <%= link_to_remote(image_tag("/plugins/comment_group_macro/images/comments.gif"),
  5 + :url => { :profile => profile_identifier, :controller => 'comment_group_macro_plugin_profile', :action => 'view_comments', :group_id => group_id, :article_id => article_id},
  6 + :loaded => visual_effect(:highlight, "comments_list_group_#{group_id}"),
  7 + :method => :post,
  8 + :condition => "!toggleGroup(#{group_id})",
  9 + :complete => "loadCompleted(#{group_id})")%>
  10 + </div>
  11 + <!-- FIXME: css file -->
  12 + <div id="comments_group_count_<%= group_id %>" style="float: right; vertical-align: middle; padding-left: 3px; padding-right: 5px; color: #5AC1FC"><span id="comment-count-<%= group_id %>" class='comment-count'><%= count %></span></div>
  13 +
  14 + </div>
  15 +
  16 + <div>
  17 + <%= inner_html %>
  18 + </div>
  19 +
  20 + <div class="comment-group-loading-<%= group_id %>"/>
  21 +
  22 + <div class="comments_list_toggle_group_<%= group_id %>" style="display:none">
  23 + <div class="article-comments-list">
  24 + <div id="comments_list_group_<%= group_id %>"></div>
  25 + </div>
  26 +
  27 + <div id="page-comment-form-<%= group_id %>" class='post_comment_box closed'><%= render :partial => 'comment/comment_form', :locals => {:comment => Comment.new, :display_link => true, :cancel_triggers_hide => true, :group_id => group_id}%></div>
  28 +
  29 + </div>
  30 +</div>
... ...
plugins/send_email/lib/send_email_plugin.rb
... ... @@ -12,12 +12,14 @@ class SendEmailPlugin &lt; Noosfero::Plugin
12 12 true
13 13 end
14 14  
15   - def parse_content(raw_content)
  15 + def parse_content(args)
  16 + raw_content = args[:html]
16 17 if context.profile
17 18 raw_content.gsub(/\{sendemail\}/, "/profile/#{context.profile.identifier}/plugin/send_email/deliver")
18 19 else
19 20 raw_content.gsub(/\{sendemail\}/, '/plugin/send_email/deliver')
20 21 end
  22 + args.clone.merge({:html => raw_content})
21 23 end
22 24  
23 25 end
... ...
test/functional/comment_controller_test.rb
... ... @@ -146,14 +146,6 @@ class CommentControllerTest &lt; ActionController::TestCase
146 146 assert_equal page, assigns(:comment).article
147 147 end
148 148  
149   - should 'show comment form opened on error' do
150   - login_as profile.identifier
151   - page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
152   - xhr :post, :create, :profile => @profile.identifier, :id => page.id, :comment => { :title => '', :body => '' }, :confirm => 'true'
153   - response = JSON.parse @response.body
154   - assert_match /<div class=\"post_comment_box opened\"/, response["html"]
155   - end
156   -
157 149 should 'show validation error when body comment is missing' do
158 150 login_as @profile.identifier
159 151 page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
... ... @@ -392,7 +384,7 @@ class CommentControllerTest &lt; ActionController::TestCase
392 384 page = profile.articles.create(:name => 'myarticle', :body => 'the body of the text', :created_at => yesterday, :updated_at => yesterday)
393 385 Article.record_timestamps = true
394 386  
395   - login_as('ze')
  387 + login_as @profile.identifier
396 388 xhr :post, :create, :profile => profile.identifier, :id => page.id, :comment => { :title => 'crap!', :body => 'I think that this article is crap' }, :confirm => 'true'
397 389 assert_not_equal yesterday, page.reload.updated_at
398 390 end
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -1185,4 +1185,118 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1185 1185 assert_equal 1, assigns(:comments_count)
1186 1186 end
1187 1187  
  1188 + should 'add extra content on comment form from plugins' do
  1189 + class Plugin1 < Noosfero::Plugin
  1190 + def comment_form_extra_contents(args)
  1191 + lambda {
  1192 + hidden_field_tag('comment[some_field_id]', 1)
  1193 + }
  1194 + end
  1195 + end
  1196 + class Plugin2 < Noosfero::Plugin
  1197 + def comment_form_extra_contents(args)
  1198 + lambda {
  1199 + hidden_field_tag('comment[another_field_id]', 1)
  1200 + }
  1201 + end
  1202 + end
  1203 +
  1204 + Environment.default.enable_plugin(Plugin1.name)
  1205 + Environment.default.enable_plugin(Plugin2.name)
  1206 +
  1207 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1208 +
  1209 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1210 +
  1211 + assert_tag :tag => 'input', :attributes => {:name => 'comment[some_field_id]', :type => 'hidden'}
  1212 + assert_tag :tag => 'input', :attributes => {:name => 'comment[another_field_id]', :type => 'hidden'}
  1213 + end
  1214 +
  1215 + should 'collect comments as plugin definition' do
  1216 + class Plugin1 < Noosfero::Plugin
  1217 + def load_comments(page)
  1218 + [page.comments.find(4)]
  1219 + end
  1220 + end
  1221 + Environment.default.enable_plugin(Plugin1.name)
  1222 + Comment.delete_all
  1223 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1224 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' ).save!
  1225 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1226 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 3' ).save!
  1227 + c = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 4' )
  1228 + c.save!
  1229 +
  1230 +
  1231 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1232 +
  1233 + assert_equal [c], assigns(:comments)
  1234 + end
  1235 +
  1236 + should 'not be a problem if loaded comments of plugins not be an array' do
  1237 + class Plugin1 < Noosfero::Plugin
  1238 + def load_comments(page)
  1239 + page.comments.find(4)
  1240 + end
  1241 + end
  1242 + Environment.default.enable_plugin(Plugin1.name)
  1243 + Comment.delete_all
  1244 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1245 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' ).save!
  1246 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1247 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 3' ).save!
  1248 + c = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 4' )
  1249 + c.save!
  1250 +
  1251 +
  1252 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1253 +
  1254 + assert_equal [c], assigns(:comments)
  1255 + end
  1256 +
  1257 +
  1258 + should 'take in consideration only the first plugin comments definition' do
  1259 + class Plugin1 < Noosfero::Plugin
  1260 + def load_comments(page)
  1261 + page.comments.first
  1262 + end
  1263 + end
  1264 + class Plugin2 < Noosfero::Plugin
  1265 + def load_comments(page)
  1266 + page.comments.last
  1267 + end
  1268 + end
  1269 + Environment.default.enable_plugin(Plugin1.name)
  1270 + Environment.default.enable_plugin(Plugin2.name)
  1271 +
  1272 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1273 + c1 = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' )
  1274 + c1.save!
  1275 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1276 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 3' ).save!
  1277 + c2 = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 4' )
  1278 + c2.save!
  1279 +
  1280 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1281 +
  1282 + assert_equal [c1], assigns(:comments)
  1283 + end
  1284 +
  1285 + should 'empty array of comments collected by plugin make the comments variable be an empty array' do
  1286 + class Plugin1 < Noosfero::Plugin
  1287 + def load_comments(page)
  1288 + []
  1289 + end
  1290 + end
  1291 + Environment.default.enable_plugin(Plugin1.name)
  1292 +
  1293 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1294 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' ).save!
  1295 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1296 +
  1297 +
  1298 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1299 +
  1300 + assert_equal [], assigns(:comments)
  1301 + end
1188 1302 end
... ...
test/unit/application_helper_test.rb
... ... @@ -3,6 +3,7 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 3 class ApplicationHelperTest < ActiveSupport::TestCase
4 4  
5 5 include ApplicationHelper
  6 + include Noosfero::Plugin::HotSpot
6 7  
7 8 def setup
8 9 self.stubs(:session).returns({})
... ... @@ -658,6 +659,45 @@ class ApplicationHelperTest &lt; ActiveSupport::TestCase
658 659 assert_not_nil add_zoom_to_images
659 660 end
660 661  
  662 + should 'parse macros' do
  663 + class Plugin1 < Noosfero::Plugin
  664 + def macro_test1(params, inner_html, source)
  665 + 'Test1'
  666 + end
  667 + def macro_test2(params, inner_html, source)
  668 + 'Test2'
  669 + end
  670 +
  671 + def macro_methods
  672 + ['macro_test1', 'macro_test2']
  673 + end
  674 + end
  675 +
  676 + @environment = Environment.default
  677 + Environment.macros = {@environment.id => {}}
  678 + context = mock()
  679 + context.stubs(:environment).returns(@environment)
  680 + Plugin1.new(context)
  681 + html = '
  682 + <div class="macro nonEdit" data-macro="test1" data-macro-param="123"></div>
  683 + <div class="macro nonEdit" data-macro="test2"></div>
  684 + <div class="macro nonEdit" data-macro="unexistent" data-macro-param="987"></div>
  685 + '
  686 + parsed_html = convert_macro(html, mock())
  687 + parsed_divs = Hpricot(parsed_html).search('div')
  688 + expected_divs = Hpricot('
  689 + <div data-macro="test1" class="parsed-macro test1">Test1</div>
  690 + <div data-macro="test2" class="parsed-macro test2">Test2</div>
  691 + <div data-macro="unexistent" class="failed-macro unexistent">Unsupported macro unexistent!</div>
  692 + ').search('div')
  693 +
  694 + # comparing div attributes between parsed and expected html
  695 + parsed_divs.each_with_index do |div, i|
  696 + assert_equal expected_divs[i].attributes.to_hash, div.attributes.to_hash
  697 + assert_equal expected_divs[i].inner_text, div.inner_text
  698 + end
  699 + end
  700 +
661 701 should 'reference to article' do
662 702 c = fast_create(Community)
663 703 a = fast_create(TinyMceArticle, :profile_id => c.id)
... ...
test/unit/comment_test.rb
... ... @@ -700,6 +700,21 @@ class CommentTest &lt; ActiveSupport::TestCase
700 700 assert_equal c1, c1.comment_root
701 701 end
702 702  
  703 + should 'count a thread of comments' do
  704 + Comment.delete_all
  705 + comments = []
  706 + comments.push(create_comment)
  707 + c1 = create_comment
  708 + comments.push(c1)
  709 + create_comment(:reply_of_id => c1.id)
  710 + create_comment(:reply_of_id => c1.id)
  711 + c2 = create_comment
  712 + comments.push(c2)
  713 + create_comment(:reply_of_id => c2.id)
  714 +
  715 + assert_equal 6, Comment.count_thread(comments)
  716 + end
  717 +
703 718 private
704 719  
705 720 def create_comment(args = {})
... ...
test/unit/macros_helper_test.rb 0 → 100644
... ... @@ -0,0 +1,124 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class MacrosHelperTest < ActiveSupport::TestCase
  4 + include MacrosHelper
  5 + include ApplicationHelper
  6 + include ActionView::Helpers::FormOptionsHelper
  7 + include ActionView::Helpers::FormTagHelper
  8 + include ActionView::Helpers::TagHelper
  9 +
  10 + CONFIG = {
  11 + :params => [
  12 + { :name => :identifier, :type => 'text'},
  13 + { :name => :size, :type => 'select',
  14 + :values => [
  15 + [_('Big'), :big],
  16 + [_('Icon'), :icon],
  17 + [_('Minor'), :minor],
  18 + [_('Portrait'), :portrait],
  19 + [_('Thumb'), :thumb]
  20 + ],
  21 + :default => :portrait
  22 + }
  23 + ],
  24 + :title => _('Profile Image Link')
  25 + }
  26 +
  27 + should 'generate html for macro configuration' do
  28 + @environment = Environment.default
  29 + Environment.macros = {}
  30 + macros = Environment.macros[@environment.id] = {}
  31 + plugin_instance = mock
  32 + plugin_instance.stubs('config_macro_example').returns(CONFIG)
  33 + macros['macro_example'] = plugin_instance
  34 + html = macro_configuration_dialog('macro_example')
  35 + assert_tag_in_string html, :tag => 'label', :content => _('Identifier')
  36 + assert_tag_in_string html, :tag => 'input', :attributes => {:name => 'identifier'}
  37 + assert_tag_in_string html, :tag => 'label', :content => 'size'.humanize
  38 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'big'}, :content => _('Big')}
  39 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'icon'}, :content => _('Icon')}
  40 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'minor'}, :content => _('Minor')}
  41 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'portrait', :selected => true}, :content => _('Portrait')}
  42 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'thumb'}, :content => _('Thumb')}
  43 + end
  44 +
  45 + should 'get macro title' do
  46 + @environment = Environment.default
  47 + Environment.macros = {}
  48 + macros = Environment.macros[@environment.id] = {}
  49 + plugin_instance = mock
  50 + plugin_instance.stubs('config_macro_example').returns(CONFIG)
  51 + macros['macro_example'] = plugin_instance
  52 + title = macro_title('macro_example')
  53 + assert _('Profile Image Link'), title
  54 + end
  55 +
  56 + should 'get only macros in menu' do
  57 + @environment = Environment.default
  58 + Environment.macros = {}
  59 + macros = Environment.macros[@environment.id] = {}
  60 + plugin_instance = mock
  61 + plugin_instance.stubs('config_macro_example').returns({})
  62 + macros['macro_example'] = plugin_instance
  63 + plugin_instance_other = mock
  64 + plugin_instance_other.stubs('config_macro_example_other').returns({:icon_path => 'icon.png'})
  65 + macros['macro_example_other'] = plugin_instance_other
  66 + assert_equal [plugin_instance], macros_in_menu.values
  67 + end
  68 +
  69 + should 'get only macros with buttons' do
  70 + @environment = Environment.default
  71 + Environment.macros = {}
  72 + macros = Environment.macros[@environment.id] = {}
  73 + plugin_instance = mock
  74 + plugin_instance.stubs('config_macro_example').returns({})
  75 + macros['macro_example'] = plugin_instance
  76 + plugin_instance_other = mock
  77 + plugin_instance_other.stubs('config_macro_example_other').returns({:icon_path => 'icon.png'})
  78 + macros['macro_example_other'] = plugin_instance_other
  79 + assert_equal [plugin_instance_other], macros_with_buttons.values
  80 + end
  81 +
  82 + should 'skip macro config dialog and call generator directly' do
  83 + @environment = Environment.default
  84 + Environment.macros = {}
  85 + macros = Environment.macros[@environment.id] = {}
  86 + plugin_instance = mock
  87 + plugin_instance.stubs('config_macro_example').returns({:skip_dialog => true, :generator => '', :params => [] })
  88 + macros['macro_example'] = plugin_instance
  89 + assert_equal 'function(){}', generate_macro_config_dialog('macro_example')
  90 + end
  91 +
  92 + should 'include js files' do
  93 + @environment = Environment.default
  94 + Environment.macros = {}
  95 + macros = Environment.macros[@environment.id] = {}
  96 + plugin_instance = mock
  97 + plugin_instance.stubs('config_macro_example').returns({:js_files => 'macro.js' })
  98 + plugin_instance.class.stubs(:public_path).with('macro.js').returns('macro.js')
  99 + macros['macro_example'] = plugin_instance
  100 + assert_equal '<script src="/javascripts/macro.js" type="text/javascript"></script>', include_macro_js_files
  101 + end
  102 +
  103 + should 'get macro css files' do
  104 + @environment = Environment.default
  105 + Environment.macros = {}
  106 + macros = Environment.macros[@environment.id] = {}
  107 + plugin_instance = mock
  108 + plugin_instance.stubs('config_macro_example').returns({:css_files => 'macro.css' })
  109 + plugin_instance.class.stubs(:public_path).with('macro.css').returns('macro.css')
  110 + macros['macro_example'] = plugin_instance
  111 + assert_equal 'macro.css', macro_css_files
  112 + end
  113 +
  114 + should 'get macro specific generator' do
  115 + @environment = Environment.default
  116 + Environment.macros = {}
  117 + macros = Environment.macros[@environment.id] = {}
  118 + plugin_instance = mock
  119 + plugin_instance.stubs('config_macro_example').returns({:generator => 'macro_generator' })
  120 + macros['macro_example'] = plugin_instance
  121 + assert_equal 'macro_generator', macro_generator('macro_example')
  122 + end
  123 +
  124 +end
... ...
test/unit/plugin_manager_test.rb
... ... @@ -2,6 +2,8 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
2 2  
3 3 class PluginManagerTest < ActiveSupport::TestCase
4 4  
  5 + include Noosfero::Plugin::HotSpot
  6 +
5 7 def setup
6 8 @environment = Environment.default
7 9 @controller = mock()
... ... @@ -26,8 +28,8 @@ class PluginManagerTest &lt; ActiveSupport::TestCase
26 28 class Plugin4 < Noosfero::Plugin; end;
27 29 environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin4.to_s])
28 30 Noosfero::Plugin.stubs(:all).returns([Plugin1.to_s, Plugin3.to_s, Plugin4.to_s])
29   - plugins = manager.enabled_plugins.map { |instance| instance.class.to_s }
30   - assert_equal [Plugin1.to_s, Plugin4.to_s], plugins
  31 + results = plugins.enabled_plugins.map { |instance| instance.class.to_s }
  32 + assert_equal [Plugin1.to_s, Plugin4.to_s], results
31 33 end
32 34  
33 35 should 'map events to registered plugins' do
... ... @@ -55,7 +57,33 @@ class PluginManagerTest &lt; ActiveSupport::TestCase
55 57 p1 = Plugin1.new
56 58 p2 = Plugin2.new
57 59  
58   - assert_equal [p1.random_event, p2.random_event], manager.dispatch(:random_event)
  60 + assert_equal [p1.random_event, p2.random_event], plugins.dispatch(:random_event)
  61 + end
  62 +
  63 + should 'dispatch_first method returns the first plugin response if there is many plugins to responde the event' do
  64 +
  65 + class Plugin1 < Noosfero::Plugin
  66 + def random_event
  67 + 'Plugin 1 action.'
  68 + end
  69 + end
  70 +
  71 + class Plugin2 < Noosfero::Plugin
  72 + def random_event
  73 + 'Plugin 2 action.'
  74 + end
  75 + end
  76 +
  77 + class Plugin3 < Noosfero::Plugin
  78 + def random_event
  79 + 'Plugin 3 action.'
  80 + end
  81 + end
  82 +
  83 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin3.to_s])
  84 + p1 = Plugin1.new
  85 +
  86 + assert_equal p1.random_event, plugins.dispatch_first(:random_event)
59 87 end
60 88  
61 89 should 'return the first non-blank result' do
... ... @@ -140,5 +168,32 @@ class PluginManagerTest &lt; ActiveSupport::TestCase
140 168 assert_equal Plugin2, manager.first_plugin(:random_event)
141 169 end
142 170  
  171 + should 'dispatch_first method returns the first plugin response if there is many plugins to responde the event and the first one respond nil' do
  172 +
  173 + class Plugin1 < Noosfero::Plugin
  174 + def random_event
  175 + nil
  176 + end
  177 + end
  178 +
  179 + class Plugin2 < Noosfero::Plugin
  180 + def random_event
  181 + 'Plugin 2 action.'
  182 + end
  183 + end
  184 +
  185 + class Plugin3 < Noosfero::Plugin
  186 + def random_event
  187 + 'Plugin 3 action.'
  188 + end
  189 + end
  190 +
  191 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin3.to_s])
  192 +
  193 + p2 = Plugin2.new
  194 +
  195 + assert_equal p2.random_event, plugins.dispatch_first(:random_event)
  196 + end
  197 +
143 198 end
144 199  
... ...
test/unit/plugin_test.rb
... ... @@ -7,6 +7,8 @@ class PluginTest &lt; ActiveSupport::TestCase
7 7 end
8 8 attr_reader :environment
9 9  
  10 + include Noosfero::Plugin::HotSpot
  11 +
10 12 should 'keep the list of all loaded subclasses' do
11 13 class Plugin1 < Noosfero::Plugin
12 14 end
... ... @@ -26,6 +28,61 @@ class PluginTest &lt; ActiveSupport::TestCase
26 28 assert_equal({:controller => 'plugin_test/plugin1_admin', :action => 'index'}, Plugin1.admin_url)
27 29 end
28 30  
  31 + should 'register its macros in the environment when instantiated' do
  32 + class Plugin1 < Noosfero::Plugin
  33 + def macro_example1(params, inner_html, source)
  34 + end
  35 +
  36 + def example2(params, inner_html, source)
  37 + end
  38 +
  39 + def not_macro
  40 + end
  41 +
  42 + def macro_methods
  43 + ['macro_example1', 'example2']
  44 + end
  45 + end
  46 +
  47 + Environment.macros = {}
  48 + Environment.macros[environment.id] = {}
  49 + macros = Environment.macros[environment.id]
  50 + context = mock()
  51 + context.stubs(:environment).returns(environment)
  52 +
  53 + plugin_instance = Plugin1.new(context)
  54 +
  55 + assert_equal plugin_instance, macros['macro_example1']
  56 + assert_equal plugin_instance, macros['example2']
  57 + assert_nil macros['not_macro']
  58 + end
  59 +
  60 + should 'load_comments return nil by default' do
  61 +
  62 + class Plugin1 < Noosfero::Plugin; end;
  63 +
  64 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s])
  65 +
  66 + profile = fast_create(Profile, :name => 'test profile', :identifier => 'test_profile')
  67 + a = fast_create(Article, :name => 'my article', :profile_id => profile.id)
  68 + assert_nil plugins.dispatch_first(:load_comments, a)
  69 + end
  70 +
  71 + should 'load_comments return the value defined by plugin' do
  72 +
  73 + class Plugin1 < Noosfero::Plugin
  74 + def load_comments(page)
  75 + 'some value'
  76 + end
  77 + end
  78 +
  79 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s])
  80 +
  81 + profile = fast_create(Profile, :name => 'test profile', :identifier => 'test_profile')
  82 + a = fast_create(Article, :name => 'my article', :profile_id => profile.id)
  83 + assert_equal 'some value', plugins.dispatch_first(:load_comments, a)
  84 + end
  85 +
29 86 should 'returns empty hash for class method extra_blocks by default if no blocks are defined on plugin' do
30 87  
31 88 class SomePlugin1 < Noosfero::Plugin
... ... @@ -494,8 +551,4 @@ class PluginTest &lt; ActiveSupport::TestCase
494 551 end
495 552 end
496 553  
497   -
498   -
499   -
500   -
501 554 end
... ...