neutral 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|