diff --git a/.gitignore b/.gitignore index 0f2c82fccf..b4cdbcae0e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ config/mail.yml *~ db/*.sqlite* db/schema.rb +db/db_test +db/db_development .*.swp .*.swo .DS_Store diff --git a/Gemfile b/Gemfile index b8121c7ee5..14fc3da2f1 100644 --- a/Gemfile +++ b/Gemfile @@ -57,4 +57,7 @@ group :development, :test do gem 'cucumber-rails-training-wheels' gem 'database_cleaner' gem 'capybara' + gem 'better_errors' + gem 'binding_of_caller' + gem 'pry' end diff --git a/Gemfile.lock b/Gemfile.lock index 64eb36cc1c..67c9103a1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,5 +1,5 @@ GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: RedCloth (4.2.9) abstract (1.0.0) @@ -34,6 +34,11 @@ GEM addressable (2.3.2) archive-tar-minitar (0.5.2) arel (2.0.10) + better_errors (0.0.8) + coderay + erubis + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) bluecloth (2.2.0) builder (2.1.2) capybara (1.1.2) @@ -60,6 +65,7 @@ GEM cucumber-rails (>= 1.1.1) daemons (1.1.9) database_cleaner (0.8.0) + debug_inspector (0.0.2) diff-lcs (1.1.3) erubis (2.6.6) abstract (>= 1.0.0) @@ -87,6 +93,8 @@ GEM i18n (>= 0.4.0) mime-types (~> 1.16) treetop (~> 1.4.8) + method_source (0.6.7) + ruby_parser (>= 2.3.1) mime-types (1.19) mini_magick (1.3.3) subexec (~> 0.0.4) @@ -94,6 +102,11 @@ GEM nokogiri (1.5.5) pg (0.14.1) polyglot (0.3.3) + pry (0.9.7.4) + coderay (~> 0.9.8) + method_source (~> 0.6.7) + ruby_parser (>= 2.3.1) + slop (~> 2.1.0) rack (1.2.5) rack-mount (0.6.14) rack (>= 1.0.0) @@ -141,6 +154,8 @@ GEM ruby-debug-base19 (>= 0.11.19) ruby_core_source (0.1.5) archive-tar-minitar (>= 0.5.2) + ruby_parser (3.8.1) + sexp_processor (~> 4.1) rubypants (0.2.0) rubyzip (0.9.9) selenium-webdriver (2.25.0) @@ -148,10 +163,12 @@ GEM libwebsocket (~> 0.1.3) multi_json (~> 1.0) rubyzip + sexp_processor (4.7.0) simplecov (0.6.4) multi_json (~> 1.0) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) + slop (2.1.0) sqlite3 (1.3.6) subexec (0.0.4) thin (1.5.0) @@ -179,6 +196,8 @@ DEPENDENCIES acts_as_list acts_as_tree_rails3 addressable (~> 2.1) + better_errors + binding_of_caller bluecloth (~> 2.1) capybara coderay (~> 0.9) @@ -193,6 +212,7 @@ DEPENDENCIES kaminari mini_magick (~> 1.3.3) pg + pry rails (~> 3.0.10) rake (~> 0.9.2) recaptcha @@ -205,3 +225,6 @@ DEPENDENCIES thin uuidtools (~> 2.1.1) webrat + +BUNDLED WITH + 1.11.2 diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb index b7026f8f29..6a6e2154eb 100644 --- a/app/controllers/admin/categories_controller.rb +++ b/app/controllers/admin/categories_controller.rb @@ -4,10 +4,10 @@ class Admin::CategoriesController < Admin::BaseController def index; redirect_to :action => 'new' ; end def edit; new_or_edit; end - def new + def new respond_to do |format| format.html { new_or_edit } - format.js { + format.js { @category = Category.new } end @@ -25,12 +25,16 @@ def destroy def new_or_edit @categories = Category.find(:all) - @category = Category.find(params[:id]) + if params[:id].nil? + @category = Category.new + else + @category = Category.find(params[:id]) + end @category.attributes = params[:category] if request.post? respond_to do |format| format.html { save_category } - format.js do + format.js do @category.save @article = Article.new @article.categories << @category @@ -43,7 +47,7 @@ def new_or_edit end def save_category - if @category.save! + if @category.save flash[:notice] = _('Category was successfully saved.') else flash[:error] = _('Category could not be saved.') diff --git a/app/controllers/admin/content_controller.rb b/app/controllers/admin/content_controller.rb index 6ef4ebf969..2870626ead 100644 --- a/app/controllers/admin/content_controller.rb +++ b/app/controllers/admin/content_controller.rb @@ -13,7 +13,7 @@ def auto_complete_for_article_keywords def index @search = params[:search] ? params[:search] : {} - + @articles = Article.search_with_pagination(@search, {:page => params[:page], :per_page => this_blog.admin_display_elements}) if request.xhr? @@ -44,7 +44,7 @@ def destroy flash[:error] = _("Error, you are not allowed to perform this action") return(redirect_to :action => 'index') end - + return(render 'admin/shared/destroy') unless request.post? @record.destroy @@ -52,6 +52,25 @@ def destroy redirect_to :action => 'index' end + def merge + if current_user.admin? + id = params[:id] + merge_id = params[:merge_with][:merge_id] + if id == merge_id + flash[:error] = _("You cannot merge an article with itself") + elsif Article.exists?(merge_id) && Article.exists?(id) + record = Article.find(id) + record.merge_with(merge_id) + flash[:notice] = _("Articles merged successfully") + else + flash[:error] = _("Article not found") + end + else + flash[:error] = _("Error, you are not allowed to perform this action") + end + redirect_to :action => 'index' + end + def insert_editor editor = 'visual' editor = 'simple' if params[:editor].to_s == 'simple' @@ -77,7 +96,7 @@ def attachment_box_add def attachment_save(attachment) begin - Resource.create(:filename => attachment.original_filename, :mime => attachment.content_type.chomp, + Resource.create(:filename => attachment.original_filename, :mime => attachment.content_type.chomp, :created_at => Time.now).write_to_disk(attachment) rescue => e logger.info(e.message) @@ -92,7 +111,7 @@ def autosave @article.text_filter = current_user.text_filter if current_user.simple_editor? get_fresh_or_existing_draft_for_article - + @article.attributes = params[:article] @article.published = false set_article_author @@ -144,7 +163,6 @@ def new_or_edit id = params[:article][:id] if params[:article] && params[:article][:id] @article = Article.get_or_build_article(id) @article.text_filter = current_user.text_filter if current_user.simple_editor? - @post_types = PostType.find(:all) if request.post? if params[:article][:draft] @@ -159,13 +177,13 @@ def new_or_edit @article.keywords = Tag.collection_to_string @article.tags @article.attributes = params[:article] # TODO: Consider refactoring, because double rescue looks... weird. - + @article.published_at = DateTime.strptime(params[:article][:published_at], "%B %e, %Y %I:%M %p GMT%z").utc rescue Time.parse(params[:article][:published_at]).utc rescue nil if request.post? set_article_author save_attachments - + @article.state = "draft" if @article.draft if @article.save diff --git a/app/models/article.rb b/app/models/article.rb index c80469b734..d7bd148cd0 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -104,10 +104,10 @@ def last_draft(article_id) end def search_with_pagination(search_hash, paginate_hash) - + state = (search_hash[:state] and ["no_draft", "drafts", "published", "withdrawn", "pending"].include? search_hash[:state]) ? search_hash[:state] : 'no_draft' - - + + list_function = ["Article.#{state}"] + function_search_no_draft(search_hash) if search_hash[:category] and search_hash[:category].to_i > 0 @@ -416,6 +416,17 @@ def access_by?(user) user.admin? || user_id == user.id end + def merge_with(other_article_id) + Article.find(other_article_id).comments.each do |comment| + comment.article_id = self.id + comment.save + end + merge_article = Article.find(other_article_id) + self.body += " " + merge_article.body + self.save + merge_article.destroy + end + protected def set_published_at diff --git a/app/views/admin/content/new.html.erb b/app/views/admin/content/new.html.erb index 0283264813..d90ea0a4a0 100644 --- a/app/views/admin/content/new.html.erb +++ b/app/views/admin/content/new.html.erb @@ -1,3 +1,15 @@ <% @page_heading = _('New article') %> <%= render "admin/shared/edit", { :form_type => "article", :form_action => { :action => "new", :id => @article.id , :class => ('autosave')} } %> + +<% if current_user.admin? && !@article.id.nil? %> +

Merge Articles

+ <%= form_tag :action=>"merge", :id => @article.id do %> + <%= error_messages_for 'article' %> + +
+ <%= text_field :merge_with, :merge_id %> +
+ <%= submit_tag("Merge") %> + <% end %> +<% end %> diff --git a/features/manage_categories.feature b/features/manage_categories.feature new file mode 100644 index 0000000000..68aca656ca --- /dev/null +++ b/features/manage_categories.feature @@ -0,0 +1,39 @@ +Feature: Manage Categories + As a blog administrator + In order to share my thoughts with the world + I want to be able to add categories to my blog + + Background: + Given the blog is set up + And I am logged into the admin panel + + Scenario: Successfully add categories + Given I am on the new category page + Then I should not see "fun" + When I fill in "category_name" with "fun" + And I fill in "category_keywords" with "friend, happy, song" + And I fill in "category_permalink" with "something" + And I fill in "category_description" with "cool beans" + And I press "Save" + Then I should be on the new category page + And I should see "fun" + And I should see "friend, happy, song" + And I should see "something" + And I should see "cool beans" + And I should have 2 categories + + Scenario: Successfully edit categories + Given I am on the edit category page + Then I should see "general" + When I fill in "category_name" with "fun" + And I fill in "category_keywords" with "friend, happy, song" + And I fill in "category_permalink" with "something" + And I fill in "category_description" with "cool beans" + And I press "Save" + Then I should be on the new category page + Then I should not see "general" + And I should see "fun" + And I should see "friend, happy, song" + And I should see "something" + And I should see "cool beans" + And I should have 1 categories diff --git a/features/merge_articles.feature b/features/merge_articles.feature new file mode 100644 index 0000000000..62a326b91d --- /dev/null +++ b/features/merge_articles.feature @@ -0,0 +1,29 @@ +Feature: Merge Articles + As a blog administrator + In order to share my thoughts with the world + I want to be able to merge similar articles on my blog + + Background: + Given the blog is set up + + Scenario: Non-admin cannot merge articles + Given I am logged in as a contributor + And I am on the edit articles page + Then I should not see "Merge Articles" + + Scenario: Admin can successfully merge articles + Given I am logged into the admin panel + And I have two similar articles titled Cats, Dogs + And I am on the manage articles page + Then I should see "Cats" + And I should see "Dogs" + When I follow "Edit" + Then I should be on the edit articles page + And I should see "Merge Articles" + When I fill in "merge_with_merge_id" with "4" + And I press "Merge" + Then I should be on the manage articles page + And I should not see "Dogs" + And I should see "Cats" + When I follow "Show" + Then I should see "I love cats I love dogs" diff --git a/features/step_definitions/web_steps.rb b/features/step_definitions/web_steps.rb index 6315105872..e18b955768 100644 --- a/features/step_definitions/web_steps.rb +++ b/features/step_definitions/web_steps.rb @@ -41,6 +41,12 @@ def with_scope(locator) :profile_id => 1, :name => 'admin', :state => 'active'}) + User.create!({:login => 'contributor', + :password => 'password', + :email => 'hello@test.com', + :profile_id => 2, + :name => 'contributor', + :state => 'active'}) end And /^I am logged into the admin panel$/ do @@ -55,6 +61,18 @@ def with_scope(locator) end end +Given /^I am logged in as a contributor$/ do + visit '/accounts/login' + fill_in 'user_login', :with => 'contributor' + fill_in 'user_password', :with => 'password' + click_button 'Login' + if page.respond_to? :should + page.should have_content('Login successful') + else + assert page.has_content?('Login successful') + end +end + # Single-line step scoper When /^(.*) within (.*[^:])$/ do |step, parent| with_scope(parent) { When step } @@ -250,7 +268,7 @@ def with_scope(locator) end end end - + Then /^(?:|I )should be on (.+)$/ do |page_name| current_path = URI.parse(current_url).path if current_path.respond_to? :should @@ -264,8 +282,8 @@ def with_scope(locator) query = URI.parse(current_url).query actual_params = query ? CGI.parse(query) : {} expected_params = {} - expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')} - + expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')} + if actual_params.respond_to? :should actual_params.should == expected_params else @@ -276,3 +294,20 @@ def with_scope(locator) Then /^show me the page$/ do save_and_open_page end + +Then /^I should have (\d+) categories$/ do |count| + Category.count.should == count.to_i +end + +Given /^I have two similar articles titled (.+)$/ do |titles| + Article.delete_all + titles.split(', ').each do |title| + article = Article.new + article.published = 1 + article.user_id = 2 + article.published_at = "March 29, 2016 07:07 PM GMT+0000 (UTC)" + article.title = title + article.body_and_extended= "I love " + title.downcase + article.save + end +end diff --git a/features/support/paths.rb b/features/support/paths.rb index e7e00e5d89..2309f75213 100644 --- a/features/support/paths.rb +++ b/features/support/paths.rb @@ -17,6 +17,15 @@ def path_to(page_name) '/' when /^the new article page$/ '/admin/content/new' + when /^the new category page$/ + '/admin/categories/new' + when /^the edit category page$/ + '/admin/categories/edit/1' + when /^the manage articles page$/ + '/admin/content' + when /^the edit articles page$/ + '/admin/content/edit/3' + # Add more mappings here. # Here is an example that pulls values out of the Regexp: diff --git a/public/files/02219_whereismyprey_1024x768.jpg b/public/files/02219_whereismyprey_1024x768.jpg new file mode 100644 index 0000000000..6f21cfb4ea Binary files /dev/null and b/public/files/02219_whereismyprey_1024x768.jpg differ diff --git a/public/javascripts/ckeditor/config.bak b/public/javascripts/ckeditor/config.bak old mode 100644 new mode 100755 index 187db086f2..492800fd66 --- a/public/javascripts/ckeditor/config.bak +++ b/public/javascripts/ckeditor/config.bak @@ -8,7 +8,7 @@ CKEDITOR.editorConfig = function( config ) config.PreserveSessionOnFileBrowser = true; // Define changes to default configuration here. For example: //config.language = ''; - config.uiColor = '#E0ECFF'; + config.uiColor = '#eee'; config.toolbar = 'Basic'; config.entities_greek = false; config.entities_latin = false; diff --git a/public/javascripts/ckeditor/config.js b/public/javascripts/ckeditor/config.js old mode 100644 new mode 100755 index 187db086f2..492800fd66 --- a/public/javascripts/ckeditor/config.js +++ b/public/javascripts/ckeditor/config.js @@ -8,7 +8,7 @@ CKEDITOR.editorConfig = function( config ) config.PreserveSessionOnFileBrowser = true; // Define changes to default configuration here. For example: //config.language = ''; - config.uiColor = '#E0ECFF'; + config.uiColor = '#eee'; config.toolbar = 'Basic'; config.entities_greek = false; config.entities_latin = false; diff --git a/spec/controllers/admin/categories_controller_spec.rb b/spec/controllers/admin/categories_controller_spec.rb index bb290f4a0d..8e7639d6e2 100644 --- a/spec/controllers/admin/categories_controller_spec.rb +++ b/spec/controllers/admin/categories_controller_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'pry' describe Admin::CategoriesController do render_views @@ -16,6 +17,47 @@ assert_response :redirect, :action => 'index' end + describe "test_new" do + before(:each) do + get :new, :format => 'html' + end + + it 'should render template new' do + assert_template 'new' + assert_tag :tag => "table", + :attributes => { :id => "category_container" } + end + + it 'should have a new category' do + assigns(:category).should_not be_nil + assert assigns(:category).id.should be_nil + assigns(:categories).should_not be_nil + end + end + + describe "test_create" do + let(:good_category_params) do + {:name=>"hey", :keywords=>"what", :permalink=>"something", :description=>"so much fun"} + end + let(:bad_category_params) do + {:name=>"", :keywords=>"", :permalink=>"", :description=>""} + end + it 'should create a new category' do + original_count = Category.all.count + post :edit, :format => 'html', :category => good_category_params + assert_response :redirect, :action => 'index' + expect(Category.all.count).to eq(original_count + 1) + expect(flash[:notice]).to eq("Category was successfully saved.") + end + it 'should not create a new category with bad input' do + original_count = Category.all.count + post :edit, :format => 'html', :category => bad_category_params + assert_response :redirect, :action => 'index' + expect(Category.all.count).to eq(original_count) + expect(flash[:error]).to eq('Category could not be saved.') + end + end + describe "test_edit" do before(:each) do get :edit, :id => Factory(:category).id @@ -48,7 +90,7 @@ it 'should render destroy template' do assert_response :success - assert_template 'destroy' + assert_template 'destroy' end end @@ -62,5 +104,5 @@ assert_raise(ActiveRecord::RecordNotFound) { Category.find(test_id) } end - + end diff --git a/spec/controllers/admin/content_controller_spec.rb b/spec/controllers/admin/content_controller_spec.rb index 0bee946588..552ca42293 100644 --- a/spec/controllers/admin/content_controller_spec.rb +++ b/spec/controllers/admin/content_controller_spec.rb @@ -48,7 +48,7 @@ response.should render_template('index') response.should be_success end - + it 'should restrict to withdrawn articles' do article = Factory(:article, :state => 'withdrawn', :published_at => '2010-01-01') get :index, :search => {:state => 'withdrawn'} @@ -56,7 +56,7 @@ response.should render_template('index') response.should be_success end - + it 'should restrict to withdrawn articles' do article = Factory(:article, :state => 'withdrawn', :published_at => '2010-01-01') get :index, :search => {:state => 'withdrawn'} @@ -514,6 +514,42 @@ def base_article(options={}) end end + describe "merge articles", :focus => true do + it 'should merge articles' do + article2 = Factory(:article) + lambda do + get :merge, :id => @article.id, :merge_with =>{:merge_id => article2.id } + response.should redirect_to(:action => 'index') + expect(response.status).to eq(302) + expect(flash[:notice]).to eq("Articles merged successfully") + end.should change(Article, :count) + end + it 'raise error if merging article with itself' do + lambda do + get :merge, :id => @article.id, :merge_with =>{:merge_id => @article.id } + response.should redirect_to(:action => 'index') + expect(response.status).to eq(302) + expect(flash[:error]).to eq("You cannot merge an article with itself") + end.should_not change(Article, :count) + end + it 'raise error if merge article does not exist' do + lambda do + get :merge, :id => @article.id, :merge_with =>{:merge_id => 111 } + response.should redirect_to(:action => 'index') + expect(response.status).to eq(302) + expect(flash[:error]).to eq("Article not found") + end.should_not change(Article, :count) + end + it 'raise error if original article does not exist' do + lambda do + get :merge, :id => 111, :merge_with =>{:merge_id => @article.id } + response.should redirect_to(:action => 'index') + expect(response.status).to eq(302) + expect(flash[:error]).to eq("Article not found") + end.should_not change(Article, :count) + end + end + it 'should allow updating body_and_extended' do article = @article post :edit, 'id' => article.id, 'article' => { @@ -670,5 +706,19 @@ def base_article(options={}) end end + + describe "merge articles" do + it 'should not allow contributor to merge articles' do + article2 = Factory(:article, :user => @user) + lambda do + get :merge, :id => @article.id, :merge_with =>{:merge_id => article2.id } + response.should redirect_to(:action => 'index') + expect(Article.find(@article.id).attributes).to eq @article.attributes + expect(flash[:error]).to eq("Error, you are not allowed to perform this action") + end.should_not change(Article, :count) + end + end + + end end diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb index 0643729876..8820c419ef 100644 --- a/spec/models/article_spec.rb +++ b/spec/models/article_spec.rb @@ -1,5 +1,6 @@ # coding: utf-8 require 'spec_helper' +require 'pry' describe Article do @@ -184,25 +185,25 @@ def assert_results_are(*expected) ### XXX: Should we have a test here? it "test_send_multiple_pings" do end - + describe "Testing redirects" do it "a new published article gets a redirect" do a = Article.create(:title => "Some title", :body => "some text", :published => true) a.redirects.first.should_not be_nil a.redirects.first.to_path.should == a.permalink_url end - - it "a new unpublished article should not get a redirect" do + + it "a new unpublished article should not get a redirect" do a = Article.create(:title => "Some title", :body => "some text", :published => false) a.redirects.first.should be_nil end - + it "Changin a published article permalink url should only change the to redirection" do a = Article.create(:title => "Some title", :body => "some text", :published => true) a.redirects.first.should_not be_nil a.redirects.first.to_path.should == a.permalink_url r = a.redirects.first.from_path - + a.permalink = "some-new-permalink" a.save a.redirects.first.should_not be_nil @@ -571,7 +572,7 @@ def assert_sets_trigger(art) describe "#find_by_permalink" do it "uses UTC to determine correct day" do @a.save - a = Article.find_by_permalink :year => 2011, :month => 2, :day => 21, :permalink => 'a-big-article' + a = Article.find_by_permalink :year => 2011, :month => 2, :day => 21, :permalink => 'a-big-article' a.should == @a end end @@ -592,7 +593,7 @@ def assert_sets_trigger(art) describe "#find_by_permalink" do it "uses UTC to determine correct day" do @a.save - a = Article.find_by_permalink :year => 2011, :month => 2, :day => 22, :permalink => 'a-big-article' + a = Article.find_by_permalink :year => 2011, :month => 2, :day => 22, :permalink => 'a-big-article' a.should == @a end end @@ -629,6 +630,24 @@ def assert_sets_trigger(art) end end + describe "merge_with" do + it "should merge two articles" do + article1 = Factory.create(:article, :title => "First Article") + user1 = article1.user + Factory.create(:comment, :article => article1) + Factory.create(:comment, :article => article1) + + article2 = Factory.create(:article, :body => "second article") + Factory.create(:comment, :article => article2) + + article1.merge_with(article2.id) + + expect(article1.body).to eq "A content with several data second article" + expect(article1.title).to eq "First Article" + expect(article1.user).to eq user1 + expect(article1.comments.count).to eq(3) + end + end + end end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 29c9e9ace4..892446569e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -36,6 +36,8 @@ def find_hooks_for(group) config.use_transactional_fixtures = true config.use_instantiated_fixtures = false config.fixture_path = "#{::Rails.root}/test/fixtures" + config.filter_run focus: true + config.run_all_when_everything_filtered = true config.before(:each) do Localization.lang = :default @@ -214,4 +216,3 @@ def self.document(stringlike) #:nodoc: end end end -