neutral 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +194 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +143 -0
  7. data/Rakefile +21 -0
  8. data/app/assets/stylesheets/neutral/index.css +43 -0
  9. data/app/controllers/neutral/application_controller.rb +22 -0
  10. data/app/controllers/neutral/votes_controller.rb +48 -0
  11. data/app/helpers/neutral/votes_helper.rb +16 -0
  12. data/app/models/neutral/vote.rb +26 -0
  13. data/app/models/neutral/voting.rb +39 -0
  14. data/app/views/neutral/votes/create.js.erb +27 -0
  15. data/app/views/neutral/votes/destroy.js.erb +18 -0
  16. data/app/views/neutral/votes/errors/cannot_change.js.erb +9 -0
  17. data/app/views/neutral/votes/errors/duplicate.js.erb +9 -0
  18. data/app/views/neutral/votes/errors/require_login.js.erb +10 -0
  19. data/app/views/neutral/votes/update.js.erb +11 -0
  20. data/bin/rails +8 -0
  21. data/config/locales/impartial.yml +6 -0
  22. data/config/routes.rb +3 -0
  23. data/lib/generators/neutral/formats.rb +16 -0
  24. data/lib/generators/neutral/install/install_generator.rb +47 -0
  25. data/lib/generators/neutral/install/templates/initializer.rb +41 -0
  26. data/lib/generators/neutral/install/templates/locale.yml +5 -0
  27. data/lib/generators/neutral/install/templates/votes.rb +12 -0
  28. data/lib/generators/neutral/install/templates/votings.rb +12 -0
  29. data/lib/generators/neutral/uninstall/templates/drop_neutral_votes_table.rb +9 -0
  30. data/lib/generators/neutral/uninstall/templates/drop_neutral_votings_table.rb +9 -0
  31. data/lib/generators/neutral/uninstall/uninstall_generator.rb +55 -0
  32. data/lib/neutral/configuration.rb +27 -0
  33. data/lib/neutral/engine.rb +28 -0
  34. data/lib/neutral/errors.rb +11 -0
  35. data/lib/neutral/helpers/action_view_extension.rb +19 -0
  36. data/lib/neutral/helpers/routes.rb +9 -0
  37. data/lib/neutral/icons/collection.rb +55 -0
  38. data/lib/neutral/icons/set.rb +22 -0
  39. data/lib/neutral/model/active_record_extension.rb +31 -0
  40. data/lib/neutral/model/vote_cached.rb +32 -0
  41. data/lib/neutral/version.rb +3 -0
  42. data/lib/neutral/voting_builder/builder.rb +42 -0
  43. data/lib/neutral/voting_builder/elements/link.rb +46 -0
  44. data/lib/neutral/voting_builder/elements/span.rb +41 -0
  45. data/lib/neutral/voting_builder/elements.rb +23 -0
  46. data/lib/neutral/voting_builder/router.rb +45 -0
  47. data/lib/neutral/voting_builder/structure.rb +21 -0
  48. data/lib/neutral.rb +46 -0
  49. data/neutral.gemspec +34 -0
  50. data/spec/controllers/votes_controller_spec.rb +138 -0
  51. data/spec/dummy/Rakefile +6 -0
  52. data/spec/dummy/app/assets/images/.keep +0 -0
  53. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  54. data/spec/dummy/app/assets/stylesheets/application.css +29 -0
  55. data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
  56. data/spec/dummy/app/controllers/application_controller.rb +11 -0
  57. data/spec/dummy/app/controllers/posts_controller.rb +33 -0
  58. data/spec/dummy/app/controllers/sessions_controller.rb +21 -0
  59. data/spec/dummy/app/controllers/users_controller.rb +24 -0
  60. data/spec/dummy/app/models/.keep +0 -0
  61. data/spec/dummy/app/models/post.rb +3 -0
  62. data/spec/dummy/app/models/user.rb +7 -0
  63. data/spec/dummy/app/views/layouts/application.html.erb +25 -0
  64. data/spec/dummy/app/views/posts/_form.html.erb +21 -0
  65. data/spec/dummy/app/views/posts/index.html.erb +26 -0
  66. data/spec/dummy/app/views/posts/new.html.erb +5 -0
  67. data/spec/dummy/app/views/sessions/new.html.erb +18 -0
  68. data/spec/dummy/app/views/users/_form.html.erb +25 -0
  69. data/spec/dummy/app/views/users/new.html.erb +3 -0
  70. data/spec/dummy/bin/bundle +3 -0
  71. data/spec/dummy/bin/rails +4 -0
  72. data/spec/dummy/bin/rake +4 -0
  73. data/spec/dummy/config/application.rb +29 -0
  74. data/spec/dummy/config/boot.rb +5 -0
  75. data/spec/dummy/config/database.yml +25 -0
  76. data/spec/dummy/config/environment.rb +5 -0
  77. data/spec/dummy/config/environments/development.rb +29 -0
  78. data/spec/dummy/config/environments/production.rb +80 -0
  79. data/spec/dummy/config/environments/test.rb +36 -0
  80. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  81. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  82. data/spec/dummy/config/initializers/neutral.rb +41 -0
  83. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  84. data/spec/dummy/config/initializers/session_store.rb +3 -0
  85. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  86. data/spec/dummy/config/locales/en.yml +23 -0
  87. data/spec/dummy/config/locales/neutral.yml +5 -0
  88. data/spec/dummy/config/routes.rb +13 -0
  89. data/spec/dummy/config.ru +4 -0
  90. data/spec/dummy/db/migrate/20140110214145_create_posts.rb +9 -0
  91. data/spec/dummy/db/migrate/20140111225442_create_users.rb +10 -0
  92. data/spec/dummy/db/migrate/20140118172808881589_create_neutral_votes.rb +12 -0
  93. data/spec/dummy/db/migrate/20140118172808882161_create_neutral_votings.rb +12 -0
  94. data/spec/dummy/db/schema.rb +47 -0
  95. data/spec/dummy/lib/assets/.keep +0 -0
  96. data/spec/dummy/log/.keep +0 -0
  97. data/spec/dummy/public/404.html +58 -0
  98. data/spec/dummy/public/422.html +58 -0
  99. data/spec/dummy/public/500.html +57 -0
  100. data/spec/dummy/public/favicon.ico +0 -0
  101. data/spec/factories.rb +18 -0
  102. data/spec/helpers/action_view_extension_spec.rb +45 -0
  103. data/spec/icons/collection_spec.rb +38 -0
  104. data/spec/icons/set_spec.rb +25 -0
  105. data/spec/models/active_record_extension_spec.rb +49 -0
  106. data/spec/models/vote_spec.rb +51 -0
  107. data/spec/models/voting_spec.rb +60 -0
  108. data/spec/spec_helper.rb +47 -0
  109. data/spec/support/shared_examples/controller_examples.rb +45 -0
  110. data/spec/support/votes_controller_base_class.rb +28 -0
  111. data/spec/support/votes_controller_helpers.rb +24 -0
  112. data/spec/voting_builder/builder_spec.rb +66 -0
  113. data/spec/voting_builder/elements/link_spec.rb +31 -0
  114. data/spec/voting_builder/elements/span_spec.rb +44 -0
  115. data/spec/voting_builder/router_spec.rb +57 -0
  116. data/spec/voting_builder/structure_spec.rb +59 -0
  117. metadata +449 -0
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe "#voting_for", type: :feature do
4
+ class View < ActionView::Base.extend(Neutral::Helpers::ActionViewExtension)
5
+ def current_voter;end
6
+ end
7
+
8
+ def view
9
+ @view ||= View.new
10
+ end
11
+
12
+ let(:voteable) { FactoryGirl.create(:post) }
13
+
14
+ context "when a valid object" do
15
+ it "builds a voting" do
16
+ Neutral::VotingBuilder::Builder.should_receive(:new).with(voteable, hash_including(:voter)).and_call_original
17
+ Neutral::VotingBuilder::Builder.any_instance.should_receive(:build)
18
+ view.voting_for voteable
19
+ end
20
+ end
21
+
22
+ context "when an invalid object" do
23
+ it "raises InvalidVoteableObject error" do
24
+ expect { voting_for "not a voteable object" }.to raise_error
25
+ end
26
+ end
27
+
28
+ context "with difference" do
29
+ it "builds a voting with difference" do
30
+ Neutral::VotingBuilder::Builder.should_receive(:new).with(voteable, hash_including(:voter, difference: true)).and_call_original
31
+ Neutral::VotingBuilder::Builder.any_instance.should_receive(:build)
32
+ view.voting_for voteable, difference: true
33
+ end
34
+ end
35
+
36
+ context "with specific icon set" do
37
+ let(:icons) { :operations }
38
+
39
+ it "builds a voting with specific icon set" do
40
+ Neutral::VotingBuilder::Builder.should_receive(:new).with(voteable, hash_including(:voter, icons: icons)).and_call_original
41
+ Neutral::VotingBuilder::Builder.any_instance.should_receive(:build)
42
+ view.voting_for voteable, icons: icons
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neutral::Icons::Collection do
4
+ let(:collection) { Neutral::Icons::Collection.new }
5
+ let(:set) { Neutral::Icons::Set.new(:my_test_icons) }
6
+
7
+ describe "#add" do
8
+ context "when given set already exists" do
9
+ let(:set) { Neutral::Icons::Set.new(:thumbs) }
10
+
11
+ it "raises AlreadyDefinedIconSet error" do
12
+ expect { collection.add(set.name, set.definitions) }.to raise_error
13
+ end
14
+ end
15
+
16
+ context "when given set does not exist yet" do
17
+ it "defines method on icons object with given set name and definitions" do
18
+ collection.should_receive(:define!).with(set.name, set.definitions)
19
+ collection.add(set)
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "#define!" do
25
+ before do
26
+ collection.send(:define!, set.name, set.definitions)
27
+ end
28
+
29
+ subject { collection.send(set.name) }
30
+
31
+ it { should be_a(Neutral::Icons::Collection::Definitions) }
32
+
33
+ [:positive, :negative, :remove].each do |definition|
34
+ its(definition) { should == set.definitions[definition] }
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,25 @@
1
+ describe Neutral::Icons::Set do
2
+ let(:set) { Neutral::Icons::Set.new('my_test_icons') }
3
+
4
+ describe "#name" do
5
+ subject { set.name }
6
+ it { should be_a(Symbol) }
7
+ end
8
+
9
+ describe "#definitions" do
10
+ subject { set.definitions }
11
+ it { should be_a(Hash) }
12
+ it { should == Neutral.icons.send(Neutral.config.default_icon_set).to_h }
13
+ end
14
+
15
+ %w[positive negative remove].each do |definition|
16
+ describe definition do
17
+ let(:fa_definition) { "fa #{definition}" }
18
+
19
+ it "adds Font Awesome definition for #{definition} icon to definitions" do
20
+ set.send(definition, fa_definition)
21
+ set.definitions[definition.to_sym].should == fa_definition
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ extended = ActiveRecord::Base.extend(Neutral::Model::ActiveRecordExtension)
4
+
5
+ class Item < extended
6
+ neutral
7
+ end
8
+
9
+ class Client < extended
10
+ neutral_voter
11
+ end
12
+
13
+ describe Item, type: :model do
14
+ it { should be_a(Neutral::Model::ActiveRecordExtension::Voteable) }
15
+
16
+ before do
17
+ @migration = ActiveRecord::Migration.new
18
+ @migration.verbose = false
19
+ @migration.create_table(:items)
20
+ end
21
+
22
+ after do
23
+ @migration.drop_table(:items)
24
+ end
25
+
26
+ describe "#neutral" do
27
+ it { should have_many(:votes) }
28
+ it { should have_many(:voters) }
29
+ it { should have_one(:voting) }
30
+ end
31
+ end
32
+
33
+ describe Client, type: :model do
34
+ it { should be_a(Neutral::Model::ActiveRecordExtension::Voter) }
35
+
36
+ before do
37
+ @migration = ActiveRecord::Migration.new
38
+ @migration.verbose = false
39
+ @migration.create_table(:clients)
40
+ end
41
+
42
+ after do
43
+ @migration.drop_table(:clients)
44
+ end
45
+
46
+ describe "#neutral_voter" do
47
+ it { should have_many(:votes_given) }
48
+ end
49
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neutral::Vote do
4
+ it { should belong_to :voteable }
5
+ it { should belong_to :voter }
6
+ it { validate_presence_of :voteable_type }
7
+ it { validate_presence_of :voteable_id }
8
+ it { validate_presence_of :value }
9
+ it { should ensure_inclusion_of(:value).in_range(0..1) }
10
+
11
+ let!(:vote) { FactoryGirl.create(:vote) }
12
+
13
+ describe "before_update" do
14
+ it "avoids duplication" do
15
+ expect { vote.update_attributes(value: vote.value) }.to raise_error
16
+ end
17
+ end
18
+
19
+ describe "after_create" do
20
+ context "when vote's voting does not exist yet" do
21
+ it "adds vote to voting" do
22
+ Neutral::Voting.should_receive(:init).with(FactoryGirl.create(:vote))
23
+ end
24
+ end
25
+
26
+ context "when vote's voting already exists" do
27
+ let(:another_vote) { FactoryGirl.create(:vote, voteable: vote.voteable) }
28
+
29
+ it "adds vote to an existing voting" do
30
+ vote.voting.should_receive(:add_to_existing).with(another_vote.nature)
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "after_update" do
36
+ let(:opposite) { vote.value == 1 ? :negative : :positive }
37
+
38
+ it "edits vote within voting" do
39
+ vote.voting.should_receive(:edit).with(opposite)
40
+ vote.update_attributes(value: vote.value.eql?(1) ? 0 : 1)
41
+ end
42
+ end
43
+
44
+ describe "after_destroy" do
45
+ it "removes vote from voting" do
46
+ vote.voting.should_receive(:remove).with(vote.nature)
47
+ vote.destroy
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neutral::Voting do
4
+ it { should belong_to(:votingable) }
5
+
6
+ let(:voting) { Neutral::Voting.create }
7
+ let(:nature) { [:positive, :negative].sample }
8
+
9
+ describe "#difference" do
10
+ let(:positive) { rand(100) }
11
+ let(:negative) { rand(100) }
12
+
13
+ subject { Neutral::Voting.new(positive: positive, negative: negative).difference }
14
+ it { should == positive - negative }
15
+ end
16
+
17
+ describe "#self.init" do
18
+ let(:vote) { FactoryGirl.build(:vote) }
19
+
20
+ subject { Neutral::Voting.init(vote) }
21
+
22
+ it { should be_a(Neutral::Voting) }
23
+ it { should be_persisted }
24
+ its(:votingable_type) { should == vote.voteable_type }
25
+ its(:votingable_id) { should == vote.voteable_id }
26
+ it { subject.send(vote.nature).should == 1 }
27
+ end
28
+
29
+ describe "#add_to_existing" do
30
+ it "adds vote to an existing voting" do
31
+ voting.should_receive(:increment!).with(nature)
32
+ voting.add_to_existing(nature)
33
+ end
34
+ end
35
+
36
+ describe "#edit" do
37
+ it "edits vote withing voting" do
38
+ voting.should_receive(:increment).with(nature)
39
+ voting.should_receive(:decrement).with(nature==:positive ? :negative : :positive)
40
+ voting.should_receive(:save)
41
+ voting.edit(nature)
42
+ end
43
+ end
44
+
45
+ describe "#remove" do
46
+ it "removes vote from voting" do
47
+ voting.should_receive(:decrement!).with(nature)
48
+ voting.remove(nature)
49
+ end
50
+ end
51
+
52
+ describe "before_destroy" do
53
+ let(:voting) { FactoryGirl.create(:vote).voting }
54
+
55
+ it "deletes voting's votes" do
56
+ voting.votes.should_receive(:delete_all)
57
+ voting.destroy
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ ENV['RAILS_ENV'] ||= 'test'
5
+
6
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
7
+ require 'rspec/rails'
8
+ require 'ffaker'
9
+ require 'factory_girl_rails'
10
+ require 'capybara/rspec'
11
+ require 'remarkable_activerecord'
12
+ require 'shoulda-matchers'
13
+ require 'database_cleaner'
14
+ require 'debugger'
15
+ require 'pry-rails'
16
+
17
+ Rails.backtrace_cleaner.remove_silencers!
18
+ ENGINE_RAILS_ROOT=File.join(File.dirname(__FILE__), '../')
19
+
20
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
21
+
22
+ RSpec.configure do |config|
23
+ config.color_enabled = true
24
+ config.mock_with :rspec
25
+ config.use_transactional_fixtures = false
26
+ config.infer_base_class_for_anonymous_controllers = true
27
+ config.before(:suite) do
28
+ DatabaseCleaner.strategy = :transaction
29
+ DatabaseCleaner.clean_with(:truncation)
30
+ end
31
+
32
+ config.before(:each) do
33
+ DatabaseCleaner.strategy = :transaction
34
+ end
35
+
36
+ config.before(:each, :js => true) do
37
+ DatabaseCleaner.strategy = :truncation
38
+ end
39
+
40
+ config.before(:each) do
41
+ DatabaseCleaner.start
42
+ end
43
+
44
+ config.after(:each) do
45
+ DatabaseCleaner.clean
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ shared_examples "unable to change" do |action|
2
+ context "when cannot change" do
3
+ context "when not permitted to change" do
4
+ before do
5
+ Neutral.configure { |config| config.can_change = false }
6
+ controller.stub(:current_user).and_return(vote.voter)
7
+ end
8
+
9
+ after do
10
+ Neutral.configure { |config| config.can_change = true }
11
+ end
12
+
13
+ it_should_behave_like "not performing #{action}"
14
+ end
15
+
16
+ context "when current_user is not an owner of the vote" do
17
+ before do
18
+ controller.stub(:current_user).and_return(voter)
19
+ end
20
+
21
+ it_should_behave_like "not performing #{action}"
22
+ end
23
+ end
24
+ end
25
+
26
+ shared_examples "not performing update" do
27
+ it "does not update the vote" do
28
+ update(vote)
29
+ vote.value.should == vote.reload.value
30
+ end
31
+
32
+ it "renders cannot_change template" do
33
+ expect(update(vote)).to render_template('errors/cannot_change')
34
+ end
35
+ end
36
+
37
+ shared_examples "not performing destroy" do
38
+ it "does not destroy the vote" do
39
+ expect { destroy(vote) }.to_not change(Neutral::Vote, :count).by(-1)
40
+ end
41
+
42
+ it "renders cannot_change template" do
43
+ expect(destroy(vote)).to render_template('errors/cannot_change')
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ module VotesControllerBaseClass
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ controller do
6
+ rescue_from Neutral::Errors::RequireLogin, with: :require_login
7
+ rescue_from Neutral::Errors::CannotChange, with: :cannot_change
8
+ rescue_from Neutral::Errors::DuplicateVote, with: :duplicate
9
+
10
+ def current_voter
11
+ send Neutral.config.current_voter_method
12
+ end
13
+
14
+ private
15
+ def require_login
16
+ render 'neutral/votes/errors/require_login', status: 401
17
+ end
18
+
19
+ def cannot_change
20
+ render 'neutral/votes/errors/cannot_change', status: 403
21
+ end
22
+
23
+ def duplicate
24
+ render 'neutral/votes/errors/duplicate', status: 409
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module VotesControllerHelpers
2
+ def create(voteable)
3
+ xhr :post, :create,
4
+ vote: {
5
+ voteable_type: voteable.class.name,
6
+ voteable_id: voteable.id,
7
+ value: rand(0..1)
8
+ }
9
+ end
10
+
11
+ def update(vote, duplicate=false)
12
+ xhr :patch, :update, id: vote.id, value: value(vote.value, duplicate)
13
+ end
14
+
15
+ def destroy(vote)
16
+ xhr :delete, :destroy, id: vote.id
17
+ end
18
+
19
+ private
20
+ def value(val, duplicate)
21
+ return val if duplicate
22
+ val == 1 ? 0 : 1
23
+ end
24
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neutral::VotingBuilder::Builder, type: :feature do
4
+ describe "#build" do
5
+ let(:voteable) { FactoryGirl.create(:post) }
6
+ let(:vote) { FactoryGirl.create(:vote) }
7
+
8
+ context "basic built" do
9
+ subject { Neutral::VotingBuilder::Builder.new(voteable, {}).build }
10
+
11
+ it { should have_selector "div.neutral" }
12
+ it { should have_selector "span#positive" }
13
+ it { should have_selector "a.positive" }
14
+ it { should have_selector "a.negative" }
15
+ it { should have_selector "span#negative" }
16
+ end
17
+
18
+ context "with difference" do
19
+ subject { Neutral::VotingBuilder::Builder.new(voteable, difference: true).build }
20
+
21
+ it { should have_selector "span#difference" }
22
+ it { should_not have_selector "span#positive" }
23
+ it { should_not have_selector "span#negative" }
24
+ end
25
+
26
+ describe "remove link" do
27
+ context "when present" do
28
+ before do
29
+ Neutral.configure { |config| config.can_change = true }
30
+ end
31
+
32
+ subject { Neutral::VotingBuilder::Builder.new(vote.voteable, voter: vote.voter).build }
33
+
34
+ it { should have_selector "a.remove" }
35
+ end
36
+
37
+ context "when not present" do
38
+ context "when current voter has not voted yet or is not an owner of the vote" do
39
+ let(:voteable) { vote.voteable }
40
+ let(:voter) { vote.voter }
41
+
42
+ before do
43
+ vote.destroy
44
+ end
45
+
46
+ subject { Neutral::VotingBuilder::Builder.new(voteable, voter: voter).build }
47
+ it { should_not have_selector "a.remove" }
48
+ end
49
+
50
+ context "when current voter has voted but cannot change the vote" do
51
+ before do
52
+ Neutral.configure { |config| config.can_change = false }
53
+ end
54
+
55
+ after do
56
+ Neutral.configure { |config| config.can_change = true }
57
+ end
58
+
59
+ subject { Neutral::VotingBuilder::Builder.new(vote.voteable, voter: vote.voter).build }
60
+
61
+ it { should_not have_selector("a.remove") }
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ include Neutral::VotingBuilder::Elements
4
+
5
+ describe Link, type: :feature do
6
+ let(:vote) { FactoryGirl.create(:vote) }
7
+
8
+ [Link::Positive, Link::Negative, Link::Remove].each do |link|
9
+ describe link do
10
+ let(:klass) { link.to_s.demodulize.downcase }
11
+ let(:router) { Neutral::VotingBuilder::Router.new(vote).send(klass) }
12
+ let(:icon) { "fa-#{klass}" }
13
+
14
+ subject { Capybara.string link.new(router, icon).to_s }
15
+
16
+ it { should have_selector("a.#{klass}") }
17
+
18
+ it "has corresponding path" do
19
+ subject.find("a.#{klass}")[:href].should include(router[:path])
20
+ end
21
+
22
+ it "has corresponding HTTP method" do
23
+ subject.find("a.#{klass}")["data-method"].should == router[:method]
24
+ end
25
+
26
+ it "has valid FontAwesome icon class" do
27
+ subject.find("i")[:class].should == "fa #{icon}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ include Neutral::VotingBuilder::Elements
4
+
5
+ describe Span, type: :feature do
6
+ [Span::Positive, Span::Negative].each do |span|
7
+ describe span do
8
+ describe "#to_s" do
9
+ let(:total) { rand(100) + 1 }
10
+
11
+ subject { Capybara.string span.new(total).to_s }
12
+
13
+ it { should have_selector("span##{span.to_s.demodulize.downcase}") }
14
+
15
+ it "has value of given total" do
16
+ subject.find("span").text.should == total.to_s
17
+ end
18
+
19
+ it "nullifies total of zero" do
20
+ Capybara.string(span.new(0).to_s).text.should == ""
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ describe Span::Difference do
27
+ describe "#to_s" do
28
+ let(:positive) { rand(100) + 1 }
29
+ let(:negative) { -(rand(100) + 1) }
30
+
31
+ it "has absolute value of total" do
32
+ Capybara.string(Span::Difference.new(negative).to_s).find("span").text.to_i.should > 0
33
+ end
34
+
35
+ it "has 'positive' class when positive value of total is passed" do
36
+ Capybara.string(Span::Difference.new(positive).to_s).find("span")[:class].should == "positive"
37
+ end
38
+
39
+ it "has 'negative' class when negative value of total is passed" do
40
+ Capybara.string(Span::Difference.new(negative).to_s).find("span")[:class].should == "negative"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neutral::VotingBuilder::Router do
4
+ include Neutral::Engine.routes.url_helpers
5
+
6
+ context "for a persisted vote" do
7
+ let(:vote) { FactoryGirl.create(:vote) }
8
+
9
+ subject { Neutral::VotingBuilder::Router.new(vote) }
10
+
11
+ describe "#positive" do
12
+ subject { super().positive }
13
+
14
+ its([:path]) { should eq(vote_path(vote, value: 1)) }
15
+ its([:method]) { should eq('patch') }
16
+ end
17
+
18
+ describe "#negative" do
19
+ subject { super().negative }
20
+
21
+ its([:path]) { should eq(vote_path(vote, value: 0)) }
22
+ its([:method]) { should eq('patch') }
23
+ end
24
+
25
+ describe "#remove" do
26
+ subject { super().remove }
27
+
28
+ its([:path]) { should eq(vote_path(vote)) }
29
+ end
30
+ end
31
+
32
+ context "for a not persisted vote" do
33
+ let(:vote) { FactoryGirl.build(:vote) }
34
+
35
+ subject { Neutral::VotingBuilder::Router.new(vote) }
36
+
37
+ describe "#positive" do
38
+ subject { super().positive }
39
+
40
+ its([:path]) { should eq(votes_path(vote: vote.main_attributes.merge(value: 1))) }
41
+ its([:method]) { should eq('post') }
42
+ end
43
+
44
+ describe "#negative" do
45
+ subject { super().negative }
46
+
47
+ its([:path]) { should eq(votes_path(vote: vote.main_attributes.merge(value: 0))) }
48
+ its([:method]) { should eq('post') }
49
+ end
50
+
51
+ describe "#remove" do
52
+ subject { -> { super().remove } }
53
+
54
+ it { should raise_error }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neutral::VotingBuilder::Structure do
4
+ let(:random) { [true, false].sample }
5
+
6
+ describe "#to_a" do
7
+ describe "spans" do
8
+ context "when not difference" do
9
+ subject { Neutral::VotingBuilder::Structure.new(random, false).to_a }
10
+
11
+ it { should include("positive_span") }
12
+ it { should include("negative_span") }
13
+ it { should_not include("difference_span") }
14
+ end
15
+
16
+ context "when difference" do
17
+ subject { Neutral::VotingBuilder::Structure.new(random, true).to_a }
18
+
19
+ it { should_not include("positive_span") }
20
+ it { should_not include("negative_span") }
21
+ it { should include("difference_span") }
22
+ end
23
+ end
24
+
25
+ describe "remove link" do
26
+ context "when present" do
27
+ before do
28
+ Neutral.configure { |config| config.can_change = true }
29
+ end
30
+
31
+ subject { Neutral::VotingBuilder::Structure.new(true, random).to_a }
32
+
33
+ it { should include("remove_link") }
34
+ end
35
+
36
+ context "when not present" do
37
+ context "when vote is not persisted" do
38
+ subject { Neutral::VotingBuilder::Structure.new(false, random).to_a }
39
+
40
+ it { should_not include("remove_link") }
41
+ end
42
+
43
+ context "when voter cannot change his vote" do
44
+ before do
45
+ Neutral.configure { |config| config.can_change = false }
46
+ end
47
+
48
+ after do
49
+ Neutral.configure { |config| config.can_change = true }
50
+ end
51
+
52
+ subject { Neutral::VotingBuilder::Structure.new(true, random).to_a }
53
+
54
+ it { should_not include("remove_link") }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end