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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +194 -0
- data/MIT-LICENSE +20 -0
- data/README.md +143 -0
- data/Rakefile +21 -0
- data/app/assets/stylesheets/neutral/index.css +43 -0
- data/app/controllers/neutral/application_controller.rb +22 -0
- data/app/controllers/neutral/votes_controller.rb +48 -0
- data/app/helpers/neutral/votes_helper.rb +16 -0
- data/app/models/neutral/vote.rb +26 -0
- data/app/models/neutral/voting.rb +39 -0
- data/app/views/neutral/votes/create.js.erb +27 -0
- data/app/views/neutral/votes/destroy.js.erb +18 -0
- data/app/views/neutral/votes/errors/cannot_change.js.erb +9 -0
- data/app/views/neutral/votes/errors/duplicate.js.erb +9 -0
- data/app/views/neutral/votes/errors/require_login.js.erb +10 -0
- data/app/views/neutral/votes/update.js.erb +11 -0
- data/bin/rails +8 -0
- data/config/locales/impartial.yml +6 -0
- data/config/routes.rb +3 -0
- data/lib/generators/neutral/formats.rb +16 -0
- data/lib/generators/neutral/install/install_generator.rb +47 -0
- data/lib/generators/neutral/install/templates/initializer.rb +41 -0
- data/lib/generators/neutral/install/templates/locale.yml +5 -0
- data/lib/generators/neutral/install/templates/votes.rb +12 -0
- data/lib/generators/neutral/install/templates/votings.rb +12 -0
- data/lib/generators/neutral/uninstall/templates/drop_neutral_votes_table.rb +9 -0
- data/lib/generators/neutral/uninstall/templates/drop_neutral_votings_table.rb +9 -0
- data/lib/generators/neutral/uninstall/uninstall_generator.rb +55 -0
- data/lib/neutral/configuration.rb +27 -0
- data/lib/neutral/engine.rb +28 -0
- data/lib/neutral/errors.rb +11 -0
- data/lib/neutral/helpers/action_view_extension.rb +19 -0
- data/lib/neutral/helpers/routes.rb +9 -0
- data/lib/neutral/icons/collection.rb +55 -0
- data/lib/neutral/icons/set.rb +22 -0
- data/lib/neutral/model/active_record_extension.rb +31 -0
- data/lib/neutral/model/vote_cached.rb +32 -0
- data/lib/neutral/version.rb +3 -0
- data/lib/neutral/voting_builder/builder.rb +42 -0
- data/lib/neutral/voting_builder/elements/link.rb +46 -0
- data/lib/neutral/voting_builder/elements/span.rb +41 -0
- data/lib/neutral/voting_builder/elements.rb +23 -0
- data/lib/neutral/voting_builder/router.rb +45 -0
- data/lib/neutral/voting_builder/structure.rb +21 -0
- data/lib/neutral.rb +46 -0
- data/neutral.gemspec +34 -0
- data/spec/controllers/votes_controller_spec.rb +138 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +29 -0
- data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/spec/dummy/app/controllers/application_controller.rb +11 -0
- data/spec/dummy/app/controllers/posts_controller.rb +33 -0
- data/spec/dummy/app/controllers/sessions_controller.rb +21 -0
- data/spec/dummy/app/controllers/users_controller.rb +24 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +7 -0
- data/spec/dummy/app/views/layouts/application.html.erb +25 -0
- data/spec/dummy/app/views/posts/_form.html.erb +21 -0
- data/spec/dummy/app/views/posts/index.html.erb +26 -0
- data/spec/dummy/app/views/posts/new.html.erb +5 -0
- data/spec/dummy/app/views/sessions/new.html.erb +18 -0
- data/spec/dummy/app/views/users/_form.html.erb +25 -0
- data/spec/dummy/app/views/users/new.html.erb +3 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +29 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/neutral.rb +41 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/locales/neutral.yml +5 -0
- data/spec/dummy/config/routes.rb +13 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20140110214145_create_posts.rb +9 -0
- data/spec/dummy/db/migrate/20140111225442_create_users.rb +10 -0
- data/spec/dummy/db/migrate/20140118172808881589_create_neutral_votes.rb +12 -0
- data/spec/dummy/db/migrate/20140118172808882161_create_neutral_votings.rb +12 -0
- data/spec/dummy/db/schema.rb +47 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories.rb +18 -0
- data/spec/helpers/action_view_extension_spec.rb +45 -0
- data/spec/icons/collection_spec.rb +38 -0
- data/spec/icons/set_spec.rb +25 -0
- data/spec/models/active_record_extension_spec.rb +49 -0
- data/spec/models/vote_spec.rb +51 -0
- data/spec/models/voting_spec.rb +60 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/shared_examples/controller_examples.rb +45 -0
- data/spec/support/votes_controller_base_class.rb +28 -0
- data/spec/support/votes_controller_helpers.rb +24 -0
- data/spec/voting_builder/builder_spec.rb +66 -0
- data/spec/voting_builder/elements/link_spec.rb +31 -0
- data/spec/voting_builder/elements/span_spec.rb +44 -0
- data/spec/voting_builder/router_spec.rb +57 -0
- data/spec/voting_builder/structure_spec.rb +59 -0
- 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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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
|