diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..91ab2c5 --- /dev/null +++ b/Guardfile @@ -0,0 +1,42 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard 'bundler' do + watch('Gemfile') + # Uncomment next line if Gemfile contain `gemspec' command + # watch(/^.+\.gemspec/) +end + +guard 'cucumber' do + watch(%r{^features/.+\.feature$}) + watch(%r{^features/support/.+$}) { 'features' } + watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' } +end + +guard 'rails' do + watch('Gemfile.lock') + watch(%r{^(config|lib)/.*}) +end + + +guard 'rspec' do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } + + # Rails example + watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } + watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } + watch(%r{^spec/support/(.+)\.rb$}) { "spec" } + watch('config/routes.rb') { "spec/routing" } + watch('app/controllers/application_controller.rb') { "spec/controllers" } + + # Capybara features specs + watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" } + + # Turnip features and steps + watch(%r{^spec/acceptance/(.+)\.feature$}) + watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } +end + diff --git a/config/application.rb b/config/application.rb index 78a6aad..9e33d60 100644 --- a/config/application.rb +++ b/config/application.rb @@ -17,6 +17,18 @@ module Signup class Application < Rails::Application + + # don't generate RSpec tests for views and helpers + config.generators do |g| + + g.test_framework :rspec, fixture: true + g.fixture_replacement :factory_girl, dir: 'spec/factories' + + + g.view_specs false + g.helper_specs false + end + # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000..2f0d8ce --- /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 = "-r features/support/ -r features/step_definitions --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/features/step_definitions/email_steps.rb b/features/step_definitions/email_steps.rb new file mode 100644 index 0000000..12bcb3f --- /dev/null +++ b/features/step_definitions/email_steps.rb @@ -0,0 +1,206 @@ +# Commonly used email steps +# +# To add your own steps make a custom_email_steps.rb +# The provided methods are: +# +# last_email_address +# reset_mailer +# open_last_email +# visit_in_email +# unread_emails_for +# mailbox_for +# current_email +# open_email +# read_emails_for +# find_email +# +# General form for email scenarios are: +# - clear the email queue (done automatically by email_spec) +# - execute steps that sends an email +# - check the user received an/no/[0-9] emails +# - open the email +# - inspect the email contents +# - interact with the email (e.g. click links) +# +# The Cucumber steps below are setup in this order. + +module EmailHelpers + def current_email_address + # Replace with your a way to find your current email. e.g @current_user.email + # last_email_address will return the last email address used by email spec to find an email. + # Note that last_email_address will be reset after each Scenario. + last_email_address || "example@example.com" + end +end + +World(EmailHelpers) + +# +# Reset the e-mail queue within a scenario. +# This is done automatically before each scenario. +# + +Given /^(?:a clear email queue|no emails have been sent)$/ do + reset_mailer +end + +# +# Check how many emails have been sent/received +# + +Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails?$/ do |address, amount| + unread_emails_for(address).size.should == parse_email_count(amount) +end + +Then /^(?:I|they|"([^"]*?)") should have (an|no|\d+) emails?$/ do |address, amount| + mailbox_for(address).size.should == parse_email_count(amount) +end + +Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject "([^"]*?)"$/ do |address, amount, subject| + unread_emails_for(address).select { |m| m.subject =~ Regexp.new(Regexp.escape(subject)) }.size.should == parse_email_count(amount) +end + +Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject \/([^"]*?)\/$/ do |address, amount, subject| + unread_emails_for(address).select { |m| m.subject =~ Regexp.new(subject) }.size.should == parse_email_count(amount) +end + +Then /^(?:I|they|"([^"]*?)") should receive an email with the following body:$/ do |address, expected_body| + open_email(address, :with_text => expected_body) +end + +# +# Accessing emails +# + +# Opens the most recently received email +When /^(?:I|they|"([^"]*?)") opens? the email$/ do |address| + open_email(address) +end + +When /^(?:I|they|"([^"]*?)") opens? the email with subject "([^"]*?)"$/ do |address, subject| + open_email(address, :with_subject => subject) +end + +When /^(?:I|they|"([^"]*?)") opens? the email with subject \/([^"]*?)\/$/ do |address, subject| + open_email(address, :with_subject => Regexp.new(subject)) +end + +When /^(?:I|they|"([^"]*?)") opens? the email with text "([^"]*?)"$/ do |address, text| + open_email(address, :with_text => text) +end + +When /^(?:I|they|"([^"]*?)") opens? the email with text \/([^"]*?)\/$/ do |address, text| + open_email(address, :with_text => Regexp.new(text)) +end + +# +# Inspect the Email Contents +# + +Then /^(?:I|they) should see "([^"]*?)" in the email subject$/ do |text| + current_email.should have_subject(text) +end + +Then /^(?:I|they) should see \/([^"]*?)\/ in the email subject$/ do |text| + current_email.should have_subject(Regexp.new(text)) +end + +Then /^(?:I|they) should see "([^"]*?)" in the email body$/ do |text| + current_email.default_part_body.to_s.should include(text) +end + +Then /^(?:I|they) should see \/([^"]*?)\/ in the email body$/ do |text| + current_email.default_part_body.to_s.should =~ Regexp.new(text) +end + +Then /^(?:I|they) should see the email delivered from "([^"]*?)"$/ do |text| + current_email.should be_delivered_from(text) +end + +Then /^(?:I|they) should see "([^\"]*)" in the email "([^"]*?)" header$/ do |text, name| + current_email.should have_header(name, text) +end + +Then /^(?:I|they) should see \/([^\"]*)\/ in the email "([^"]*?)" header$/ do |text, name| + current_email.should have_header(name, Regexp.new(text)) +end + +Then /^I should see it is a multi\-part email$/ do + current_email.should be_multipart +end + +Then /^(?:I|they) should see "([^"]*?)" in the email html part body$/ do |text| + current_email.html_part.body.to_s.should include(text) +end + +Then /^(?:I|they) should see "([^"]*?)" in the email text part body$/ do |text| + current_email.text_part.body.to_s.should include(text) +end + +# +# Inspect the Email Attachments +# + +Then /^(?:I|they) should see (an|no|\d+) attachments? with the email$/ do |amount| + current_email_attachments.size.should == parse_email_count(amount) +end + +Then /^there should be (an|no|\d+) attachments? named "([^"]*?)"$/ do |amount, filename| + current_email_attachments.select { |a| a.filename == filename }.size.should == parse_email_count(amount) +end + +Then /^attachment (\d+) should be named "([^"]*?)"$/ do |index, filename| + current_email_attachments[(index.to_i - 1)].filename.should == filename +end + +Then /^there should be (an|no|\d+) attachments? of type "([^"]*?)"$/ do |amount, content_type| + current_email_attachments.select { |a| a.content_type.include?(content_type) }.size.should == parse_email_count(amount) +end + +Then /^attachment (\d+) should be of type "([^"]*?)"$/ do |index, content_type| + current_email_attachments[(index.to_i - 1)].content_type.should include(content_type) +end + +Then /^all attachments should not be blank$/ do + current_email_attachments.each do |attachment| + attachment.read.size.should_not == 0 + end +end + +Then /^show me a list of email attachments$/ do + EmailSpec::EmailViewer::save_and_open_email_attachments_list(current_email) +end + +# +# Interact with Email Contents +# + +When /^(?:I|they) follow "([^"]*?)" in the email$/ do |link| + visit_in_email(link) +end + +When /^(?:I|they) click the first link in the email$/ do + click_first_link_in_email +end + +# +# Debugging +# These only work with Rails and OSx ATM since EmailViewer uses RAILS_ROOT and OSx's 'open' command. +# Patches accepted. ;) +# + +Then /^save and open current email$/ do + EmailSpec::EmailViewer::save_and_open_email(current_email) +end + +Then /^save and open all text emails$/ do + EmailSpec::EmailViewer::save_and_open_all_text_emails +end + +Then /^save and open all html emails$/ do + EmailSpec::EmailViewer::save_and_open_all_html_emails +end + +Then /^save and open all raw emails$/ do + EmailSpec::EmailViewer::save_and_open_all_raw_emails +end diff --git a/features/support/email_spec.rb b/features/support/email_spec.rb new file mode 100644 index 0000000..797d3b2 --- /dev/null +++ b/features/support/email_spec.rb @@ -0,0 +1 @@ +require 'email_spec/cucumber' diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..9f3b86d --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,58 @@ +# 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 CSS3 selectors rather than XPath. +# If you'd prefer to use XPath, just uncomment this line and adjust any +# selectors in your step definitions to use the XPath syntax. +# Capybara.default_selector = :xpath + +# 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 +# # { :except => [:widgets] } may not do what you expect here +# # as Cucumber::Rails::Database.javascript_strategy overrides +# # this setting. +# DatabaseCleaner.strategy = :truncation +# end +# +# Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do +# DatabaseCleaner.strategy = :transaction +# end +# + +# Possible values are :truncation and :transaction +# The :transaction strategy is faster, but might give you threading problems. +# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature +Cucumber::Rails::Database.javascript_strategy = :truncation + diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 0000000..9f53ce4 --- /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 => '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 => '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 => '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 the generic Rails test:prepare hook, append a no-op task that we can depend upon. + task '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..280231a --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,51 @@ +# 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' +require 'email_spec' +require 'rspec/autorun' + +# 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| + config.include(EmailSpec::Helpers) + config.include(EmailSpec::Matchers) + # ## 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 + + # 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. + config.infer_base_class_for_anonymous_controllers = false + + # Run specs in random order to surface order dependencies. If you find an + # 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.before(:suite) do + DatabaseCleaner.strategy = :truncation + end + config.before(:each) do + DatabaseCleaner.start + end + config.after(:each) do + DatabaseCleaner.clean + end +end diff --git a/spec/support/devise.rb b/spec/support/devise.rb new file mode 100644 index 0000000..3552bea --- /dev/null +++ b/spec/support/devise.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include Devise::TestHelpers, :type => :controller +end