diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..53607ea --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--colour diff --git a/Gemfile b/Gemfile index 52969ce..074bcb3 100644 --- a/Gemfile +++ b/Gemfile @@ -2,30 +2,11 @@ source 'http://rubygems.org' gem 'rails', '3.0.9' -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' - gem 'sqlite3' -# Use unicorn as the web server -# gem 'unicorn' - -# Deploy with Capistrano -# gem 'capistrano' - -# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) -# gem 'ruby-debug' -# gem 'ruby-debug19', :require => 'ruby-debug' - -# Bundle the extra gems: -# gem 'bj' -# gem 'nokogiri' -# gem 'sqlite3-ruby', :require => 'sqlite3' -# gem 'aws-s3', :require => 'aws/s3' - -# Bundle gems for the local environment. Make sure to -# put test-only gems in this group so their generators -# and rake tasks are available in development mode: -# group :development, :test do -# gem 'webrat' -# end +group :development, :test do + gem 'rspec-rails' + gem 'cucumber-rails' + gem 'capybara' + gem 'database_cleaner' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..130a640 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,128 @@ +GEM + remote: http://rubygems.org/ + specs: + abstract (1.0.0) + actionmailer (3.0.9) + actionpack (= 3.0.9) + mail (~> 2.2.19) + actionpack (3.0.9) + activemodel (= 3.0.9) + activesupport (= 3.0.9) + builder (~> 2.1.2) + erubis (~> 2.6.6) + i18n (~> 0.5.0) + rack (~> 1.2.1) + rack-mount (~> 0.6.14) + rack-test (~> 0.5.7) + tzinfo (~> 0.3.23) + activemodel (3.0.9) + activesupport (= 3.0.9) + builder (~> 2.1.2) + i18n (~> 0.5.0) + activerecord (3.0.9) + activemodel (= 3.0.9) + activesupport (= 3.0.9) + arel (~> 2.0.10) + tzinfo (~> 0.3.23) + activeresource (3.0.9) + activemodel (= 3.0.9) + activesupport (= 3.0.9) + activesupport (3.0.9) + arel (2.0.10) + builder (2.1.2) + capybara (1.0.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + selenium-webdriver (~> 0.2.0) + xpath (~> 0.1.4) + childprocess (0.1.9) + ffi (~> 1.0.6) + cucumber (1.0.0) + builder (>= 2.1.2) + diff-lcs (>= 1.1.2) + gherkin (~> 2.4.1) + json (>= 1.4.6) + term-ansicolor (>= 1.0.5) + cucumber-rails (1.0.2) + capybara (>= 1.0.0) + cucumber (~> 1.0.0) + nokogiri (>= 1.4.6) + database_cleaner (0.6.7) + diff-lcs (1.1.2) + erubis (2.6.6) + abstract (>= 1.0.0) + ffi (1.0.9) + gherkin (2.4.1) + json (>= 1.4.6) + i18n (0.5.0) + json (1.5.3) + json_pure (1.5.3) + mail (2.2.19) + activesupport (>= 2.3.6) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) + mime-types (1.16) + nokogiri (1.5.0) + polyglot (0.3.1) + rack (1.2.2) + rack-mount (0.6.14) + rack (>= 1.0.0) + rack-test (0.5.7) + rack (>= 1.0) + rails (3.0.9) + actionmailer (= 3.0.9) + actionpack (= 3.0.9) + activerecord (= 3.0.9) + activeresource (= 3.0.9) + activesupport (= 3.0.9) + bundler (~> 1.0) + railties (= 3.0.9) + railties (3.0.9) + actionpack (= 3.0.9) + activesupport (= 3.0.9) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (~> 0.14.4) + rake (0.9.2) + rdoc (3.8) + rspec (2.6.0) + rspec-core (~> 2.6.0) + rspec-expectations (~> 2.6.0) + rspec-mocks (~> 2.6.0) + rspec-core (2.6.4) + rspec-expectations (2.6.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.6.0) + rspec-rails (2.6.1) + actionpack (~> 3.0) + activesupport (~> 3.0) + railties (~> 3.0) + rspec (~> 2.6.0) + rubyzip (0.9.4) + selenium-webdriver (0.2.2) + childprocess (>= 0.1.9) + ffi (>= 1.0.7) + json_pure + rubyzip + sqlite3 (1.3.3) + term-ansicolor (1.0.5) + thor (0.14.6) + treetop (1.4.9) + polyglot (>= 0.3.1) + tzinfo (0.3.29) + xpath (0.1.4) + nokogiri (~> 1.3) + +PLATFORMS + ruby + +DEPENDENCIES + capybara + cucumber-rails + database_cleaner + rails (= 3.0.9) + rspec-rails + sqlite3 diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000..19b288d --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,8 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" +%> +default: <%= std_opts %> features +wip: --tags @wip:3 --wip features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/config/database.yml b/config/database.yml index 90d87cc..ad7d699 100644 --- a/config/database.yml +++ b/config/database.yml @@ -9,7 +9,7 @@ development: # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. -test: +test: &test adapter: sqlite3 database: db/test.sqlite3 pool: 5 @@ -20,3 +20,6 @@ production: database: db/production.sqlite3 pool: 5 timeout: 5000 + +cucumber: + <<: *test \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..671c0da --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,15 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 0) do + +end diff --git a/features/step_definitions/web_steps.rb b/features/step_definitions/web_steps.rb new file mode 100644 index 0000000..456f5d2 --- /dev/null +++ b/features/step_definitions/web_steps.rb @@ -0,0 +1,211 @@ +# TL;DR: YOU SHOULD DELETE THIS FILE +# +# This file was generated by Cucumber-Rails and is only here to get you a head start +# These step definitions are thin wrappers around the Capybara/Webrat API that lets you +# visit pages, interact with widgets and make assertions about page content. +# +# If you use these step definitions as basis for your features you will quickly end up +# with features that are: +# +# * Hard to maintain +# * Verbose to read +# +# A much better approach is to write your own higher level step definitions, following +# the advice in the following blog posts: +# +# * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html +# * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/ +# * http://elabs.se/blog/15-you-re-cuking-it-wrong +# + + +require 'uri' +require 'cgi' +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths")) +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors")) + +module WithinHelpers + def with_scope(locator) + locator ? within(*selector_for(locator)) { yield } : yield + end +end +World(WithinHelpers) + +# Single-line step scoper +When /^(.*) within (.*[^:])$/ do |step, parent| + with_scope(parent) { When step } +end + +# Multi-line step scoper +When /^(.*) within (.*[^:]):$/ do |step, parent, table_or_string| + with_scope(parent) { When "#{step}:", table_or_string } +end + +Given /^(?:|I )am on (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )go to (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )press "([^"]*)"$/ do |button| + click_button(button) +end + +When /^(?:|I )follow "([^"]*)"$/ do |link| + click_link(link) +end + +When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value| + fill_in(field, :with => value) +end + +When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field| + fill_in(field, :with => value) +end + +# Use this to fill in an entire form with data from a table. Example: +# +# When I fill in the following: +# | Account Number | 5002 | +# | Expiry date | 2009-11-01 | +# | Note | Nice guy | +# | Wants Email? | | +# +# TODO: Add support for checkbox, select og option +# based on naming conventions. +# +When /^(?:|I )fill in the following:$/ do |fields| + fields.rows_hash.each do |name, value| + When %{I fill in "#{name}" with "#{value}"} + end +end + +When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field| + select(value, :from => field) +end + +When /^(?:|I )check "([^"]*)"$/ do |field| + check(field) +end + +When /^(?:|I )uncheck "([^"]*)"$/ do |field| + uncheck(field) +end + +When /^(?:|I )choose "([^"]*)"$/ do |field| + choose(field) +end + +When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field| + attach_file(field, File.expand_path(path)) +end + +Then /^(?:|I )should see "([^"]*)"$/ do |text| + if page.respond_to? :should + page.should have_content(text) + else + assert page.has_content?(text) + end +end + +Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp| + regexp = Regexp.new(regexp) + + if page.respond_to? :should + page.should have_xpath('//*', :text => regexp) + else + assert page.has_xpath?('//*', :text => regexp) + end +end + +Then /^(?:|I )should not see "([^"]*)"$/ do |text| + if page.respond_to? :should + page.should have_no_content(text) + else + assert page.has_no_content?(text) + end +end + +Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp| + regexp = Regexp.new(regexp) + + if page.respond_to? :should + page.should have_no_xpath('//*', :text => regexp) + else + assert page.has_no_xpath?('//*', :text => regexp) + end +end + +Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value| + with_scope(parent) do + field = find_field(field) + field_value = (field.tag_name == 'textarea') ? field.text : field.value + if field_value.respond_to? :should + field_value.should =~ /#{value}/ + else + assert_match(/#{value}/, field_value) + end + end +end + +Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value| + with_scope(parent) do + field = find_field(field) + field_value = (field.tag_name == 'textarea') ? field.text : field.value + if field_value.respond_to? :should_not + field_value.should_not =~ /#{value}/ + else + assert_no_match(/#{value}/, field_value) + end + end +end + +Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent| + with_scope(parent) do + field_checked = find_field(label)['checked'] + if field_checked.respond_to? :should + field_checked.should be_true + else + assert field_checked + end + end +end + +Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent| + with_scope(parent) do + field_checked = find_field(label)['checked'] + if field_checked.respond_to? :should + field_checked.should be_false + else + assert !field_checked + end + end +end + +Then /^(?:|I )should be on (.+)$/ do |page_name| + current_path = URI.parse(current_url).path + if current_path.respond_to? :should + current_path.should == path_to(page_name) + else + assert_equal path_to(page_name), current_path + end +end + +Then /^(?:|I )should have the following query string:$/ do |expected_pairs| + query = URI.parse(current_url).query + actual_params = query ? CGI.parse(query) : {} + expected_params = {} + expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')} + + if actual_params.respond_to? :should + actual_params.should == expected_params + else + assert_equal expected_params, actual_params + end +end + +Then /^show me the page$/ do + save_and_open_page +end diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..6c40c8f --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,50 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'cucumber/rails' + +# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In +# order to ease the transition to Capybara we set the default here. If you'd +# prefer to use XPath just remove this line and adjust any selectors in your +# steps to use the XPath syntax. +Capybara.default_selector = :css + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false + +# Remove/comment out the lines below if your app doesn't have a database. +# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. +begin + DatabaseCleaner.strategy = :transaction +rescue NameError + raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." +end + +# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. +# See the DatabaseCleaner documentation for details. Example: +# +# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do +# DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]} +# end +# +# Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do +# DatabaseCleaner.strategy = :transaction +# end +# diff --git a/features/support/paths.rb b/features/support/paths.rb new file mode 100644 index 0000000..ee9b251 --- /dev/null +++ b/features/support/paths.rb @@ -0,0 +1,33 @@ +module NavigationHelpers + # Maps a name to a path. Used by the + # + # When /^I go to (.+)$/ do |page_name| + # + # step definition in web_steps.rb + # + def path_to(page_name) + case page_name + + when /^the home\s?page$/ + '/' + + # Add more mappings here. + # Here is an example that pulls values out of the Regexp: + # + # when /^(.*)'s profile page$/i + # user_profile_path(User.find_by_login($1)) + + else + begin + page_name =~ /^the (.*) page$/ + path_components = $1.split(/\s+/) + self.send(path_components.push('path').join('_').to_sym) + rescue NoMethodError, ArgumentError + raise "Can't find mapping from \"#{page_name}\" to a path.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end + end +end + +World(NavigationHelpers) diff --git a/features/support/selectors.rb b/features/support/selectors.rb new file mode 100644 index 0000000..44e34a4 --- /dev/null +++ b/features/support/selectors.rb @@ -0,0 +1,39 @@ +module HtmlSelectorsHelpers + # Maps a name to a selector. Used primarily by the + # + # When /^(.+) within (.+)$/ do |step, scope| + # + # step definitions in web_steps.rb + # + def selector_for(locator) + case locator + + when "the page" + "html > body" + + # Add more mappings here. + # Here is an example that pulls values out of the Regexp: + # + # when /^the (notice|error|info) flash$/ + # ".flash.#{$1}" + + # You can also return an array to use a different selector + # type, like: + # + # when /the header/ + # [:xpath, "//header"] + + # This allows you to provide a quoted selector as the scope + # for "within" steps as was previously the default for the + # web steps: + when /^"(.+)"$/ + $1 + + else + raise "Can't find mapping from \"#{locator}\" to a selector.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end +end + +World(HtmlSelectorsHelpers) diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 0000000..83f7947 --- /dev/null +++ b/lib/tasks/cucumber.rake @@ -0,0 +1,65 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + + task :statsetup do + require 'rails/code_statistics' + ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') + ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') + end + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end + + # In case we don't have ActiveRecord, append a no-op task that we can depend upon. + task 'db:test:prepare' do + end + + task :stats => 'cucumber:statsetup' +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/script/cucumber b/script/cucumber new file mode 100755 index 0000000..7fa5c92 --- /dev/null +++ b/script/cucumber @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +if vendored_cucumber_bin + load File.expand_path(vendored_cucumber_bin) +else + require 'rubygems' unless ENV['NO_RUBYGEMS'] + require 'cucumber' + load Cucumber::BINARY +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..9b8b02c --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,27 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' + +# 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} + +RSpec.configure do |config| + # == Mock Framework + # + # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: + # + # config.mock_with :mocha + # config.mock_with :flexmock + # config.mock_with :rr + config.mock_with :rspec + + # 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 +end