├── .gitignore
├── install.rb
├── uninstall.rb
├── rails
└── init.rb
├── app
├── views
│ ├── threaded_comments
│ │ ├── remove_notifications.erb
│ │ ├── new.erb
│ │ ├── show.erb
│ │ └── _comment_form.erb
│ └── threaded_comment_notifier
│ │ ├── failed_comment_creation_notification.erb
│ │ ├── new_comment_notification.erb
│ │ └── comment_reply_notification.erb
├── models
│ ├── threaded_comment.rb
│ ├── threaded_comment_notifier.rb
│ └── threaded_comment_observer.rb
├── controllers
│ └── threaded_comments_controller.rb
└── helpers
│ └── threaded_comments_helper.rb
├── test
├── setup
│ ├── initialize_models.rb
│ ├── initialize_routes.rb
│ ├── initialize_constants.rb
│ ├── config
│ │ ├── database.yml
│ │ └── schema.rb
│ ├── initialize_database.rb
│ ├── initialize_test_helper_methods.rb
│ └── initialize_controllers.rb
├── factories
│ ├── book_factories.rb
│ └── threaded_comment_factories.rb
├── stubs
│ ├── delayed_job_stubs.rb
│ └── action_view_stubs.rb
├── unit
│ ├── extend_actioncontroller_test.rb
│ ├── config_loader_test.rb
│ ├── extend_activerecord_test.rb
│ ├── threaded_comment_observer_test.rb
│ ├── threaded_comment_test.rb
│ ├── threaded_comment_notifier_test.rb
│ └── threaded_comments_helper_test.rb
├── test_helper.rb
├── performance
│ └── helper_benchmark.rb
└── functional
│ ├── generator_test.rb
│ └── threaded_comments_controller_test.rb
├── tasks
└── has_threaded_comments_tasks.rake
├── generators
└── install_has_threaded_comments
│ ├── templates
│ ├── ajax-loader.gif
│ ├── upmod-arrow.gif
│ ├── downmod-arrow.gif
│ ├── create_threaded_comments.rb
│ ├── threaded_comments_config.yml
│ └── threaded_comment_styles.css
│ ├── USAGE
│ └── install_has_threaded_comments_generator.rb
├── lib
├── has_threaded_comments
│ ├── extend_actioncontroller.rb
│ └── extend_activerecord.rb
└── has_threaded_comments.rb
├── config
├── initializers
│ └── load_config.rb
└── routes.rb
├── Rakefile
├── MIT-LICENSE
├── MORE_INFO.rdoc
└── README.rdoc
/.gitignore:
--------------------------------------------------------------------------------
1 | rdoc/
2 | *.log
3 | *.db
--------------------------------------------------------------------------------
/install.rb:
--------------------------------------------------------------------------------
1 | # Install hook code here
2 |
--------------------------------------------------------------------------------
/uninstall.rb:
--------------------------------------------------------------------------------
1 | # Uninstall hook code here
2 |
--------------------------------------------------------------------------------
/rails/init.rb:
--------------------------------------------------------------------------------
1 | require 'has_threaded_comments'
--------------------------------------------------------------------------------
/app/views/threaded_comments/remove_notifications.erb:
--------------------------------------------------------------------------------
1 | <%= @message %>
--------------------------------------------------------------------------------
/app/views/threaded_comments/new.erb:
--------------------------------------------------------------------------------
1 | <%= render_comment_form(@comment) %>
--------------------------------------------------------------------------------
/app/views/threaded_comments/show.erb:
--------------------------------------------------------------------------------
1 | <%= render_threaded_comments([@comment]) %>
--------------------------------------------------------------------------------
/test/setup/initialize_models.rb:
--------------------------------------------------------------------------------
1 | class Book < ActiveRecord::Base
2 | has_threaded_comments
3 | end
--------------------------------------------------------------------------------
/test/setup/initialize_routes.rb:
--------------------------------------------------------------------------------
1 | ActionController::Routing::Routes.draw do |map|
2 | map.resources :books
3 | end
--------------------------------------------------------------------------------
/tasks/has_threaded_comments_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :has_threaded_comments do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/templates/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aarongough/has_threaded_comments/HEAD/generators/install_has_threaded_comments/templates/ajax-loader.gif
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/templates/upmod-arrow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aarongough/has_threaded_comments/HEAD/generators/install_has_threaded_comments/templates/upmod-arrow.gif
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/templates/downmod-arrow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aarongough/has_threaded_comments/HEAD/generators/install_has_threaded_comments/templates/downmod-arrow.gif
--------------------------------------------------------------------------------
/test/factories/book_factories.rb:
--------------------------------------------------------------------------------
1 | Factory.define :book do |f|
2 | f.sequence(:title) {|n| "Book #{n}" }
3 | f.content 'Call me ishmael...'
4 | f.sequence(:email) {|n| "book#{n}@example.com" }
5 | end
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Explain the generator
3 |
4 | Example:
5 | ./script/generate has_threaded_comments Thing
6 |
7 | This will create:
8 | what/will/it/create
9 |
--------------------------------------------------------------------------------
/lib/has_threaded_comments/extend_actioncontroller.rb:
--------------------------------------------------------------------------------
1 | ActionController::Base.send(:append_after_filter, Proc.new do |controller|
2 | cookies = controller.send(:cookies)
3 | cookies[:threaded_comment_cookies_enabled] = true
4 | end)
--------------------------------------------------------------------------------
/app/views/threaded_comment_notifier/failed_comment_creation_notification.erb:
--------------------------------------------------------------------------------
1 | A user tried to create a comment and encountered the following errors:
2 |
3 | * <%= @comment.errors.full_messages.join("\n* ") %>
4 |
5 | Name: <%= h @comment.name %>
6 | Email: <%= h @comment.email %>
7 |
8 | <%= h @comment.body %>
--------------------------------------------------------------------------------
/lib/has_threaded_comments.rb:
--------------------------------------------------------------------------------
1 | require 'has_threaded_comments/extend_activerecord'
2 | require 'has_threaded_comments/extend_actioncontroller'
3 | require File.join(File.dirname(__FILE__), "..", "config", "initializers", "load_config.rb")
4 |
5 | ThreadedCommentObserver.instance
6 |
7 | ActionView::Base.send :include, ThreadedCommentsHelper
--------------------------------------------------------------------------------
/test/stubs/delayed_job_stubs.rb:
--------------------------------------------------------------------------------
1 | module DelayedJobStubs
2 |
3 | def stub_send_later
4 | $delayed_jobs ||= []
5 | Object.class_eval <<-EOD
6 | def send_later(*args)
7 | $delayed_jobs << 'new_delayed_job'
8 | end
9 | EOD
10 | yield
11 | Object.send(:remove_method, :send_later)
12 | end
13 |
14 | end
--------------------------------------------------------------------------------
/test/unit/extend_actioncontroller_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 |
3 | class ExtendActioncontrollerTest < ActiveSupport::TestCase
4 |
5 | test "ActionController::Base filter_chain should not be nil" do
6 | assert ActionController::Base.filter_chain.length > 0
7 | end
8 |
9 | end
--------------------------------------------------------------------------------
/app/views/threaded_comment_notifier/new_comment_notification.erb:
--------------------------------------------------------------------------------
1 | <%= h @comment.name %> created the following comment:
2 |
3 | Name: <%= h @comment.name %>
4 | Email: <%= h @comment.email %>
5 |
6 | <%= h @comment.body %>
7 |
8 | See the comment in it's original context:
9 | <%= __send__(@comment.owner_item.class.table_name.singularize + "_url", @comment.owner_item, :host => THREADED_COMMENTS_CONFIG[:notifications][:site_domain]) + "#threaded_comment_#{@comment.id}" %>
--------------------------------------------------------------------------------
/test/setup/initialize_constants.rb:
--------------------------------------------------------------------------------
1 | temp = YAML.load_file(File.join(File.dirname(__FILE__), "..", "..", "generators", "install_has_threaded_comments", "templates", "threaded_comments_config.yml"))
2 | if(!temp[RAILS_ENV].nil?)
3 | temp = temp[RAILS_ENV]
4 | else
5 | temp = temp['production']
6 | end
7 | THREADED_COMMENTS_CONFIG = {}
8 | temp.each_pair do |key, value|
9 | THREADED_COMMENTS_CONFIG[key.to_sym] = value.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
10 | end
11 |
--------------------------------------------------------------------------------
/config/initializers/load_config.rb:
--------------------------------------------------------------------------------
1 | load_path = File.expand_path("#{RAILS_ROOT}/config/threaded_comments_config.yml")
2 | if( File.exists?(load_path))
3 | temp = YAML.load_file(load_path)
4 | if(!temp[RAILS_ENV].nil?)
5 | temp = temp[RAILS_ENV]
6 | else
7 | temp = temp['production']
8 | end
9 | THREADED_COMMENTS_CONFIG = {}
10 | temp.each_pair do |key, value|
11 | THREADED_COMMENTS_CONFIG[key.to_sym] = value.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
12 | end
13 | end
--------------------------------------------------------------------------------
/test/unit/config_loader_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 |
3 | class ConfigLoaderTest < ActiveSupport::TestCase
4 |
5 | test "should loaded threaded_comments_config.yml" do
6 | assert_not_nil THREADED_COMMENTS_CONFIG
7 | assert_not_nil THREADED_COMMENTS_CONFIG[:notifications]
8 | assert_not_nil THREADED_COMMENTS_CONFIG[:render_threaded_comments]
9 | assert_not_nil THREADED_COMMENTS_CONFIG[:render_comment_form]
10 | end
11 |
12 | end
--------------------------------------------------------------------------------
/test/factories/threaded_comment_factories.rb:
--------------------------------------------------------------------------------
1 | Factory.define :threaded_comment do |f|
2 | f.sequence(:id) {|n| n }
3 | f.sequence(:name) {|n| "TestCommenter#{n}" }
4 | f.sequence(:email) {|n| "commenter#{n}@example.com" }
5 | f.sequence(:body) {|n| "This is a short example comment. This comment was produced by a factory and is number: #{n}" }
6 | f.parent_id 0
7 | f.sequence(:rating) {|n| n }
8 | f.threaded_comment_polymorphic_type 'Book'
9 | f.threaded_comment_polymorphic_id 1
10 | f.created_at Time.now
11 | end
--------------------------------------------------------------------------------
/test/stubs/action_view_stubs.rb:
--------------------------------------------------------------------------------
1 | module ActionViewStubs
2 |
3 | def link_to_remote(*args)
4 | if( args.last.is_a?(Hash))
5 | url = args.last[:url]
6 | url = "/#{url[:controller]}/#{url[:action]}/#{url[:id]}"
7 | end
8 | if( args.first.is_a?(String))
9 | "#{args.first}"
10 | else
11 | ""
12 | end
13 | end
14 |
15 | def time_ago_in_words(*args)
16 | args.first.to_s
17 | end
18 |
19 | def render(options)
20 | options
21 | end
22 |
23 | end
--------------------------------------------------------------------------------
/test/setup/config/database.yml:
--------------------------------------------------------------------------------
1 | sqlite:
2 | :adapter: sqlite
3 | :database: vendor/plugins/has_threaded_comments/test/has_threaded_comments_plugin.sqlite.db
4 |
5 | sqlite3:
6 | :adapter: sqlite3
7 | :database: vendor/plugins/has_threaded_comments/test/has_threaded_comments_plugin.sqlite3.db
8 |
9 | postgresql:
10 | :adapter: postgresql
11 | :username: postgres
12 | :password: postgres
13 | :database: has_threaded_comments_plugin_test
14 | :min_messages: ERROR
15 |
16 | mysql:
17 | :adapter: mysql
18 | :host: localhost
19 | :username: root
20 | :password: password
21 | :database: has_threaded_comments_plugin_test
22 |
--------------------------------------------------------------------------------
/lib/has_threaded_comments/extend_activerecord.rb:
--------------------------------------------------------------------------------
1 | module ThreadedCommentsExtension
2 | def self.included(base)
3 | base.send :extend, ClassMethods
4 | end
5 |
6 | module ClassMethods
7 | # any method placed here will apply to classes, like Book
8 | def has_threaded_comments(options = {})
9 | has_many :comments, :as => :threaded_comment_polymorphic, :class_name => "ThreadedComment"
10 | send :include, InstanceMethods
11 | end
12 | end
13 |
14 | module InstanceMethods
15 | # any method placed here will apply to instaces, like @book
16 | end
17 | end
18 |
19 | ActiveRecord::Base.send :include, ThreadedCommentsExtension
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] = 'test'
2 | ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
3 | require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
4 |
5 | require 'test_help'
6 | require 'test/unit'
7 | require 'factory_girl'
8 |
9 | require_files = []
10 | require_files << File.join(File.dirname(__FILE__), '..', 'rails', 'init.rb')
11 | require_files.concat Dir[File.join(File.dirname(__FILE__), 'factories', '*_factories.rb')]
12 | require_files.concat Dir[File.join(File.dirname(__FILE__), 'setup', 'initialize_*.rb')]
13 | require_files.concat Dir[File.join(File.dirname(__FILE__), 'stubs', '*_stubs.rb')]
14 |
15 | require_files.each do |file|
16 | require File.expand_path(file)
17 | end
--------------------------------------------------------------------------------
/test/unit/extend_activerecord_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 |
3 | class ExtendActiverecordTest < ActiveSupport::TestCase
4 |
5 | test "book schema and model has loaded correctly" do
6 | assert_difference('Book.count') do
7 | assert Book.new(Factory.attributes_for(:book)).save
8 | end
9 | end
10 |
11 | test "has_threaded_comments association" do
12 | @test_book = Book.create!(Factory.attributes_for(:book))
13 | assert_difference('ThreadedComment.count') do
14 | assert_difference('@test_book.comments.count') do
15 | @test_book.comments.create(Factory.attributes_for(:threaded_comment))
16 | end
17 | end
18 | end
19 |
20 | end
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/templates/create_threaded_comments.rb:
--------------------------------------------------------------------------------
1 | class CreateThreadedComments < ActiveRecord::Migration
2 | def self.up
3 | create_table :threaded_comments, :force => true do |t|
4 | t.string :name, :default => ""
5 | t.text :body
6 | t.integer :rating, :default => 0
7 | t.integer :flags, :default => 0
8 | t.integer :parent_id, :default => 0
9 | t.datetime :created_at
10 | t.datetime :updated_at
11 | t.string :email, :default => ""
12 | t.boolean :notifications, :default => true
13 |
14 | t.integer :threaded_comment_polymorphic_id
15 | t.string :threaded_comment_polymorphic_type
16 | end
17 | end
18 |
19 | def self.down
20 | drop_table :threaded_comments
21 | end
22 | end
--------------------------------------------------------------------------------
/test/performance/helper_benchmark.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 | require 'benchmark'
3 |
4 | class HelperPerformanceTest < ActionView::TestCase
5 |
6 | include ThreadedCommentsHelper
7 | include ActionViewStubs
8 |
9 | test "render_threaded_comments performance" do
10 | puts "\n\nBenchmarking: render_threaded_comments"
11 | complex_thread = create_complex_thread(50)
12 | simple_thread = []
13 | complex_thread.length.times do
14 | simple_thread << Factory.build(:threaded_comment)
15 | end
16 | Benchmark.bmbm do |b|
17 | b.report("Simple thread with #{simple_thread.length} comments") {render_threaded_comments(simple_thread)}
18 | b.report("Complex thread with #{complex_thread.length} comments") {render_threaded_comments(complex_thread)}
19 | end
20 | end
21 |
22 | end
--------------------------------------------------------------------------------
/test/setup/initialize_database.rb:
--------------------------------------------------------------------------------
1 | config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'config', 'database.yml')))
2 | ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), '..','debug.log'))
3 |
4 | db_adapter = ENV['DB']
5 |
6 | # no db passed, try one of these fine config-free DBs before bombing.
7 | db_adapter ||=
8 | begin
9 | require 'rubygems'
10 | require 'sqlite'
11 | 'sqlite'
12 | rescue MissingSourceFile
13 | begin
14 | require 'sqlite3'
15 | 'sqlite3'
16 | rescue MissingSourceFile
17 | end
18 | end
19 |
20 | if db_adapter.nil?
21 | raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
22 | end
23 |
24 | ActiveRecord::Base.establish_connection(config[db_adapter])
25 | load(File.join(File.dirname(__FILE__), 'config', 'schema.rb'))
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/install_has_threaded_comments_generator.rb:
--------------------------------------------------------------------------------
1 | class InstallHasThreadedCommentsGenerator < Rails::Generator::Base
2 | def manifest
3 | record do |m|
4 | m.file "threaded_comments_config.yml", "config/threaded_comments_config.yml"
5 | m.directory "public/stylesheets"
6 | m.file "threaded_comment_styles.css", "public/stylesheets/threaded_comment_styles.css"
7 | m.directory "public/has-threaded-comments-images"
8 | m.file "downmod-arrow.gif", "public/has-threaded-comments-images/downmod-arrow.gif"
9 | m.file "upmod-arrow.gif", "public/has-threaded-comments-images/upmod-arrow.gif"
10 | m.file "ajax-loader.gif", "public/has-threaded-comments-images/ajax-loader.gif"
11 | m.migration_template "create_threaded_comments.rb", "db/migrate"
12 | end
13 | end
14 |
15 | def file_name
16 | "create_threaded_comments"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/setup/config/schema.rb:
--------------------------------------------------------------------------------
1 | # Redirect STDOUT so that the migration info is not echoed to the shell
2 | original_stdout = $stdout
3 | $stdout = File.open("/dev/null", "w")
4 |
5 | ActiveRecord::Schema.define(:version => 0) do
6 | create_table :books, :force => true do |t|
7 | t.string :title
8 | t.text :content
9 | t.string :email
10 | t.boolean :notifications, :default => true
11 | end
12 |
13 | create_table :threaded_comments, :force => true do |t|
14 | t.string :name
15 | t.text :body
16 | t.integer :rating, :default => 0
17 | t.integer :flags, :default => 0
18 | t.string :email
19 | t.boolean :notifications, :default => true
20 | t.integer :parent_id, :default => 0
21 | t.integer :threaded_comment_polymorphic_id
22 | t.string :threaded_comment_polymorphic_type
23 | t.timestamps
24 | end
25 | end
26 |
27 | # Restore STDOUT so that the rest of the tests can echo to the shell as usual
28 | $stdout = original_stdout
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'rake/testtask'
3 | require 'rake/rdoctask'
4 |
5 | desc 'Default: run unit tests.'
6 | task :default => :test
7 |
8 | desc 'Test the has_threaded_comments plugin.'
9 | Rake::TestTask.new(:test) do |t|
10 | t.libs << 'lib'
11 | t.libs << 'test'
12 | t.pattern = 'test/**/*_test.rb'
13 | t.verbose = true
14 | end
15 |
16 | desc 'Test the performance of the has_threaded_comments plugin.'
17 | Rake::TestTask.new("test:performance") do |t|
18 | t.libs << 'lib'
19 | t.libs << 'test'
20 | t.pattern = 'test/**/*_benchmark.rb'
21 | t.verbose = true
22 | end
23 |
24 | desc 'Generate documentation for the has_threaded_comments plugin.'
25 | Rake::RDocTask.new(:rdoc) do |rdoc|
26 | rdoc.rdoc_dir = 'rdoc'
27 | rdoc.title = 'HasThreadedComments'
28 | rdoc.options << '--line-numbers' << '--inline-source'
29 | rdoc.rdoc_files.include('README.rdoc')
30 | rdoc.rdoc_files.include('MORE_INFO.rdoc')
31 | rdoc.rdoc_files.include('lib/**/*.rb')
32 | rdoc.rdoc_files.include('app/**/*.rb')
33 | end
34 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 Aaron Gough (http://thingsaaronmade.com/)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/app/views/threaded_comment_notifier/comment_reply_notification.erb:
--------------------------------------------------------------------------------
1 | <%= "#{@comment.name} has commented on your #{@comment.owner_item.class.table_name.singularize}: " unless(@parent_comment) -%>
2 | <%= "#{@comment.name} has replied to your comment: " if(@parent_comment) -%>
3 |
4 |
5 | "<%= h( @comment.body ) %>"
6 |
7 |
8 | To see the original <%= @comment.class.table_name.singularize -%> and all it's comments:
9 | <%= __send__(@comment.owner_item.class.table_name.singularize + "_url", @comment.owner_item, :host => THREADED_COMMENTS_CONFIG[:notifications][:site_domain]) %>
10 |
11 | To see only this comment or to reply directly to it:
12 | <%= __send__(@comment.owner_item.class.table_name.singularize + "_url", @comment.owner_item, :host => THREADED_COMMENTS_CONFIG[:notifications][:site_domain]) + "#threaded_comment_#{@comment.id}" %>
13 |
14 | -----------
15 | Please click the link below if you don't want to receive further notifications about this comment:
16 | <%= remove_threaded_comment_notifications_url( :id => @parent_comment.id, :hash => @parent_comment.email_hash, :host => THREADED_COMMENTS_CONFIG[:notifications][:site_domain]) if( @comment.parent_id != 0 ) -%>
17 |
18 |
19 | Please do not respond to this email, this email address is not monitored.
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | ActionController::Routing::Routes.draw do |map|
2 | map.create_threaded_comment '/threaded-comments', :controller => 'threaded_comments', :action => 'create', :conditions => { :method => :post }
3 | map.new_threaded_comment '/threaded-comments/new', :controller => 'threaded_comments', :action => 'new', :conditions => { :method => :get }
4 | map.threaded_comment '/threaded-comments/:id', :controller => 'threaded_comments', :action => 'show', :conditions => { :method => :get }
5 | map.flag_threaded_comment '/threaded-comments/:id/flag', :controller => 'threaded_comments', :action => 'flag', :conditions => { :method => :post }
6 | map.upmod_threaded_comment '/threaded-comments/:id/upmod', :controller => 'threaded_comments', :action => 'upmod', :conditions => { :method => :post }
7 | map.downmod_threaded_comment '/threaded-comments/:id/downmod', :controller => 'threaded_comments', :action => 'downmod', :conditions => { :method => :post }
8 | map.remove_threaded_comment_notifications '/threaded-comments/:id/remove-notifications/:hash', :controller => 'threaded_comments', :action => 'remove_notifications', :conditions => { :method => :get }
9 | end
--------------------------------------------------------------------------------
/app/models/threaded_comment.rb:
--------------------------------------------------------------------------------
1 | class ThreadedComment < ActiveRecord::Base
2 |
3 | require 'digest/md5'
4 |
5 | validates_presence_of :threaded_comment_polymorphic_id, :threaded_comment_polymorphic_type, :parent_id
6 | validates_length_of :name, :within => 2..18
7 | validates_length_of :body, :within => 30..2000
8 | validates_text_content :body if( ActiveRecord::Base.respond_to?('validates_text_content'))
9 | validates_length_of :email, :minimum => 6
10 | validates_format_of :email, :with => /.*@.*\./
11 |
12 | belongs_to :threaded_comment_polymorphic, :polymorphic => true
13 | alias owner_item threaded_comment_polymorphic
14 |
15 | before_validation :assign_owner_info_to_nested_comment
16 |
17 | attr_accessible :name, :body, :email, :parent_id, :threaded_comment_polymorphic_id, :threaded_comment_polymorphic_type
18 |
19 | def assign_owner_info_to_nested_comment
20 | unless( self[:parent_id].nil? || self[:parent_id] == 0 )
21 | parentComment = ThreadedComment.find(self[:parent_id])
22 | self[:threaded_comment_polymorphic_id] = parentComment.threaded_comment_polymorphic_id
23 | self[:threaded_comment_polymorphic_type] = parentComment.threaded_comment_polymorphic_type
24 | end
25 | self[:parent_id] = 0 if( self[:parent_id].nil? )
26 | end
27 |
28 | def email_hash
29 | return Digest::MD5.hexdigest("#{self.email}-#{self.created_at}")
30 | end
31 |
32 | end
--------------------------------------------------------------------------------
/test/setup/initialize_test_helper_methods.rb:
--------------------------------------------------------------------------------
1 | # Test helper method for easily creating a complex threaded_comment
2 | # structure for use in other tests. Returns an array of comments
3 | # the same way that ACtiveRecord would.
4 |
5 | def create_complex_thread(length=100)
6 | comments = []
7 | length.times do
8 | comments << parent_comment = Factory.build(:threaded_comment)
9 | 3.times do
10 | comments << subcomment1 = Factory.build(:threaded_comment, :parent_id => parent_comment.id)
11 | 2.times do
12 | comments << subcomment2 = Factory.build(:threaded_comment, :parent_id => subcomment1.id)
13 | 2.times do
14 | comments << subcomment3 = Factory.build(:threaded_comment, :parent_id => subcomment2.id)
15 | end
16 | end
17 | end
18 | end
19 | comments
20 | end
21 |
22 | # Test helper method for temporarily changing the value of a
23 | # configuration option. Eg:
24 | #
25 | # change_config_option(:render_threaded_comments, :enable_flagging, false) do
26 | # # some code that requires flagging to be disabled by default
27 | # end
28 |
29 | def change_config_option(namespace, key, value, &block)
30 | old_config = THREADED_COMMENTS_CONFIG.dup
31 | old_stderr = $stderr
32 | $stderr = StringIO.new
33 | THREADED_COMMENTS_CONFIG[namespace][key] = value
34 | $stderr = old_stderr
35 | yield block
36 | ensure
37 | $stderr = StringIO.new
38 | Kernel.const_set('THREADED_COMMENTS_CONFIG', old_config)
39 | $stderr = old_stderr
40 | end
--------------------------------------------------------------------------------
/app/models/threaded_comment_notifier.rb:
--------------------------------------------------------------------------------
1 | class ThreadedCommentNotifier < ActionMailer::Base
2 |
3 | def new_comment_notification( comment )
4 | recipients THREADED_COMMENTS_CONFIG[:notifications][:admin_email]
5 | from THREADED_COMMENTS_CONFIG[:notifications][:system_send_email_address]
6 | subject THREADED_COMMENTS_CONFIG[:notifications][:new_comment_subject]
7 | body :comment => comment
8 | end
9 |
10 | def comment_reply_notification( user_email, comment )
11 | recipients user_email
12 | from THREADED_COMMENTS_CONFIG[:notifications][:system_send_email_address]
13 | if( comment.parent_id == 0 )
14 | subject THREADED_COMMENTS_CONFIG[:notifications][:comment_reply_subject].gsub("{name}", comment.name)
15 | else
16 | body :parent_comment => ThreadedComment.find( comment.parent_id ), :comment => comment
17 | subject "#{comment.name} has replied to your comment"
18 | return
19 | end
20 | body :comment => comment
21 | end
22 |
23 | def failed_comment_creation_notification( comment )
24 | if(THREADED_COMMENTS_CONFIG[:notifications][:enable_comment_creation_failure_notifications])
25 | recipients THREADED_COMMENTS_CONFIG[:notifications][:admin_email]
26 | from THREADED_COMMENTS_CONFIG[:notifications][:system_send_email_address]
27 | subject THREADED_COMMENTS_CONFIG[:notifications][:failed_comment_creation_subject]
28 | body :comment => comment
29 | end
30 | end
31 |
32 | end
--------------------------------------------------------------------------------
/MORE_INFO.rdoc:
--------------------------------------------------------------------------------
1 | === API Documentation
2 |
3 | To get more details API documentation for has_threaded_comments run 'rake rdoc' in the root directory
4 | of the plugin after you have installed it.
5 |
6 | === Generated Files
7 |
8 | When has_threaded_comments is fully installed it adds the following files to your application:
9 |
10 | * config/threaded_comments_connfig.yml
11 | * db/migrate/xxxxxxxxxxxxxx_create_threaded_comments.rb
12 | * public/stylesheets/threaded_comment_styles.css
13 | * public/has-threaded-comments-images/downmod-arrow.gif
14 | * public/has-threaded-comments-images/upmod-arrow.gif
15 |
16 | === Interfaces
17 |
18 | has_threaded_comments makes the following interfaces available to your application:
19 |
20 | Models:
21 | * ThreadedComment
22 | * ThreadedCommentObserver
23 | * ThreadedCommentNotifier
24 |
25 | Controllers:
26 | * ThreadedCommentsController
27 |
28 | Helpers:
29 | * render_threaded_comments
30 | * render_comment_form
31 |
32 | Routes:
33 | threaded_comment_index POST /threaded-comments(.:format)
34 | new_threaded_comment GET /threaded-comments/new(.:format)
35 | threaded_comment GET /threaded-comments/:id(.:format)
36 | flag_threaded_comment POST /threaded-comments/:id/flag
37 | upmod_threaded_comment POST /threaded-comments/:id/upmod
38 | downmod_threaded_comment POST /threaded-comments/:id/downmod
39 | remove_threaded_comment_notifications GET /threaded-comments/:id/remove-notifications/:hash
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/templates/threaded_comments_config.yml:
--------------------------------------------------------------------------------
1 | production:
2 | notifications:
3 | enable_notifications: true
4 | enable_comment_creation_failure_notifications: true
5 | site_domain: yoursite.com
6 | admin_email: admin@yoursite.com
7 | system_send_email_address: noreply@yoursite.com
8 | new_comment_subject: Yoursite.com - New comment
9 | failed_comment_creation_subject: Yoursite.com - Failed comment creation
10 | comment_reply_subject: Yoursite.com - {name} has replied to your comment
11 |
12 | render_threaded_comments:
13 | enable_rating: true
14 | enable_flagging: true
15 | flag_message: Are you really sure you want to flag this comment?
16 | reply_link_text: Reply
17 | max_indent: 3
18 | flag_threshold: 3
19 | header_separator: " - "
20 | no_comments_message: There aren't any comments yet, be the first to comment!
21 |
22 | render_comment_form:
23 | name_label: Name
24 | email_label: Email (so we can notify you when someone replies to your comment)
25 | body_label: 2000 characters max. HTML is not allowed.
26 | submit_title: Add Comment
27 | partial: threaded_comments/comment_form
28 | honeypot_name: confirm_email
29 |
30 | # If the config for a particular environment is not found, the default environment settings (production)
31 | # will be used. This makes it much easier to setup multiple environments with the same settings.
32 | # You can setup multiple environments by adding new entries with the appropriate top-level name eg:
33 | #
34 | # test:
35 | # notifications:
36 | # enable_notifications: true
37 | # enable_comment_creation_failure_notifications: true
38 | # site_domain: yoursite.com
39 | # admin_email: admin@yoursite.com
40 | # ...
41 | # ...
42 |
43 |
--------------------------------------------------------------------------------
/app/views/threaded_comments/_comment_form.erb:
--------------------------------------------------------------------------------
1 | <% remove_submit_script = <<-EOD
2 | var submitButton = document.getElementById('threaded_comment_submit_#{timestamp}');
3 | var loadingDiv = document.createElement('div');
4 | loadingDiv.className = 'threaded_comment_loading';
5 | submitButton.parentNode.appendChild(loadingDiv);
6 | submitButton.parentNode.removeChild(submitButton);
7 | EOD
8 |
9 | remove_message_script = <<-EOD
10 | message = document.getElementById('no_comments_message');
11 | message.parentNode.removeChild(message);
12 | EOD
13 | %>
14 |
15 |
--------------------------------------------------------------------------------
/app/models/threaded_comment_observer.rb:
--------------------------------------------------------------------------------
1 | class ThreadedCommentObserver < ActiveRecord::Observer
2 | def after_create( threaded_comment )
3 | return unless(THREADED_COMMENTS_CONFIG[:notifications][:enable_notifications])
4 | if(ThreadedCommentNotifier.respond_to?(:send_later))
5 | # Send admin notification
6 | ThreadedCommentNotifier.send_later(:deliver_new_comment_notification, threaded_comment )
7 |
8 | # Send user notifications if notifications are enabled
9 | # for the parent comment
10 | if(threaded_comment.parent_id == 0 || threaded_comment.parent_id.nil?)
11 | if( threaded_comment.threaded_comment_polymorphic.respond_to?(:notifications) && threaded_comment.threaded_comment_polymorphic.respond_to?(:email))
12 | ThreadedCommentNotifier.send_later(:deliver_comment_reply_notification, threaded_comment.threaded_comment_polymorphic.email, threaded_comment ) if( threaded_comment.threaded_comment_polymorphic.notifications )
13 | end
14 | else
15 | parent_comment = ThreadedComment.find( threaded_comment.parent_id )
16 | ThreadedCommentNotifier.send_later(:deliver_comment_reply_notification, parent_comment.email, threaded_comment ) if( parent_comment.notifications )
17 | end
18 | else
19 | # Send admin notification
20 | ThreadedCommentNotifier.deliver_new_comment_notification( threaded_comment )
21 |
22 | # Send user notifications if notifications are enabled
23 | # for the parent comment
24 | if(threaded_comment.parent_id == 0 || threaded_comment.parent_id.nil?)
25 | if( threaded_comment.threaded_comment_polymorphic.respond_to?(:notifications) && threaded_comment.threaded_comment_polymorphic.respond_to?(:email))
26 | ThreadedCommentNotifier.deliver_comment_reply_notification( threaded_comment.threaded_comment_polymorphic.email, threaded_comment ) if( threaded_comment.threaded_comment_polymorphic.notifications )
27 | end
28 | else
29 | parent_comment = ThreadedComment.find( threaded_comment.parent_id )
30 | ThreadedCommentNotifier.deliver_comment_reply_notification( parent_comment.email, threaded_comment ) if( parent_comment.notifications )
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/test/setup/initialize_controllers.rb:
--------------------------------------------------------------------------------
1 | class BooksController < ApplicationController
2 | # GET /books
3 | # GET /books.xml
4 | def index
5 | @books = Book.all
6 |
7 | respond_to do |format|
8 | format.html # index.html.erb
9 | format.xml { render :xml => @books }
10 | end
11 | end
12 |
13 | # GET /books/1
14 | # GET /books/1.xml
15 | def show
16 | @book = Book.find(params[:id], :include => {:comments => []})
17 | @new_comment = @book.comments.new(:name => session[:name], :email => session[:email])
18 |
19 | respond_to do |format|
20 | format.html # show.html.erb
21 | format.xml { render :xml => @book }
22 | end
23 | end
24 |
25 | # GET /books/new
26 | # GET /books/new.xml
27 | def new
28 | @book = Book.new
29 |
30 | respond_to do |format|
31 | format.html # new.html.erb
32 | format.xml { render :xml => @book }
33 | end
34 | end
35 |
36 | # GET /books/1/edit
37 | def edit
38 | @book = Book.find(params[:id])
39 | end
40 |
41 | # POST /books
42 | # POST /books.xml
43 | def create
44 | @book = Book.new(params[:book])
45 |
46 | respond_to do |format|
47 | if @book.save
48 | flash[:notice] = 'Book was successfully created.'
49 | format.html { redirect_to(@book) }
50 | format.xml { render :xml => @book, :status => :created, :location => @book }
51 | else
52 | format.html { render :action => "new" }
53 | format.xml { render :xml => @book.errors, :status => :unprocessable_entity }
54 | end
55 | end
56 | end
57 |
58 | # PUT /books/1
59 | # PUT /books/1.xml
60 | def update
61 | @book = Book.find(params[:id])
62 |
63 | respond_to do |format|
64 | if @book.update_attributes(params[:book])
65 | flash[:notice] = 'Book was successfully updated.'
66 | format.html { redirect_to(@book) }
67 | format.xml { head :ok }
68 | else
69 | format.html { render :action => "edit" }
70 | format.xml { render :xml => @book.errors, :status => :unprocessable_entity }
71 | end
72 | end
73 | end
74 |
75 | # DELETE /books/1
76 | # DELETE /books/1.xml
77 | def destroy
78 | @book = Book.find(params[:id])
79 | @book.destroy
80 |
81 | respond_to do |format|
82 | format.html { redirect_to(books_url) }
83 | format.xml { head :ok }
84 | end
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/test/unit/threaded_comment_observer_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 |
3 | class ThreadedCommentObserverTest < ActiveSupport::TestCase
4 |
5 | include DelayedJobStubs
6 |
7 | def setup
8 | @test_book = Book.create!(Factory.attributes_for(:book))
9 | @test_parent_comment = @test_book.comments.create!(Factory.attributes_for(:threaded_comment))
10 | end
11 |
12 | test "should observe comment creation and send notifications" do
13 | assert_difference("ActionMailer::Base.deliveries.length", 2) do
14 | @test_book.comments.create(Factory.attributes_for(:threaded_comment))
15 | end
16 | end
17 |
18 | test "should observe comment creation and send delayed notifications" do
19 | stub_send_later do
20 | assert_difference("$delayed_jobs.length", 2) do
21 | @test_book.comments.create(Factory.attributes_for(:threaded_comment))
22 | end
23 | end
24 | end
25 |
26 | test "should observe subcomment creation and send notifications" do
27 | assert_difference("ActionMailer::Base.deliveries.length", 2) do
28 | @test_book.comments.create!(Factory.attributes_for(:threaded_comment, :parent_id => @test_parent_comment.id))
29 | end
30 | end
31 |
32 | test "should observe subcomment creation and send delayed notifications" do
33 | stub_send_later do
34 | assert_difference("$delayed_jobs.length", 2) do
35 | @test_book.comments.create!(Factory.attributes_for(:threaded_comment, :parent_id => @test_parent_comment.id))
36 | end
37 | end
38 | end
39 |
40 | test "should only send one notification after subcomment creation on comment with notifications = false" do
41 | @test_parent_comment.notifications = false
42 | @test_parent_comment.save
43 | assert_difference("ActionMailer::Base.deliveries.length", 1) do
44 | @test_book.comments.create!(Factory.attributes_for(:threaded_comment, :parent_id => @test_parent_comment.id))
45 | end
46 | end
47 |
48 | test "should only send one delayed notification after subcomment creation on comment with notifications = false" do
49 | stub_send_later do
50 | @test_parent_comment.notifications = false
51 | @test_parent_comment.save
52 | assert_difference("$delayed_jobs.length", 1) do
53 | @test_book.comments.create!(Factory.attributes_for(:threaded_comment, :parent_id => @test_parent_comment.id))
54 | end
55 | end
56 | end
57 |
58 | end
59 |
--------------------------------------------------------------------------------
/test/functional/generator_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 | require 'rails_generator'
3 | require 'rails_generator/scripts/generate'
4 |
5 | class GeneratorTest < Test::Unit::TestCase
6 |
7 | def setup
8 | FileUtils.mkdir_p(fake_rails_root)
9 | FileUtils.mkdir_p(File.join(fake_rails_root, 'config'))
10 | FileUtils.mkdir_p(File.join(fake_rails_root, 'db', 'migrate'))
11 | FileUtils.mkdir_p(File.join(fake_rails_root, 'public', 'stylesheets'))
12 | FileUtils.mkdir_p(File.join(fake_rails_root, 'public', 'has-threaded-comments-images'))
13 | end
14 |
15 | def teardown
16 | FileUtils.rm_r(fake_rails_root)
17 | end
18 |
19 | def test_generates_threaded_comments_config
20 | @original_files = file_list('config')
21 | Rails::Generator::Scripts::Generate.new.run(["install_has_threaded_comments"], :destination => fake_rails_root, :quiet => true)
22 | new_file = (file_list('config') - @original_files).first
23 | assert_equal "threaded_comments_config.yml", File.basename(new_file)
24 | end
25 |
26 | def test_generates_threaded_comments_migration
27 | @original_files = file_list('db', 'migrate')
28 | Rails::Generator::Scripts::Generate.new.run(["install_has_threaded_comments"], :destination => fake_rails_root, :quiet => true)
29 | new_file = (file_list('db', 'migrate') - @original_files).first
30 | assert new_file.index('create_threaded_comments')
31 | end
32 |
33 | def test_generates_threaded_comments_styles_stylesheet
34 | @original_files = file_list('public', 'stylesheets')
35 | Rails::Generator::Scripts::Generate.new.run(["install_has_threaded_comments"], :destination => fake_rails_root, :quiet => true)
36 | new_file = (file_list('public', 'stylesheets') - @original_files).first
37 | assert_equal "threaded_comment_styles.css", File.basename(new_file)
38 | end
39 |
40 | def test_adds_images
41 | @original_files = file_list('public', 'has-threaded-comments-images')
42 | Rails::Generator::Scripts::Generate.new.run(["install_has_threaded_comments"], :destination => fake_rails_root, :quiet => true)
43 | new_files = (file_list('public', 'has-threaded-comments-images') - @original_files)
44 | assert_equal 3, new_files.length
45 | new_files.sort!
46 | assert_equal "ajax-loader.gif", File.basename(new_files.first)
47 | assert_equal "downmod-arrow.gif", File.basename(new_files[1])
48 | assert_equal "upmod-arrow.gif", File.basename(new_files.last)
49 | end
50 |
51 | private
52 |
53 | def fake_rails_root
54 | File.join(File.dirname(__FILE__), 'rails_root')
55 | end
56 |
57 | def file_list(*path)
58 | Dir.glob(File.join(fake_rails_root, path, '*'))
59 | end
60 |
61 | end
--------------------------------------------------------------------------------
/app/controllers/threaded_comments_controller.rb:
--------------------------------------------------------------------------------
1 | class ThreadedCommentsController < ActionController::Base
2 |
3 | before_filter :was_action_already_performed, :only => [:flag, :upmod, :downmod]
4 |
5 | # GET /threaded-comments
6 | def new
7 | @comment = ThreadedComment.new(params[:threaded_comment])
8 | @comment.name = session[:name] unless( session[:name].nil? )
9 | @comment.email = session[:email] unless( session[:email].nil? )
10 | render :layout => false
11 | end
12 |
13 | # GET /threaded-comments/1
14 | def show
15 | if(ThreadedComment.exists?(params[:id]))
16 | @comment = ThreadedComment.find(params[:id])
17 | render :layout => false and return
18 | end
19 | head :bad_request
20 | end
21 |
22 | # POST /threaded-comments
23 | def create
24 | head :status => :bad_request and return if check_honeypot( 'threaded_comment' )
25 | if( !params[:threaded_comment][:parent_id].nil? && params[:threaded_comment][:parent_id].to_i > 0 && !ThreadedComment.exists?(params[:threaded_comment][:parent_id]))
26 | flash[:notice] = "The comment you were trying to comment on no longer exists."
27 | head :status => :bad_request and return
28 | end
29 | @comment = ThreadedComment.new(params[:threaded_comment])
30 | if( @comment.save )
31 | session[:name] = @comment.name
32 | session[:email] = @comment.email
33 | render :action => 'show', :layout => false
34 | else
35 | render :action => 'new', :layout => false, :status => :bad_request
36 | end
37 | end
38 |
39 | # POST /threaded-comments/1/upmod
40 | def upmod
41 | begin
42 | @comment = ThreadedComment.find(params[:id])
43 | render :text => @comment.rating.to_s and return if(@comment.increment!('rating'))
44 | rescue ActiveRecord::RecordNotFound
45 | head :error
46 | end
47 | end
48 |
49 | # POST /threaded-comments/1/downmod
50 | def downmod
51 | begin
52 | @comment = ThreadedComment.find(params[:id])
53 | render :text => @comment.rating.to_s and return if(@comment.decrement!('rating'))
54 | rescue ActiveRecord::RecordNotFound
55 | head :error
56 | end
57 | end
58 |
59 | # POST /threaded-comments/1/flag
60 | def flag
61 | begin
62 | @comment = ThreadedComment.find(params[:id])
63 | render :text => "Thanks!" and return if(@comment.increment!('flags'))
64 | rescue ActiveRecord::RecordNotFound
65 | head :error
66 | end
67 | end
68 |
69 | # GET /threaded-comments/1/remove-notifications
70 | def remove_notifications
71 | @message = "The comment you are looking for has been removed or is incorrect." and render :action => 'remove_notifications' and return unless( ThreadedComment.exists?(params[:id]))
72 | @comment = ThreadedComment.find(params[:id])
73 | @message = "The information you provided does not match this comment." and render :action => 'remove_notifications' and return unless( params[:hash] == @comment.email_hash )
74 | @message = "Thank-you. Your email (#{@comment.email}) has been removed."
75 | @comment.notifications = false
76 | @comment.save
77 | render :action => 'remove_notifications'
78 | end
79 |
80 | private
81 |
82 | def was_action_already_performed
83 | if( session["/threaded-comments/#{params[:id]}/#{params[:action]}"].nil? && !cookies[:threaded_comment_cookies_enabled].nil? )
84 | session["/threaded-comments/#{params[:id]}/#{params[:action]}"] = true
85 | else
86 | head :status => :bad_request and return
87 | end
88 | end
89 |
90 | def check_honeypot( form_name, honeypot = "confirm_email" )
91 | unless( params[form_name][honeypot].nil? || (params[form_name][honeypot].length == 0) )
92 | return true
93 | end
94 | params[form_name].delete( honeypot )
95 | return false
96 | end
97 |
98 | end
--------------------------------------------------------------------------------
/test/unit/threaded_comment_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 | require 'digest/md5'
3 |
4 | class ThreadedCommentTest < ActiveSupport::TestCase
5 |
6 | def setup
7 | @test_book = Book.create!(Factory.attributes_for(:book))
8 | end
9 |
10 | test "threaded comment should be created" do
11 | assert_difference('ThreadedComment.count') do
12 | ThreadedComment.create!(Factory.attributes_for(:threaded_comment))
13 | end
14 | end
15 |
16 | test "threaded comment should not be created without name" do
17 | assert_no_difference('ThreadedComment.count') do
18 | ThreadedComment.create(Factory.attributes_for(:threaded_comment, :name => nil))
19 | end
20 | end
21 |
22 | test "threaded comment should not be create without body" do
23 | assert_no_difference('ThreadedComment.count') do
24 | ThreadedComment.create(Factory.attributes_for(:threaded_comment, :body => nil))
25 | end
26 | end
27 |
28 | test "threaded comment should not be created without email" do
29 | assert_no_difference('ThreadedComment.count') do
30 | ThreadedComment.create(Factory.attributes_for(:threaded_comment, :email => nil))
31 | end
32 | end
33 |
34 | test "threaded comment should not be created with junk email" do
35 | assert_no_difference('ThreadedComment.count') do
36 | ThreadedComment.create(Factory.attributes_for(:threaded_comment, :email => "asasdasdas"))
37 | end
38 | end
39 |
40 | test "threaded sub-comment should be created and associated with it's correct parent" do
41 | assert_difference('ThreadedComment.count', 2) do
42 | @test_comment = @test_book.comments.create!(Factory.attributes_for(:threaded_comment))
43 | @test_subcomment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment, :parent_id => @test_comment.id, :threaded_comment_polymorphic_id => nil, :threaded_comment_polymorphic_type => nil))
44 | @test_subcomment.reload
45 | assert_equal @test_comment.threaded_comment_polymorphic_id, @test_subcomment.threaded_comment_polymorphic_id
46 | assert_equal @test_comment.threaded_comment_polymorphic_type, @test_subcomment.threaded_comment_polymorphic_type
47 | end
48 | end
49 |
50 | test "threaded comment with nil parent_id defaults to zero" do
51 | assert_difference('ThreadedComment.count') do
52 | @test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment))
53 | @test_comment.reload
54 | assert_equal 0, @test_comment.parent_id
55 | end
56 | end
57 |
58 | test "threaded comment with empty parent_id defaults to zero" do
59 | assert_difference('ThreadedComment.count') do
60 | @test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment, :parent_id => ""))
61 | @test_comment.reload
62 | assert_equal 0, @test_comment.parent_id
63 | end
64 | end
65 |
66 | test "threaded comment rating should not be able to be set via mass assignment" do
67 | assert_difference('ThreadedComment.count') do
68 | @test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment, :rating => 20))
69 | @test_comment.reload
70 | assert_equal 0, @test_comment.rating
71 | end
72 | end
73 |
74 | test "threaded comment flags should not be able to be set via mass assignment" do
75 | assert_difference('ThreadedComment.count') do
76 | @test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment, :flags => 20))
77 | @test_comment.reload
78 | assert_equal 0, @test_comment.flags
79 | end
80 | end
81 |
82 | test "threaded comment email hash creation" do
83 | assert_difference('ThreadedComment.count') do
84 | @test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment))
85 | assert_equal Digest::MD5.hexdigest("#{@test_comment.email}-#{@test_comment.created_at}"), @test_comment.email_hash
86 | end
87 | end
88 |
89 | test "owner_item should alias threaded_comment_polymorphic" do
90 | @test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment))
91 | assert_equal @test_comment.owner_item, @test_comment.threaded_comment_polymorphic
92 | end
93 | end
--------------------------------------------------------------------------------
/generators/install_has_threaded_comments/templates/threaded_comment_styles.css:
--------------------------------------------------------------------------------
1 | /************************************************
2 | Styles for displaying comment threads
3 | *************************************************/
4 | .threaded_comment_loading{
5 | width: 100px;
6 | height: 32px;
7 | background: transparent url(/has-threaded-comments-images/ajax-loader.gif) center center no-repeat;
8 | }
9 |
10 | .threaded_comment_container{
11 | width: 100%;
12 | position: relative;
13 | padding-bottom: 10px;
14 | }
15 |
16 | .threaded_comment_container a{
17 | border: none;
18 | outline: none;
19 | }
20 |
21 | .fade_level_1 .threaded_comment_body{
22 | -moz-opacity: 0.80;
23 | -webkit-opacity: 0.80;
24 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
25 | filter: alpha(opacity=80);
26 | opacity: 0.80;
27 | }
28 |
29 | .fade_level_2 .threaded_comment_body{
30 | -moz-opacity: 0.60;
31 | -webkit-opacity: 0.60;
32 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";
33 | filter: alpha(opacity=60);
34 | opacity: 0.60;
35 | }
36 |
37 | .fade_level_3 .threaded_comment_body{
38 | -moz-opacity: 0.35;
39 | -webkit-opacity: 0.35;
40 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=35)";
41 | filter: alpha(opacity=35);
42 | opacity: 0.35;
43 | }
44 |
45 | .fade_level_4 .threaded_comment_body{
46 | -moz-opacity: 0.12;
47 | -webkit-opacity: 0.12;
48 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=12)";
49 | filter: alpha(opacity=12);
50 | opacity: 0.12;
51 | }
52 |
53 | .threaded_comment_container_header{
54 | color: #656565;
55 | font-size: 90%;
56 | }
57 |
58 | .threaded_comment_rating_container{
59 | display: block;
60 | width: 65px;
61 | height: 20px;
62 | float: left;
63 | }
64 |
65 | .upmod_threaded_comment{
66 | position: absolute;
67 | left: 0px;
68 | top: 0px;
69 | display: block;
70 | width: 20px;
71 | height: 20px;
72 | background: transparent url(/has-threaded-comments-images/upmod-arrow.gif) top left no-repeat;
73 | }
74 |
75 | .threaded_comment_rating_text{
76 | display: block;
77 | width: 20px;
78 | height: 20px;
79 | line-height: 20px;
80 | text-align: center;
81 | position: absolute;
82 | top: 0px;
83 | left: 20px;
84 | }
85 |
86 | .downmod_threaded_comment{
87 | position: absolute;
88 | left: 40px;
89 | top: 0px;
90 | display: block;
91 | width: 20px;
92 | height: 20px;
93 | background: transparent url(/has-threaded-comments-images/downmod-arrow.gif) top left no-repeat;
94 | }
95 |
96 | .threaded_comment_name{
97 | color: #2a2a2a;
98 | font-size: 110%;
99 | }
100 |
101 | .threaded_comment_age{
102 |
103 | }
104 |
105 | .threaded_comment_link{
106 | text-decoration: none;
107 | color: #656565;
108 | }
109 |
110 | .threaded_comment_link:hover{
111 | text-decoration: underline;
112 | }
113 |
114 | .flag_threaded_comment_container{
115 |
116 | }
117 |
118 | .flag_threaded_comment_container a{
119 | text-decoration: none;
120 | color: #656565;
121 | }
122 |
123 | .flag_threaded_comment_container a:hover{
124 | text-decoration: underline;
125 | }
126 |
127 | .threaded_comment_body{
128 | margin: 0;
129 | }
130 |
131 | .threaded_comment_body p{
132 | margin: 5px 0 3px 0;
133 | }
134 |
135 | .threaded_comment_reply_container{
136 |
137 | }
138 |
139 | .threaded_comment_reply_container a{
140 | text-decoration: none
141 | }
142 |
143 | .threaded_comment_reply_container a:hover{
144 | text-decoration: underline;
145 | }
146 |
147 | .threaded_comment_container_footer{
148 | }
149 |
150 | .subcomment_container{
151 | padding-left: 30px;
152 | }
153 |
154 | .subcomment_container_no_indent{
155 | }
156 |
157 | /* prevent indenting of subcomments that are within a comment
158 | that has indenting disabled */
159 | .subcomment_container_no_indent .subcomment_container{
160 | padding-left: 0;
161 | }
162 |
163 | /************************************************
164 | Styles for displaying the new comment form
165 | *************************************************/
166 | .new_threaded_comment_form{
167 |
168 | }
169 |
170 | .new_threaded_comment_email_confirm{
171 | /* this is to hide the honeypot field */
172 | position: absolute;
173 | left: -20000px;
174 | top: -20000px;
175 | }
176 |
177 | .new_threaded_comment_name{
178 |
179 | }
180 |
181 | .threaded_comment_name_input{
182 | width: 200px;
183 | }
184 |
185 | .new_threaded_comment_email{
186 |
187 | }
188 |
189 | .threaded_comment_email_input{
190 | width: 200px;
191 | }
192 |
193 | .new_threaded_comment_body{
194 |
195 | }
196 |
197 | .threaded_comment_body_input{
198 | width: 100%;
199 | height: 130px;
200 | }
201 |
202 | .new_threaded_comment_submit{
203 |
204 | }
205 |
206 | .threaded_comment_submit_input{
207 |
208 | }
209 |
210 | /************************************************
211 | Styles for form errors
212 | *************************************************/
213 | .fieldWithErrors input, .fieldWithErrors textarea{
214 | margin-top: 4px;
215 | outline: red dashed 2px;
216 | }
217 |
218 | .errorExplanation{
219 | margin-top: 15px;
220 | outline: red dashed 2px;
221 | padding: 10px 20px;
222 | background: #f2ebeb;
223 | }
--------------------------------------------------------------------------------
/test/unit/threaded_comment_notifier_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 |
3 | class ThreadedCommentNotifierTest < ActionMailer::TestCase
4 |
5 | def setup
6 | @test_book = Book.create!(Factory.attributes_for(:book))
7 | @test_comment = @test_book.comments.create!(Factory.attributes_for(:threaded_comment, :threaded_comment_polymorphic_id => nil, :threaded_comment_polymorphic_type => nil))
8 | end
9 |
10 | test "should send new comment notification" do
11 | assert_difference("ActionMailer::Base.deliveries.length", 1) do
12 | @email = ThreadedCommentNotifier.deliver_new_comment_notification( @test_comment )
13 | end
14 | assert_equal [THREADED_COMMENTS_CONFIG[:notifications][:admin_email]], @email.to
15 | assert_equal [THREADED_COMMENTS_CONFIG[:notifications][:system_send_email_address]], @email.from
16 | assert @email.subject.index( "New" ), "Email subject did not include 'New':\n#{@email.subject}"
17 | assert @email.body.index( @test_comment.body ), "Email did not include comment body:\n#{@email.body}"
18 | assert @email.body.index( @test_comment.name ), "Email did not include comment name:\n#{@email.body}"
19 | assert @email.body.index( @test_comment.email ), "Email did not include comment email address:\n#{@email.body}"
20 | assert @email.body.index( THREADED_COMMENTS_CONFIG[:notifications][:site_domain] + "/books/#{@test_comment.owner_item.id}#threaded_comment_" + @test_comment.id.to_s ), "Email did not include link to comment:\n#{@email.body}"
21 | end
22 |
23 | test "should send user comment reply notification" do
24 | assert_difference("ActionMailer::Base.deliveries.length", 1) do
25 | @email = ThreadedCommentNotifier.deliver_comment_reply_notification( 'test@test.com', @test_comment )
26 | end
27 | assert_equal [THREADED_COMMENTS_CONFIG[:notifications][:system_send_email_address]], @email.from
28 | assert @email.body.index( @test_comment.body ), "Email did not include comment body:\n#{@email.body}"
29 | assert @email.body.index( @test_comment.name ), "Email did not include comment name:\n#{@email.body}"
30 | assert_nil @email.body.index( @test_comment.email ), "Email should not include comment email address:\n#{@email.body}"
31 | assert @email.body.index( THREADED_COMMENTS_CONFIG[:notifications][:site_domain] + "/books/#{@test_comment.owner_item.id}\n" ), "Email did not include link to comment parent item:\n#{@email.body}"
32 | assert @email.body.index( THREADED_COMMENTS_CONFIG[:notifications][:site_domain] + "/books/#{@test_comment.owner_item.id}#threaded_comment_" + @test_comment.id.to_s ), "Email did not include link to comment:\n#{@email.body}"
33 | end
34 |
35 | test "should send user subcomment reply notification" do
36 | @test_subcomment = @test_book.comments.create!(Factory.attributes_for(:threaded_comment, :threaded_comment_polymorphic_id => nil, :threaded_comment_polymorphic_type => nil, :parent_id => @test_comment.id))
37 | assert_difference("ActionMailer::Base.deliveries.length", 1) do
38 | @email = ThreadedCommentNotifier.deliver_comment_reply_notification( @test_comment.email, @test_subcomment )
39 | end
40 | assert_equal [THREADED_COMMENTS_CONFIG[:notifications][:system_send_email_address]], @email.from
41 | assert @email.body.index( @test_subcomment.body ), "Email did not include comment body:\n#{@email.body}"
42 | assert @email.body.index( @test_subcomment.name ), "Email did not include comment name:\n#{@email.body}"
43 | assert_nil @email.body.index( @test_subcomment.email ), "Email should not include comment email address:\n#{@email.body}"
44 | assert @email.body.index( THREADED_COMMENTS_CONFIG[:notifications][:site_domain] + "/books/#{@test_subcomment.owner_item.id}\n" ), "Email did not include link to comment parent item:\n#{@email.body}"
45 | assert @email.body.index( THREADED_COMMENTS_CONFIG[:notifications][:site_domain] + "/books/#{@test_subcomment.owner_item.id}#threaded_comment_" + @test_subcomment.id.to_s ), "Email did not include link to comment:\n#{@email.body}"
46 | removal_link = THREADED_COMMENTS_CONFIG[:notifications][:site_domain] + "/threaded-comments/#{@test_comment.id}/remove-notifications/#{@test_comment.email_hash}"
47 | assert @email.body.index(removal_link), "Email did not include notification removal link:\n\n#{removal_link}\n\n#{@email.body}"
48 | end
49 |
50 | test "should send failed comment notification" do
51 | assert_difference("ActionMailer::Base.deliveries.length", 1) do
52 | @email = ThreadedCommentNotifier.deliver_failed_comment_creation_notification( @test_comment )
53 | end
54 | assert_equal [THREADED_COMMENTS_CONFIG[:notifications][:admin_email]], @email.to
55 | assert_equal [THREADED_COMMENTS_CONFIG[:notifications][:system_send_email_address]], @email.from
56 | assert @email.subject.index( "Failed" ), "Email subject did not include 'Failed'"
57 | assert @email.body.index( @test_comment.body ), "Email did not include comment body"
58 | assert @email.body.index( @test_comment.name ), "Email did not include comment name"
59 | assert @email.body.index( @test_comment.email ), "Email did not include comment email address"
60 | end
61 |
62 | end
63 |
--------------------------------------------------------------------------------
/app/helpers/threaded_comments_helper.rb:
--------------------------------------------------------------------------------
1 | module ThreadedCommentsHelper
2 |
3 | def render_threaded_comments(comments, options={})
4 | options = {
5 | :indent_level => 0,
6 | :base_indent => 0,
7 | :parent_id => 0,
8 | :bucketed => false
9 | }.merge(THREADED_COMMENTS_CONFIG[:render_threaded_comments].dup).merge(options)
10 |
11 | return '' unless(comments.length > 0)
12 | unless(options[:bucketed])
13 | comments = comments.delete_if{|comment| (comment.flags > options[:flag_threshold]) && (options[:flag_threshold] > 0) }
14 | comments = sort_comments(comments)
15 | options[:parent_id] = comments.first.parent_id if(comments.length == 1)
16 | comments = bucket_comments(comments)
17 | end
18 | return '' if( comments[options[:parent_id]].nil? )
19 | ret = ''
20 | this_indent = " " * (options[:base_indent] + options[:indent_level])
21 |
22 | comments[options[:parent_id]].each do |comment|
23 | ret << this_indent << "\n"
24 | ret << this_indent << "\n"
48 |
49 | ret << this_indent << "\n"
52 | end
53 | return ret
54 | end
55 |
56 | def render_comment_form(comment, options={})
57 | options = {
58 | :timestamp => Time.now.to_i.to_s,
59 | :comment => comment
60 | }.merge(THREADED_COMMENTS_CONFIG[:render_comment_form].dup).merge(options)
61 | render :partial => options[:partial], :locals => options
62 | end
63 |
64 | private
65 |
66 | def sort_comments(comments)
67 | comments.sort {|a,b|
68 | ((b.rating.to_f + 1.0) / ((((Time.now - b.created_at) / 3600).to_f + 0.5) ** 1.25)) <=> ((a.rating.to_f + 1.0) / ((((Time.now - a.created_at) / 3600).to_f + 0.5) ** 1.25))
69 | }
70 | end
71 |
72 | def bucket_comments(comments)
73 | bucketed_comments = []
74 | comments.each do |comment|
75 | bucketed_comments[comment.parent_id] = [] if( bucketed_comments[comment.parent_id].nil? )
76 | bucketed_comments[comment.parent_id] << comment
77 | end
78 | return bucketed_comments
79 | end
80 |
81 | end
82 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = has_threaded_comments
2 |
3 | Feature highlights:
4 | * Only 7 lines of code need to be added to your application!
5 | * Complete comment system including UI
6 | * Minimal barrier to entry for users (no captchas or account creation required)
7 | * Encourages continuing community participation via comment rating, popularity based sorting, and comment reply notifications
8 | * Discourages bad behavior via fading of negatively rated comments, and user flagging of comments
9 | * Automatic comment moderation using validates_text_content (optional)
10 |
11 | has_threaded_comments is a Rails plugin that provides an entire threaded commenting
12 | system with the addition of only a few lines of code to your application. The system includes
13 | support for rating and flagging of comments as well as automatically generating email
14 | notifications (for both users and website admin) of replies to comments. Additionally
15 | if you have {delayed_job}[http://github.com/tobi/delayed_job] setup then has_threaded_comments
16 | will automatically make use of that to send email notifications in a asynchronous manner.
17 |
18 | has_threaded_comments does not require the user be logged in to create a comment, in
19 | keeping with the idea of keeping the barrier to entry as low as possible. It uses
20 | reverse captchas (honeypots) to foil spambots.
21 |
22 | Additionally has_threaded_comments will use {validates_text_content}[http://github.com/aarongough/validates_text_content] to provide automatic
23 | content moderation if the {validates_text_content}[http://github.com/aarongough/validates_text_content] plugin has been installed.
24 |
25 | If you have any questions or find an issue with this plugin please contact me at: mailto:aaron@aarongough.com
26 |
27 | === Examples
28 |
29 | To see an installation of has_threaded_comments in the wild check out the comment system at: {WhyIAmAngry.com}[http://whyiamangry.com/]
30 |
31 | === Installation
32 |
33 | To install the plugin use one of the following commands:
34 |
35 | # To install via Git using script/plugin:
36 | ./script/plugin install git://github.com/aarongough/has_threaded_comments.git
37 |
38 | # To install via SVN using script/plugin
39 | ./script/plugin install http://svn.github.com/aarongough/has_threaded_comments.git
40 |
41 | # If your application is not under version control you can still install using a SVN export:
42 | ./script/plugin install -e http://svn.github.com/aarongough/has_threaded_comments.git
43 |
44 | Then you need to copy the configuration files, database migration and UI files into your application like so:
45 |
46 | ./script/generate install_has_threaded_comments
47 |
48 | === Usage
49 |
50 | Then follow these steps to use the comment system in your application:
51 |
52 | 1. Make sure you you have no pending database migrations
53 |
54 | rake db:migrate
55 |
56 | 2. Add the has_threaded_comments declaration to your model
57 |
58 | # app/models/book.rb
59 | class Book < ActiveRecord::Base
60 | has_threaded_comments
61 | end
62 |
63 | 3. Add the code for eager-loading comments and generating a blank comment to your controller
64 |
65 | # app/controllers/books_controller.rb
66 | def show
67 | @book = Book.find(params[:id], :include => {:comments => []})
68 | @new_comment = @book.comments.new(:name => session[:name], :email => session[:email])
69 | end
70 |
71 | 4. Add the code for rendering the comments and the new comment form to your 'show' view
72 |
73 | # app/views/books/show.html.erb
74 |
78 |
79 | 5. Add threaded_comment_styles.css and prototype.js to your layout
80 |
81 | # app/views/layouts/books.html.erb
82 |
83 | <%= javascript_include_tag :defaults -%>
84 | <%= stylesheet_link_tag 'threaded_comment_styles' -%>
85 |
86 |
87 | 6. Change the settings in config/threaded_comments_config.yml according to the needs of your application, eg:
88 |
89 | # config/threaded_comments_config.yml
90 | production:
91 | notifications:
92 | enable_notifications: true
93 | enable_comment_creation_failure_notifications: true
94 | site_domain: yoursite.com
95 | admin_email: admin@yoursite.com
96 | system_send_email_address: noreply@yoursite.com
97 | new_comment_subject: Yoursite.com - New comment
98 | failed_comment_creation_subject: Yoursite.com - Failed comment creation
99 | comment_reply_subject: Yoursite.com - {name} has replied to your comment
100 |
101 | render_threaded_comments:
102 | enable_rating: true
103 | enable_flagging: true
104 | ...
105 | ...
106 |
107 | 7. Enjoy!
108 |
109 | === Upgrading
110 |
111 | Upgrading from a previous version of has_threaded_comments is easy!
112 |
113 | # Run this from your application's root directory
114 | ./script/plugin remove has_threaded_comments
115 |
116 | # Then re-follow the instructions listed above in the 'installation' section
117 |
118 | If you have made changes to any of the has_threaded_comments config or asset files the generator
119 | will ask you whether or not to overwrite them. It's recommended to backup all these files, overwrite
120 | them with the new versions and then make any changes you need to. Older versions of the config files
121 | will cause errors with newer plugin code unless they are updated.
122 |
123 | === More info
124 |
125 | Please refer to MORE_INFO.rdoc
126 |
127 | === Author & Credits
128 |
129 | Author:: {Aaron Gough}[mailto:aaron@aarongough.com]
130 | Contributors:: {Julio Capote}[http://github.com/capotej], {Noah Litvin}[http://github.com/noahlitvin]
131 |
132 | Copyright (c) 2010 {Aaron Gough}[http://thingsaaronmade.com/] ({thingsaaronmade.com}[http://thingsaaronmade.com/]), released under the MIT license
--------------------------------------------------------------------------------
/test/functional/threaded_comments_controller_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 |
3 | class ThreadedCommentsControllerTest < ActionController::TestCase
4 |
5 | def setup
6 | @test_book = Book.create!(Factory.attributes_for(:book))
7 | ThreadedComment.create!(Factory.attributes_for(:threaded_comment))
8 | @request.cookies['threaded_comment_cookies_enabled'] = CGI::Cookie.new('threaded_comment_cookies_enabled', 'true')
9 | end
10 |
11 | test "should get show" do
12 | @test_comment = ThreadedComment.new(Factory.attributes_for(:threaded_comment, :parent_id => 0))
13 | @test_comment.save
14 | get :show, :id => @test_comment.id
15 | assert_response :success, @response.body
16 | assert_not_nil assigns(:comment)
17 | assert @response.body.index(@test_comment.name), "Did not include comment name"
18 | assert @response.body.index(@test_comment.body), "Did not include comment body"
19 | assert @response.body.index(upmod_threaded_comment_path(@test_comment)), "Did not include link to upmod"
20 | assert @response.body.index(downmod_threaded_comment_path(@test_comment)), "Did not include link to downmod"
21 | assert @response.body.index(flag_threaded_comment_path(@test_comment)), "Did not include link to flag"
22 | assert @response.body.index(new_threaded_comment_path), "Did not include link to new"
23 | end
24 |
25 | test "show should not display threaded comments with flags greater than flag_threshold" do
26 | @test_comment = ThreadedComment.new(Factory.attributes_for(:threaded_comment, :name => "Flagged Commenter"))
27 | @test_comment.flags = 99999999
28 | @test_comment.save
29 | get :show, :id => @test_comment.id
30 | assert_response :success, @response.body
31 | assert_not_nil assigns(:comment)
32 | assert_nil @response.body.index(@test_comment.name), "Should not include comment name"
33 | assert_nil @response.body.index(@test_comment.body), "Should not include comment body"
34 | end
35 |
36 | test "should create comment" do
37 | assert_difference('ThreadedComment.count') do
38 | @test_comment = Factory.attributes_for(:threaded_comment)
39 | put :create, :threaded_comment => @test_comment
40 | assert_response :success
41 | assert @response.body.index(@test_comment[:name]), "Did not include comment name"
42 | assert @response.body.index(@test_comment[:body]), "Did not include comment body"
43 | end
44 | end
45 |
46 | test "should create sub-comment" do
47 | @test_parent_comment = @test_book.comments.create!(Factory.attributes_for(:threaded_comment))
48 | @test_comment = Factory.attributes_for(:threaded_comment, :parent_id => @test_parent_comment.id.to_s)
49 | assert_difference('ThreadedComment.count') do
50 | put :create, :threaded_comment => @test_comment
51 | assert_response :success
52 | assert @response.body.index(@test_comment[:name]), "Did not include comment name"
53 | assert @response.body.index(@test_comment[:body]), "Did not include comment body"
54 | end
55 | end
56 |
57 | test "should not create comment if negative captcha is filled" do
58 | assert_no_difference('ThreadedComment.count') do
59 | put :create, :threaded_comment => Factory.attributes_for(:threaded_comment, :confirm_email => "test@example.com")
60 | end
61 | assert_response :bad_request
62 | end
63 |
64 | test "should get new" do
65 | session[:name] = "Test Name"
66 | session[:email] = "Test Name"
67 | @test_comment = Factory.attributes_for(:threaded_comment, :name => nil, :email => nil, :parent_id => "2")
68 | get :new, :threaded_comment => @test_comment
69 | assert_response :success
70 | assert_not_nil assigns(:comment)
71 | assert @response.body.include?(session[:name]), "Response body did not include commenter name"
72 | assert @response.body.include?(session[:email]), "Response body did not include commenter email"
73 | assert @response.body.include?(@test_comment[:body]), "Response body did not include body"
74 | assert @response.body.include?(@test_comment[:threaded_comment_polymorphic_id].to_s), "Response body did not include threaded_comment_polymorphic_id"
75 | assert @response.body.include?(@test_comment[:threaded_comment_polymorphic_type]), "Response body did not include threaded_comment_polymorphic_type"
76 | assert @response.body.include?(@test_comment[:parent_id]), "Response body did not include parent_id"
77 | assert @response.body.include?("threaded_comment[name]"), "Response body did not include form for name"
78 | assert @response.body.include?("threaded_comment[body]"), "Response body did not include form for body"
79 | assert @response.body.include?("threaded_comment[email]"), "Response body did not include form for email"
80 | assert @response.body.include?("threaded_comment[threaded_comment_polymorphic_id]"), "Response body did not include form for threaded_comment_polymorphic_id"
81 | assert @response.body.include?("threaded_comment[threaded_comment_polymorphic_type]"), "Response body did not include form for threaded_comment_polymorphic_type"
82 | assert @response.body.include?("threaded_comment[parent_id]"), "Response body did not include form for parent_id"
83 | assert @response.body.include?("threaded_comment[#{THREADED_COMMENTS_CONFIG[:render_comment_form][:honeypot_name]}]"), "Response body did not include honeypot form"
84 | assert @response.body.include?(THREADED_COMMENTS_CONFIG[:render_comment_form][:name_label]), "Response body did not include name label"
85 | assert @response.body.include?(THREADED_COMMENTS_CONFIG[:render_comment_form][:email_label]), "Response body did not include email label"
86 | assert @response.body.include?(THREADED_COMMENTS_CONFIG[:render_comment_form][:body_label]), "Response body did not include body label"
87 | assert @response.body.include?(THREADED_COMMENTS_CONFIG[:render_comment_form][:submit_title]), "Response body did not include submit title"
88 | assert @response.body.include?('removeChild(message)'), "Response body did not include javascript callback for removing no_comments_message"
89 | end
90 |
91 | test "should upmod comment" do
92 | assert_difference('ThreadedComment.find(1).rating') do
93 | post :upmod, :id => 1
94 | assert_response :success
95 | assert @response.body.index(@expected_rating.to_s), "Response body did not include new rating"
96 | end
97 | end
98 |
99 | test "upmodding non-existant comment should cause error" do
100 | post :upmod, :id => 9999999
101 | assert_response :error
102 | end
103 |
104 | test "should downmod comment" do
105 | assert_difference('ThreadedComment.find(1).rating', -1) do
106 | post :downmod, :id => 1
107 | assert_response :success
108 | assert @response.body.index(@expected_rating.to_s), "Response body did not include new rating"
109 | end
110 | end
111 |
112 | test "downmodding non-existant comment should cause error" do
113 | post :downmod, :id => 9999999
114 | assert_response :error
115 | end
116 |
117 | test "should flag comment" do
118 | assert_difference('ThreadedComment.find(1).flags') do
119 | post :flag, :id => 1
120 | assert_response :success
121 | end
122 | end
123 |
124 | test "flagging non-existant comment should cause error" do
125 | post :flag, :id => 9999999
126 | assert_response :error
127 | end
128 |
129 | test "should only allow rating or flagging once per action per session" do
130 | @actions = [
131 | { :action => 'flag', :field => 'flags', :difference => 1},
132 | { :action => 'upmod', :field => 'rating', :difference => 1},
133 | { :action => 'downmod', :field => 'rating', :difference => -1}
134 | ]
135 | @actions.each do |action|
136 | test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment))
137 | assert_difference("test_comment.#{action[:field]}", action[:difference], "Action failed first time: #{action[:action]}") do
138 | put action[:action], :id => test_comment.id
139 | assert_response :success
140 | test_comment.reload
141 | end
142 | assert_no_difference( "test_comment.#{action[:field]}", "Action succeeded when it should have failed: #{action[:action]}") do
143 | put action[:action], :id => test_comment.id
144 | assert_response :bad_request
145 | test_comment.reload
146 | end
147 | end
148 | end
149 |
150 | test "actions should fail if cookies are disabled" do
151 | @request.cookies['threaded_comment_cookies_enabled'] = nil
152 | @actions = [
153 | { :action => 'flag', :field => 'flags', :difference => 1},
154 | { :action => 'upmod', :field => 'rating', :difference => 1},
155 | { :action => 'downmod', :field => 'rating', :difference => -1}
156 | ]
157 | @actions.each do |action|
158 | test_comment = ThreadedComment.create!(Factory.attributes_for(:threaded_comment))
159 | assert_no_difference("test_comment.#{action[:field]}", "Action failed first time: #{action[:action]}") do
160 | put action[:action], :id => test_comment.id
161 | assert_response :bad_request
162 | test_comment.reload
163 | end
164 | end
165 | end
166 |
167 | test "should remove email notifications if hash matches" do
168 | test_comment = ThreadedComment.find(1)
169 | assert !test_comment.email.empty?
170 | assert test_comment.notifications == true
171 | get :remove_notifications, :id => 1, :hash => test_comment.email_hash
172 | assert_response :success
173 | test_comment.reload
174 | assert !test_comment.email.empty?
175 | assert test_comment.notifications == false
176 | assert @response.body.index( "removed" ), "Removal notice was not included in response body"
177 | end
178 |
179 | test "should not remove email notifications if hash does not match" do
180 | test_comment = ThreadedComment.find(1)
181 | assert !test_comment.email.empty?
182 | assert test_comment.notifications == true
183 | get :remove_notifications, :id => 1, :hash => test_comment.email_hash + "1"
184 | assert_response :success
185 | test_comment.reload
186 | assert !test_comment.email.empty?
187 | assert test_comment.notifications == true
188 | assert @response.body.index( "The information you provided does not match" ), "Failure notice was not included in response body"
189 | end
190 | end
--------------------------------------------------------------------------------
/test/unit/threaded_comments_helper_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2 |
3 | class ThreadedCommentsHelperTest < ActionView::TestCase
4 |
5 | include ThreadedCommentsHelper
6 | include ActionViewStubs
7 |
8 | def setup
9 | @test_book = Book.create!(Factory.attributes_for(:book))
10 | @test_comments = create_complex_thread(2)
11 | @test_comment = Factory.build(:threaded_comment)
12 | @rendered_html = render_threaded_comments(@test_comments)
13 | end
14 |
15 | test "render_threaded_comments should output no_comments_message when comments.length = 0" do
16 | @rendered_html = render_threaded_comments([])
17 | assert @rendered_html.include?(THREADED_COMMENTS_CONFIG[:render_threaded_comments][:no_comments_message]), "The 'no comments' message was not included"
18 | assert @rendered_html.include?('id="no_comments_message"'), "The no_comments_message container was not included"
19 | end
20 |
21 | test "render_threaded_comments should output comment names" do
22 | @test_comments.each do |comment|
23 | assert @rendered_html.include?(comment.name), "Did not include comment name"
24 | end
25 | end
26 |
27 | test "render_threaded_comments should escape comment names" do
28 | test_comment = Factory.build(:threaded_comment, :name => "<> Aaron")
29 | rendered_html = render_threaded_comments([test_comment])
30 | assert rendered_html.include?(h(test_comment.name)), "Did not escape comment name"
31 | end
32 |
33 | test "render_threaded_comments should output comment bodies" do
34 | @test_comments.each do |comment|
35 | assert @rendered_html.include?(comment.body), "Did not include comment body"
36 | end
37 | end
38 |
39 | test "render_threaded_comments should escape comment bodies" do
40 | test_comment = Factory.build(:threaded_comment, :body => "<> Aaron")
41 | rendered_html = render_threaded_comments([test_comment])
42 | assert rendered_html.include?(h(test_comment.body)), "Did not escape comment body"
43 | end
44 |
45 | test "render_threaded_comments should output comment creation times" do
46 | @test_comments.each do |comment|
47 | assert @rendered_html.include?(time_ago_in_words(comment.created_at)), "Did not include comment creation time"
48 | end
49 | end
50 |
51 | test "render_threaded_comments should output anchor for each comment" do
52 | @test_comments.each do |comment|
53 | assert @rendered_html.include?("threaded_comment_#{comment.id}"), "Did not include anchor for comment"
54 | end
55 | end
56 |
57 | test "render_threaded_comments should output subcomment container for each comment" do
58 | @test_comments.each do |comment|
59 | assert @rendered_html.include?("subcomment_container_#{comment.id}"), "Did not include subcomment container for comment"
60 | end
61 | end
62 |
63 | test "render_threaded_comments options and config" do
64 | test_option "rating text", :enable_rating, "threaded_comment_rating_:id"
65 | test_option "upmod button", :enable_rating, link_to_remote('', :url => {:controller => "threaded_comments", :action => "upmod", :id => ":id"})
66 | test_option "downmod button", :enable_rating, link_to_remote('', :url => {:controller => "threaded_comments", :action => "downmod", :id => ":id"})
67 | test_option "flag button", :enable_flagging, link_to_remote('flag', :url => {:controller => "threaded_comments", :action => "flag", :id => ":id"})
68 | test_option "flag button container", :enable_flagging, "flag_threaded_comment_container_:id"
69 | test_option "reply link text", :reply_link_text, "Reply"
70 | end
71 |
72 | test "render_threaded_comments should not overwrite global config when options are set" do
73 | old_config = old_config = THREADED_COMMENTS_CONFIG.dup
74 | @rendered_html = render_threaded_comments(@test_comments, :enable_flagging => false)
75 | assert_equal old_config, THREADED_COMMENTS_CONFIG
76 | end
77 |
78 | test "render_threaded_comments should not mark comments with more than max_indent ancestors as indented" do
79 | 10.times do |max_indent|
80 | @rendered_html = render_threaded_comments(@test_comments, :max_indent => max_indent)
81 | @test_comments.each do |comment|
82 | ancestors = 0
83 | if(comment.parent_id > 0)
84 | parent_comment = @test_comments[comment.parent_id - @test_comments.first.id]
85 | ancestors += 1
86 | assert_equal comment.parent_id, parent_comment.id
87 | until(parent_comment.parent_id == 0) do
88 | parent_comment = @test_comments[parent_comment.parent_id - @test_comments.first.id]
89 | ancestors += 1
90 | end
91 | end
92 | subcomment_container_position = @rendered_html.index("subcomment_container_#{comment.id}")
93 | assert_not_nil subcomment_container_position
94 | @subcomment_html = @rendered_html.slice(subcomment_container_position - 100, 200)
95 | assert @subcomment_html.include?('class="subcomment_container"'), "Expecting 'class=\"subcomment_container\"':\n" + @subcomment_html if(ancestors < max_indent)
96 | assert @subcomment_html.include?('class="subcomment_container_no_indent"'), "Expecting 'class=\"subcomment_container_no_indent\"':\n" + @subcomment_html if(ancestors >= max_indent)
97 | end
98 | end
99 | end
100 |
101 | test "render_threaded_comments should not mark comments with a rating of 5 with fade_level" do
102 | test_comment = Factory.build(:threaded_comment, :rating => 5)
103 | @rendered_html = render_threaded_comments([test_comment])
104 | assert !@rendered_html.include?("fade_level"), "Comment should not be marked with fade level"
105 | end
106 |
107 | test "render_threaded_comments should not mark comments with a rating of 0 with fade_level" do
108 | test_comment = Factory.build(:threaded_comment, :rating => 0)
109 | @rendered_html = render_threaded_comments([test_comment])
110 | assert !@rendered_html.include?("fade_level"), "Comment should not be marked with fade level"
111 | end
112 |
113 | test "render_threaded_comments should mark comments with a rating of -1 with fade_level_1" do
114 | test_comment = Factory.build(:threaded_comment, :rating => -1)
115 | @rendered_html = render_threaded_comments([test_comment])
116 | assert @rendered_html.include?("fade_level_1"), "Comment was not marked with appropriate fade level"
117 | end
118 |
119 | test "render_threaded_comments should mark comments with a rating of -2 with fade_level_2" do
120 | test_comment = Factory.build(:threaded_comment, :rating => -2)
121 | @rendered_html = render_threaded_comments([test_comment])
122 | assert @rendered_html.include?("fade_level_2"), "Comment was not marked with appropriate fade level"
123 | end
124 |
125 | test "render_threaded_comments should mark comments with a rating of -3 with fade_level_3" do
126 | test_comment = Factory.build(:threaded_comment, :rating => -3)
127 | @rendered_html = render_threaded_comments([test_comment])
128 | assert @rendered_html.include?("fade_level_3"), "Comment was not marked with appropriate fade level"
129 | end
130 |
131 | test "render_threaded_comments should mark comments with a rating of -4 with fade_level_4" do
132 | test_comment = Factory.build(:threaded_comment, :rating => -4)
133 | @rendered_html = render_threaded_comments([test_comment])
134 | assert @rendered_html.include?("fade_level_4"), "Comment was not marked with appropriate fade level"
135 | end
136 |
137 | test "render_threaded_comments should mark comments with a rating of -5 with fade_level_4" do
138 | test_comment = Factory.build(:threaded_comment, :rating => -5)
139 | @rendered_html = render_threaded_comments([test_comment])
140 | assert @rendered_html.include?("fade_level_4"), "Comment was not marked with appropriate fade level"
141 | end
142 |
143 | test "render_threaded_comments should mark comments with a rating of -10 with fade_level_4" do
144 | test_comment = Factory.build(:threaded_comment, :rating => -10)
145 | @rendered_html = render_threaded_comments([test_comment])
146 | assert @rendered_html.include?("fade_level_4"), "Comment was not marked with appropriate fade level"
147 | end
148 |
149 | test "render_threaded_comments child comments of a flagged comment should not be shown" do
150 | @test_comments[0].flags = 5
151 | @rendered_html = render_threaded_comments(@test_comments, :flag_threshold => 4)
152 | @test_comments.each do |comment|
153 | if(comment.parent_id == @test_comments[0].id)
154 | assert !@rendered_html.include?(comment.name), "render_threaded_comments should not show child comments of a flagged comment"
155 | end
156 | end
157 | end
158 |
159 | test "sort_comments: comments should be sorted by age if ratings are all equal" do
160 | comments = []
161 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 4.hours.ago)
162 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 3.hours.ago)
163 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 2.hours.ago)
164 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 1.hours.ago)
165 | sorted_comments = sort_comments(comments)
166 | comments.reverse!
167 | assert sorted_comments == comments, "Should be:\n#{comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}\nBut was:\n#{sorted_comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}"
168 | end
169 |
170 | test "sort_comments: comments should be sorted by rating if ages are all equal" do
171 | age = 1.hours.ago
172 | comments = []
173 | comments << Factory.build(:threaded_comment, :rating => 5, :created_at => age)
174 | comments << Factory.build(:threaded_comment, :rating => 4, :created_at => age)
175 | comments << Factory.build(:threaded_comment, :rating => 3, :created_at => age)
176 | comments << Factory.build(:threaded_comment, :rating => 2, :created_at => age)
177 | sorted_comments = sort_comments(comments)
178 | assert sorted_comments == comments, "Should be:\n#{comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}\nBut was:\n#{sorted_comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}"
179 | end
180 |
181 | test "sort_comments: 'hot' comment should be at the top of the comment list" do
182 | comments = []
183 | comments << Factory.build(:threaded_comment, :rating => 10, :created_at => 4.hours.ago)
184 | comments << Factory.build(:threaded_comment, :rating => 5, :created_at => 4.hours.ago)
185 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 2.hours.ago)
186 | comments << Factory.build(:threaded_comment, :rating => 0, :created_at => 1.hours.ago)
187 | sorted_comments = sort_comments(comments)
188 | assert sorted_comments == comments, "Should be:\n#{comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}\nBut was:\n#{sorted_comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}"
189 | end
190 |
191 | test "sort_comments: really recent comment should be at the top of the comment list" do
192 | comments = []
193 | comments << Factory.build(:threaded_comment, :rating => 10, :created_at => 4.hours.ago)
194 | comments << Factory.build(:threaded_comment, :rating => 5, :created_at => 4.hours.ago)
195 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 2.hours.ago)
196 | comments << Factory.build(:threaded_comment, :rating => 0, :created_at => 1.hours.ago)
197 | comments << Factory.build(:threaded_comment, :rating => 0, :created_at => 5.minutes.ago)
198 | sorted_comments = sort_comments(comments)
199 | comments.insert(0, comments.pop)
200 | assert sorted_comments == comments, "Should be:\n#{comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}\nBut was:\n#{sorted_comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}"
201 | end
202 |
203 | test "sort_comments: recent comment should be near the top of the comment list" do
204 | comments = []
205 | comments << Factory.build(:threaded_comment, :rating => 10, :created_at => 4.hours.ago)
206 | comments << Factory.build(:threaded_comment, :rating => 5, :created_at => 4.hours.ago)
207 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 2.hours.ago)
208 | comments << Factory.build(:threaded_comment, :rating => 0, :created_at => 1.hours.ago)
209 | comments << Factory.build(:threaded_comment, :rating => 0, :created_at => 15.minutes.ago)
210 | sorted_comments = sort_comments(comments)
211 | comments.insert(1, comments.pop)
212 | assert sorted_comments == comments, "Should be:\n#{comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}\nBut was:\n#{sorted_comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}"
213 | end
214 |
215 | test "sort_comments: somewhat recent comment should be near the top of the comment list" do
216 | comments = []
217 | comments << Factory.build(:threaded_comment, :rating => 10, :created_at => 4.hours.ago)
218 | comments << Factory.build(:threaded_comment, :rating => 5, :created_at => 4.hours.ago)
219 | comments << Factory.build(:threaded_comment, :rating => 1, :created_at => 2.hours.ago)
220 | comments << Factory.build(:threaded_comment, :rating => 0, :created_at => 1.hours.ago)
221 | comments << Factory.build(:threaded_comment, :rating => 0, :created_at => 35.minutes.ago)
222 | sorted_comments = sort_comments(comments)
223 | comments.insert(2, comments.pop)
224 | assert sorted_comments == comments, "Should be:\n#{comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}\nBut was:\n#{sorted_comments.map{|a| " #{a.id}, #{a.rating}, #{(Time.now - a.created_at) / 3600}\n"}}"
225 | end
226 |
227 | test "should bucket comments for rendering" do
228 | test_comments = create_complex_thread(2)
229 | assert test_comments.first.is_a?(ThreadedComment)
230 | bucketed_comments = bucket_comments(test_comments)
231 | assert_not_equal bucketed_comments, test_comments
232 | assert_equal test_comments.last.id - 1, bucketed_comments.length, bucketed_comments.inspect
233 | end
234 |
235 | test "render_comment_form should use name label from config" do
236 | passthrough = render_comment_form(@test_comment)
237 | assert_equal passthrough[:locals][:name_label], THREADED_COMMENTS_CONFIG[:render_comment_form][:name_label]
238 | end
239 |
240 | test "render_comment_form should use email label from config" do
241 | passthrough = render_comment_form(@test_comment)
242 | assert_equal passthrough[:locals][:email_label], THREADED_COMMENTS_CONFIG[:render_comment_form][:email_label]
243 | end
244 |
245 | test "render_comment_form should use body label from config" do
246 | passthrough = render_comment_form(@test_comment)
247 | assert_equal passthrough[:locals][:body_label], THREADED_COMMENTS_CONFIG[:render_comment_form][:body_label]
248 | end
249 |
250 | test "render_comment_form should use submit label from config" do
251 | passthrough = render_comment_form(@test_comment)
252 | assert_equal passthrough[:locals][:submit_label], THREADED_COMMENTS_CONFIG[:render_comment_form][:submit_label]
253 | end
254 |
255 | test "render_comment_form should use name label from options" do
256 | passthrough = render_comment_form(@test_comment, :name_label => 'test_label')
257 | assert_equal passthrough[:locals][:name_label], 'test_label'
258 | end
259 |
260 | test "render_comment_form should use email label from options" do
261 | passthrough = render_comment_form(@test_comment, :email_label => 'test_label')
262 | assert_equal passthrough[:locals][:email_label], 'test_label'
263 | end
264 |
265 | test "render_comment_form should use body label from options" do
266 | passthrough = render_comment_form(@test_comment, :body_label => 'test_label')
267 | assert_equal passthrough[:locals][:body_label], 'test_label'
268 | end
269 |
270 | test "render_comment_form should use submit label from options" do
271 | passthrough = render_comment_form(@test_comment, :submit_label => 'test_label')
272 | assert_equal passthrough[:locals][:submit_label], 'test_label'
273 | end
274 |
275 | private
276 |
277 | def test_option(name, option_name, pattern, namespace = :render_threaded_comments)
278 | assert defined?(THREADED_COMMENTS_CONFIG[namespace][option_name]), "The option name '#{namespace}:#{option_name}' was not set in the default config"
279 | if(THREADED_COMMENTS_CONFIG[namespace][option_name].is_a?(TrueClass) or THREADED_COMMENTS_CONFIG[namespace][option_name].is_a?(FalseClass))
280 | # Enabled in config - not set in options
281 | change_config_option(namespace, option_name, true) do
282 | @rendered_html = render_threaded_comments(@test_comments)
283 | @test_comments.each do |comment|
284 | @single_comment = render_threaded_comments([comment])
285 | assert @rendered_html.include?(pattern.gsub(":id", comment.id.to_s)), "render_threaded_comments did not output '#{pattern.gsub(":id", comment.id.to_s)}' with '#{namespace}:#{option_name}' enabled in config\n ---------- \n#{@single_comment}\n"
286 | end
287 | end
288 | # Disabled in config - not set in options
289 | change_config_option(namespace, option_name, false) do
290 | @rendered_html = render_threaded_comments(@test_comments)
291 | @test_comments.each do |comment|
292 | @single_comment = render_threaded_comments([comment])
293 | assert !@rendered_html.include?(pattern.gsub(":id", comment.id.to_s)), "render_threaded_comments should not output '#{pattern.gsub(":id", comment.id.to_s)}' when '#{namespace}:#{option_name}' disabled in config\n ---------- \n#{@single_comment}\n"
294 | end
295 | end
296 | # Enabled in options - disabled in config - options should override
297 | change_config_option(namespace, option_name, false) do
298 | @rendered_html = render_threaded_comments(@test_comments, option_name => true)
299 | @test_comments.each do |comment|
300 | @single_comment = render_threaded_comments([comment], option_name => true)
301 | assert @rendered_html.include?(pattern.gsub(":id", comment.id.to_s)), "render_threaded_comments did not output '#{pattern.gsub(":id", comment.id.to_s)}' when '#{namespace}:#{option_name}' enabled in options\n ---------- \n#{@single_comment}\n"
302 | end
303 | end
304 | # Disabled in options - enabled in config - options should override
305 | change_config_option(namespace, option_name, true) do
306 | @rendered_html = render_threaded_comments(@test_comments, option_name => false)
307 | @test_comments.each do |comment|
308 | @single_comment = render_threaded_comments([comment], option_name => false)
309 | assert !@rendered_html.include?(pattern.gsub(":id", comment.id.to_s)), "render_threaded_comments should not output '#{pattern.gsub(":id", comment.id.to_s)}' when '#{namespace}:#{option_name}' disabled in options\n ---------- \n#{@single_comment}\n"
310 | end
311 | end
312 | elsif(THREADED_COMMENTS_CONFIG[namespace][option_name].is_a?(String))
313 | # Default - should be set in config
314 | @rendered_html = render_threaded_comments(@test_comments)
315 | @single_comment = render_threaded_comments([@test_comments[0]])
316 | assert_equal @test_comments.length, @rendered_html.split(pattern).length - 1, "render_threaded_comments did not output '#{pattern}' for each comment by default\n ---------- \n#{@single_comment}\n"
317 | # Set in config - not set in options
318 | change_config_option(namespace, option_name, "replacement_pattern_config") do
319 | @rendered_html = render_threaded_comments(@test_comments)
320 | @single_comment = render_threaded_comments([@test_comments[0]])
321 | assert_equal @test_comments.length, @rendered_html.split("replacement_pattern_config").length - 1, "render_threaded_comments did not output value of '#{namespace}:#{option_name}' for each comment when set in config\n ---------- \n#{@single_comment}\n"
322 | assert_equal 1, @rendered_html.split(pattern).length, "render_threaded_comments still output default value of '#{namespace}:#{option_name}' even when overwritten in config"
323 | end
324 | # Set in options - also set in config - options should override
325 | change_config_option(namespace, option_name, "replacement_pattern_config") do
326 | @rendered_html = render_threaded_comments(@test_comments, option_name => "replacement_pattern_options")
327 | @single_comment = render_threaded_comments([@test_comments[0]], option_name => "replacement_pattern_options")
328 | assert_equal @test_comments.length, @rendered_html.split("replacement_pattern_options").length - 1, "render_threaded_comments did not output value of '#{namespace}:#{option_name}' for each comment when set in options\n ---------- \n#{@single_comment}\n"
329 | assert_equal 1, @rendered_html.split(pattern).length, "render_threaded_comments still output default value of '#{namespace}:#{option_name}' even when overwritten in config and options"
330 | assert_equal 1, @rendered_html.split("replacement_pattern_config").length, "render_threaded_comments still output config value of '#{namespace}:#{option_name}' even when overwritten in options"
331 | end
332 | else
333 | flunk "Unrecognized option type: #{THREADED_COMMENTS_CONFIG[namespace][option_name].class}"
334 | end
335 | end
336 |
337 | end
338 |
--------------------------------------------------------------------------------
24 | <%= label_tag "threaded_comment[#{honeypot_name}]", 'Do not provide this unless you are a robot!' %>
27 | 28 |25 | <%= text_field_tag "threaded_comment[#{honeypot_name}]" %> 26 |
29 | <%= f.label :name, name_label, :class => 'comment_name_label' -%> 30 | <%= f.text_field :name, :class => 'threaded_comment_name_input' %> 31 |
32 | 33 |34 | <%= f.label :email, email_label, :class => 'comment_email_label' -%> 35 | <%= f.text_field :email, :class => 'threaded_comment_email_input' %> 36 |
37 | 38 |39 | <%= f.label :body, body_label, :class => "comment_body_label" -%> 40 | <%= f.text_area :body, :class => 'threaded_comment_body_input' %> 41 |
42 | 43 |44 | <%= f.submit submit_title, :class => 'threaded_comment_submit_input', :id => "threaded_comment_submit_#{timestamp}" %> 45 |
46 | <% end %> 47 |