diff --git a/.travis.yml b/.travis.yml index cdb33fd2f..3d8884632 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: ruby -sudo: false +sudo: required cache: bundler bundler_args: '--without production development' rvm: @@ -11,3 +11,5 @@ before_script: - sleep 10 services: - elasticsearch +addons: + chrome: stable diff --git a/Gemfile b/Gemfile index 5a1943a13..fc93e86d9 100644 --- a/Gemfile +++ b/Gemfile @@ -49,17 +49,17 @@ end group :development, :test do gem "rspec-rails", '~> 3.7.2' - gem "capybara", '~> 2.4.4' gem "byebug" end group :test do - # Do not upgrade until - # https://github.com/DatabaseCleaner/database_cleaner/issues/317 is fixed - gem "database_cleaner", '1.3.0' + gem "database_cleaner", '1.6.2' gem 'shoulda', ">= 3.5" gem 'fabrication' gem 'faker' + gem 'capybara', '~> 2.7' + gem 'capybara-selenium', '~> 0.0.6' + gem 'chromedriver-helper', '~> 1.0' end group :production do diff --git a/Gemfile.lock b/Gemfile.lock index 3032c85f9..1bd435d94 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -53,6 +53,8 @@ GEM sshkit (>= 1.6.1, != 1.7.0) arbre (1.1.1) activesupport (>= 3.0.0) + archive-zip (0.11.0) + io-like (~> 0.3.0) arel (6.0.4) ast (2.4.0) autoprefixer-rails (6.3.1) @@ -84,12 +86,21 @@ GEM capistrano-rbenv (2.1.3) capistrano (~> 3.1) sshkit (~> 1.3) - capybara (2.4.4) - mime-types (>= 1.16) + capybara (2.18.0) + addressable + mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) - xpath (~> 2.0) + xpath (>= 2.0, < 4.0) + capybara-selenium (0.0.6) + capybara + selenium-webdriver + childprocess (0.9.0) + ffi (~> 1.0, >= 1.0.11) + chromedriver-helper (1.2.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) chronic (0.10.2) coderay (1.1.2) coffee-rails (4.1.0) @@ -103,7 +114,7 @@ GEM concurrent-ruby (1.0.5) crass (1.0.3) dalli (2.7.2) - database_cleaner (1.3.0) + database_cleaner (1.6.2) debug_inspector (0.0.3) devise (4.4.1) bcrypt (~> 3.0) @@ -138,6 +149,7 @@ GEM i18n (~> 0.5) faraday (0.9.1) multipart-post (>= 1.2, < 3) + ffi (1.9.23) formtastic (3.1.5) actionpack (>= 3.2.13) formtastic_i18n (0.6.0) @@ -160,6 +172,7 @@ GEM has_scope (~> 0.6) railties (>= 4.2, <= 5.2) responders + io-like (0.3.0) jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -303,6 +316,7 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) + rubyzip (1.2.1) sass (3.4.21) sass-rails (5.0.7) railties (>= 4.0.0, < 6) @@ -312,6 +326,9 @@ GEM tilt (>= 1.1, < 3) select2-rails (4.0.1) thor (~> 0.14) + selenium-webdriver (3.11.0) + childprocess (~> 0.5) + rubyzip (~> 1.2) shoulda (3.5.0) shoulda-context (~> 1.0, >= 1.0.1) shoulda-matchers (>= 1.4.1, < 3.0) @@ -359,8 +376,8 @@ GEM sprockets-rails (>= 2.0, < 4.0) whenever (0.9.4) chronic (>= 0.6.3) - xpath (2.0.0) - nokogiri (~> 1.3) + xpath (3.0.0) + nokogiri (~> 1.8) PLATFORMS ruby @@ -375,10 +392,12 @@ DEPENDENCIES capistrano (~> 3.1) capistrano-rails (~> 1.1) capistrano-rbenv (~> 2.1) - capybara (~> 2.4.4) + capybara (~> 2.7) + capybara-selenium (~> 0.0.6) + chromedriver-helper (~> 1.0) coffee-rails dalli - database_cleaner (= 1.3.0) + database_cleaner (= 1.6.2) devise (~> 4.4.1) dotenv-rails (= 1.0.2) elasticsearch-model diff --git a/spec/features/create_offer_spec.rb b/spec/features/create_offer_spec.rb new file mode 100644 index 000000000..1a7c2de07 --- /dev/null +++ b/spec/features/create_offer_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +feature 'create offer' do + let(:user) do + user = Fabricate( + :user, + email: 'user@timeoverflow.org', + password: 'papapa22', + terms_accepted_at: 1.day.from_now + ) + + user.add_to_organization(organization) + + user + end + + let(:organization) { Fabricate(:organization) } + + let!(:category) do + Category.create(name: 'le category') + end + + context 'create an offer' do + it 'can be created' do + sign_in_with(user.email, user.password) + click_on I18n.t('activerecord.models.offer.other') + click_on I18n.t('helpers.submit.create', model: I18n.t('activerecord.models.offer.one')) + fill_in 'offer_title', with: 'Le title' + fill_in 'offer_description', with: 'Lorem ipsum in the night' + select category.name, from: 'offer_category_id' + + # TODO there are two i18n keys for getting "Crear oferta" copy ( one returns 'Crear oferta' and the other 'Crear Oferta' ) + click_on I18n.t('offers.new.submit', model: I18n.t('activerecord.models.offer.one')) + + page.save_screenshot('create-offer-yolo.png') + end + end +end + diff --git a/spec/features/sign_in_spec.rb b/spec/features/sign_in_spec.rb new file mode 100644 index 000000000..a7abc778d --- /dev/null +++ b/spec/features/sign_in_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +feature 'sign in' do + let(:user) do + Fabricate(:user, email: 'user@timeoverflow.org', password: 'papapa22') + end + + context 'with a valid password' do + it 'signs the user in' do + expect(Capybara.current_session.driver.browser.manage.cookie_named('_timeoverflow_session')).to be_falsy + + sign_in_with(user.email, user.password) + + expect(Capybara.current_session.driver.browser.manage.cookie_named('_timeoverflow_session')).to be_truthy + expect(page).to have_no_content(I18n.t('devise.failure.invalid')) + end + end + + context 'with an invalid password' do + it 'shows an error' do + sign_in_with(user.email, 'wrong_password') + expect(page).to have_content(I18n.t('devise.failure.invalid')) + end + end +end diff --git a/spec/features/sign_out_spec.rb b/spec/features/sign_out_spec.rb new file mode 100644 index 000000000..7199b6fa0 --- /dev/null +++ b/spec/features/sign_out_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +feature 'sign out' do + let!(:user) do + Fabricate( + :user, + email: 'user@timeoverflow.org', + password: 'papapa22', + terms_accepted_at: 1.day.from_now + ) + end + + it 'signs the user out' do + sign_in_with(user.email, user.password) + click_link user.email + click_link I18n.t('application.navbar.sign_out') + + expect(current_path).to eq(root_path) + end +end diff --git a/spec/features/transfer_spec.rb b/spec/features/transfer_spec.rb new file mode 100644 index 000000000..3573a3dfc --- /dev/null +++ b/spec/features/transfer_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +feature 'time transfer' do + let(:user) do + user = Fabricate( + :user, + email: 'user@timeoverflow.org', + password: 'papapa22', + terms_accepted_at: 1.day.from_now + ) + + user.add_to_organization(organization) + + user + end + + let(:other_user) do + other_user = Fabricate(:user, email: 'other_user@timeoverflow.org', password: 'papapa22') + + other_user.add_to_organization(organization) + other_user + end + + let(:organization) { Fabricate(:organization) } + + it 'transfers time from one account to another' do + offer = Fabricate(:offer, user: other_user, organization: organization) + sign_in_with(user.email, user.password) + navigate_to_member + navigate_to_transfer_for(offer) + submit_transfer_form_with(hours: 2) + + expect(page).to have_css('.transactions tr', count: 1, text: '2:00') + end + + def submit_transfer_form_with(hours: nil, minutes: nil) + within transfer_form do + fill_in 'transfer_hours', with: hours + fill_in 'transfer_minutes', with: minutes + + click_button 'Crear Transferencia' + end + end + + def navigate_to_transfer_for(offer) + click_link offer.title + click_link I18n.t('offers.show.give_time_for') + end + + def navigate_to_member + within members_list do + click_link other_user.username + end + end + + def members_list + find('.users tbody') + end + + def transfer_form + find('#new_transfer') + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5f36a63ee..43434d797 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,11 +5,32 @@ require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'capybara/rails' +require 'capybara/rspec' require 'database_cleaner' require 'fabrication' +require 'selenium/webdriver' require 'faker' I18n.reload! +Capybara.register_driver :chrome do |app| + Capybara::Selenium::Driver.new(app, browser: :chrome) +end + +Capybara.register_driver :headless_chrome do |app| + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: { args: %w(headless disable-gpu) } + ) + + Capybara::Selenium::Driver.new( + app, + browser: :chrome, + desired_capabilities: capabilities + ) +end + +Capybara.javascript_driver = :headless_chrome +Capybara.default_driver = :headless_chrome + # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } @@ -35,14 +56,6 @@ # config.mock_with :flexmock # config.mock_with :rr - # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = "#{::Rails.root}/spec/fixtures" - - # If you're not using ActiveRecord, or you'd prefer not to run each of your - # examples within a transaction, remove the following line or assign false - # instead of true. - config.use_transactional_fixtures = true - # If true, the base class of anonymous controllers will be inferred # automatically. This will be the default behavior in future versions of # rspec-rails. @@ -52,30 +65,76 @@ # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 - config.order = "random" - config.include Devise::Test::ControllerHelpers, :type => :controller - config.include ControllerMacros, :type => :controller + puts "Randomized with seed #{config.seed}." - # Database cleaner configuration + config.register_ordering(:global) do |items| + items_by_type = items.group_by { |item| item.metadata[:type] === :feature ? :feature : :rest } - config.before :suite do - DatabaseCleaner.strategy = :transaction - DatabaseCleaner.clean_with :truncation + feature_specs = items_by_type[:feature] || [] + rest_of_specs = items_by_type[:rest] || [] + + + random = Random.new(config.seed) + + [ + *rest_of_specs.shuffle(random: random), + *feature_specs.shuffle(random: random) + ] + end - # Create terms and conditions - Document.create(label: "t&c") do |doc| + config.include Devise::TestHelpers, type: :controller + config.include ControllerMacros, type: :controller + config.include Features::SessionHelpers, type: :feature + + # Create terms and conditions + config.before do + Document.create!(label: "t&c") do |doc| doc.title = "Terms and Conditions" doc.content = "blah blah blah" end + end + + config.use_transactional_fixtures = false + + config.before(:suite) do + if config.use_transactional_fixtures? + raise(<<-MSG) + Delete line `config.use_transactional_fixtures = true` from rails_helper.rb + (or set it to false) to prevent uncommitted transactions being used in + JavaScript-dependent specs. + + During testing, the app-under-test that the browser driver connects to + uses a different database connection to the database connection used by + the spec. The app's database connection would not be able to access + uncommitted transaction data setup over the spec's database connection. + MSG + end + DatabaseCleaner.clean_with(:truncation) + end + config.before(:each) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each, type: :feature) do + # :rack_test driver's Rack app under test shares database connection + # with the specs, so continue to use transaction strategy for speed. + driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test + + unless driver_shares_db_connection_with_specs + # Driver is probably for an external browser with an app + # under test that does *not* share a database connection with the + # specs, so use truncation strategy. + DatabaseCleaner.strategy = :truncation + end end config.before(:each) do DatabaseCleaner.start end - config.after(:each) do + config.append_after(:each) do DatabaseCleaner.clean end diff --git a/spec/support/features/session_helpers.rb b/spec/support/features/session_helpers.rb new file mode 100644 index 000000000..d9a686c19 --- /dev/null +++ b/spec/support/features/session_helpers.rb @@ -0,0 +1,10 @@ +module Features + module SessionHelpers + def sign_in_with(email, password) + visit '/login' + fill_in 'user_email', with: email + fill_in 'user_password', with: password + click_button I18n.t('application.login_form.button') + end + end +end