neutral 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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