path-email_spec 0.6.2

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.
Files changed (180) hide show
  1. data/History.txt +199 -0
  2. data/MIT-LICENSE.txt +19 -0
  3. data/README.rdoc +137 -0
  4. data/Rakefile +64 -0
  5. data/examples/rails3_root/Gemfile +24 -0
  6. data/examples/rails3_root/README +5 -0
  7. data/examples/rails3_root/Rakefile +11 -0
  8. data/examples/rails3_root/app/controllers/application_controller.rb +3 -0
  9. data/examples/rails3_root/app/controllers/welcome_controller.rb +16 -0
  10. data/examples/rails3_root/app/helpers/application_helper.rb +2 -0
  11. data/examples/rails3_root/app/mailers/user_mailer.rb +39 -0
  12. data/examples/rails3_root/app/models/user.rb +2 -0
  13. data/examples/rails3_root/app/views/user_mailer/attachments.html.erb +6 -0
  14. data/examples/rails3_root/app/views/user_mailer/newsletter.html.erb +8 -0
  15. data/examples/rails3_root/app/views/user_mailer/signup.html.erb +3 -0
  16. data/examples/rails3_root/app/views/welcome/attachments.html.erb +0 -0
  17. data/examples/rails3_root/app/views/welcome/confirm.html.erb +1 -0
  18. data/examples/rails3_root/app/views/welcome/index.html.erb +8 -0
  19. data/examples/rails3_root/app/views/welcome/newsletter.html.erb +0 -0
  20. data/examples/rails3_root/app/views/welcome/signup.html.erb +1 -0
  21. data/examples/rails3_root/attachments/document.pdf +0 -0
  22. data/examples/rails3_root/attachments/image.png +0 -0
  23. data/examples/rails3_root/config.ru +4 -0
  24. data/examples/rails3_root/config/application.rb +43 -0
  25. data/examples/rails3_root/config/boot.rb +14 -0
  26. data/examples/rails3_root/config/cucumber.yml +7 -0
  27. data/examples/rails3_root/config/database.yml +25 -0
  28. data/examples/rails3_root/config/environment.rb +6 -0
  29. data/examples/rails3_root/config/environments/development.rb +19 -0
  30. data/examples/rails3_root/config/environments/production.rb +42 -0
  31. data/examples/rails3_root/config/environments/test.rb +29 -0
  32. data/examples/rails3_root/config/initializers/backtrace_silencers.rb +7 -0
  33. data/examples/rails3_root/config/initializers/cookie_verification_secret.rb +7 -0
  34. data/examples/rails3_root/config/initializers/inflections.rb +10 -0
  35. data/examples/rails3_root/config/initializers/mime_types.rb +5 -0
  36. data/examples/rails3_root/config/initializers/rspec_generator.rb +6 -0
  37. data/examples/rails3_root/config/initializers/session_store.rb +10 -0
  38. data/examples/rails3_root/config/locales/en.yml +5 -0
  39. data/examples/rails3_root/config/routes.rb +7 -0
  40. data/examples/rails3_root/db/migrate/20090125013728_create_users.rb +11 -0
  41. data/examples/rails3_root/db/migrate/20090908054656_create_delayed_jobs.rb +20 -0
  42. data/examples/rails3_root/db/schema.rb +32 -0
  43. data/examples/rails3_root/db/seeds.rb +7 -0
  44. data/examples/rails3_root/doc/README_FOR_APP +2 -0
  45. data/examples/rails3_root/features/attachments.feature +27 -0
  46. data/examples/rails3_root/features/delayed_job.feature +13 -0
  47. data/examples/rails3_root/features/errors.feature +32 -0
  48. data/examples/rails3_root/features/example.feature +62 -0
  49. data/examples/rails3_root/features/step_definitions/user_steps.rb +26 -0
  50. data/examples/rails3_root/features/step_definitions/web_steps.rb +202 -0
  51. data/examples/rails3_root/features/support/env.rb +59 -0
  52. data/examples/rails3_root/features/support/env_ext.rb +2 -0
  53. data/examples/rails3_root/features/support/paths.rb +33 -0
  54. data/examples/rails3_root/lib/tasks/cucumber.rake +47 -0
  55. data/examples/rails3_root/lib/tasks/rspec.rake +69 -0
  56. data/examples/rails3_root/public/404.html +26 -0
  57. data/examples/rails3_root/public/422.html +26 -0
  58. data/examples/rails3_root/public/500.html +26 -0
  59. data/examples/rails3_root/public/favicon.ico +0 -0
  60. data/examples/rails3_root/public/images/rails.png +0 -0
  61. data/examples/rails3_root/public/javascripts/application.js +2 -0
  62. data/examples/rails3_root/public/javascripts/controls.js +965 -0
  63. data/examples/rails3_root/public/javascripts/dragdrop.js +974 -0
  64. data/examples/rails3_root/public/javascripts/effects.js +1123 -0
  65. data/examples/rails3_root/public/javascripts/prototype.js +4874 -0
  66. data/examples/rails3_root/public/javascripts/rails.js +109 -0
  67. data/examples/rails3_root/public/robots.txt +5 -0
  68. data/examples/rails3_root/script/cucumber +10 -0
  69. data/examples/rails3_root/script/rails +9 -0
  70. data/examples/rails3_root/spec/controllers/welcome_controller_spec.rb +18 -0
  71. data/examples/rails3_root/spec/models/user_mailer_spec.rb +47 -0
  72. data/examples/rails3_root/spec/spec.opts +2 -0
  73. data/examples/rails3_root/spec/spec_helper.rb +19 -0
  74. data/examples/rails_root/Rakefile +13 -0
  75. data/examples/rails_root/app/controllers/application_controller.rb +15 -0
  76. data/examples/rails_root/app/controllers/welcome_controller.rb +16 -0
  77. data/examples/rails_root/app/helpers/application_helper.rb +3 -0
  78. data/examples/rails_root/app/helpers/welcome_helper.rb +2 -0
  79. data/examples/rails_root/app/models/user.rb +2 -0
  80. data/examples/rails_root/app/models/user_mailer.rb +39 -0
  81. data/examples/rails_root/app/views/user_mailer/attachments.erb +6 -0
  82. data/examples/rails_root/app/views/user_mailer/newsletter.erb +8 -0
  83. data/examples/rails_root/app/views/user_mailer/signup.erb +3 -0
  84. data/examples/rails_root/app/views/welcome/attachments.html.erb +0 -0
  85. data/examples/rails_root/app/views/welcome/confirm.html.erb +1 -0
  86. data/examples/rails_root/app/views/welcome/index.html.erb +8 -0
  87. data/examples/rails_root/app/views/welcome/newsletter.html.erb +0 -0
  88. data/examples/rails_root/app/views/welcome/signup.html.erb +1 -0
  89. data/examples/rails_root/attachments/document.pdf +0 -0
  90. data/examples/rails_root/attachments/image.png +0 -0
  91. data/examples/rails_root/config/boot.rb +110 -0
  92. data/examples/rails_root/config/cucumber.yml +7 -0
  93. data/examples/rails_root/config/database.yml +25 -0
  94. data/examples/rails_root/config/environment.rb +28 -0
  95. data/examples/rails_root/config/environments/cucumber.rb +29 -0
  96. data/examples/rails_root/config/environments/development.rb +17 -0
  97. data/examples/rails_root/config/environments/production.rb +28 -0
  98. data/examples/rails_root/config/environments/test.rb +28 -0
  99. data/examples/rails_root/config/initializers/inflections.rb +10 -0
  100. data/examples/rails_root/config/initializers/mime_types.rb +5 -0
  101. data/examples/rails_root/config/initializers/new_rails_defaults.rb +17 -0
  102. data/examples/rails_root/config/routes.rb +45 -0
  103. data/examples/rails_root/db/migrate/20090125013728_create_users.rb +11 -0
  104. data/examples/rails_root/db/migrate/20090908054656_create_delayed_jobs.rb +20 -0
  105. data/examples/rails_root/db/schema.rb +32 -0
  106. data/examples/rails_root/doc/README_FOR_APP +5 -0
  107. data/examples/rails_root/features/attachments.feature +27 -0
  108. data/examples/rails_root/features/delayed_job.feature +13 -0
  109. data/examples/rails_root/features/errors.feature +32 -0
  110. data/examples/rails_root/features/example.feature +62 -0
  111. data/examples/rails_root/features/step_definitions/user_steps.rb +26 -0
  112. data/examples/rails_root/features/step_definitions/web_steps.rb +273 -0
  113. data/examples/rails_root/features/support/env.rb +58 -0
  114. data/examples/rails_root/features/support/env_ext.rb +6 -0
  115. data/examples/rails_root/features/support/paths.rb +33 -0
  116. data/examples/rails_root/lib/tasks/cucumber.rake +47 -0
  117. data/examples/rails_root/public/404.html +30 -0
  118. data/examples/rails_root/public/422.html +30 -0
  119. data/examples/rails_root/public/500.html +33 -0
  120. data/examples/rails_root/public/dispatch.rb +10 -0
  121. data/examples/rails_root/public/favicon.ico +0 -0
  122. data/examples/rails_root/public/images/rails.png +0 -0
  123. data/examples/rails_root/public/javascripts/application.js +2 -0
  124. data/examples/rails_root/public/javascripts/controls.js +963 -0
  125. data/examples/rails_root/public/javascripts/dragdrop.js +973 -0
  126. data/examples/rails_root/public/javascripts/effects.js +1128 -0
  127. data/examples/rails_root/public/javascripts/prototype.js +4320 -0
  128. data/examples/rails_root/public/robots.txt +5 -0
  129. data/examples/rails_root/script/about +4 -0
  130. data/examples/rails_root/script/autospec +5 -0
  131. data/examples/rails_root/script/console +3 -0
  132. data/examples/rails_root/script/cucumber +10 -0
  133. data/examples/rails_root/script/dbconsole +3 -0
  134. data/examples/rails_root/script/delayed_job +5 -0
  135. data/examples/rails_root/script/destroy +3 -0
  136. data/examples/rails_root/script/generate +3 -0
  137. data/examples/rails_root/script/performance/benchmarker +3 -0
  138. data/examples/rails_root/script/performance/profiler +3 -0
  139. data/examples/rails_root/script/performance/request +3 -0
  140. data/examples/rails_root/script/plugin +3 -0
  141. data/examples/rails_root/script/process/inspector +3 -0
  142. data/examples/rails_root/script/process/reaper +3 -0
  143. data/examples/rails_root/script/process/spawner +3 -0
  144. data/examples/rails_root/script/runner +3 -0
  145. data/examples/rails_root/script/server +3 -0
  146. data/examples/rails_root/script/spec +5 -0
  147. data/examples/rails_root/script/spec_server +125 -0
  148. data/examples/rails_root/spec/controllers/welcome_controller_spec.rb +15 -0
  149. data/examples/rails_root/spec/model_factory.rb +6 -0
  150. data/examples/rails_root/spec/models/user_mailer_spec.rb +58 -0
  151. data/examples/rails_root/spec/models/user_spec.rb +5 -0
  152. data/examples/rails_root/spec/rcov.opts +2 -0
  153. data/examples/rails_root/spec/spec.opts +4 -0
  154. data/examples/rails_root/spec/spec_helper.rb +51 -0
  155. data/examples/sinatra/app.rb +33 -0
  156. data/examples/sinatra/features/errors.feature +32 -0
  157. data/examples/sinatra/features/example.feature +62 -0
  158. data/examples/sinatra/features/step_definitions/user_steps.rb +26 -0
  159. data/examples/sinatra/features/step_definitions/web_steps.rb +273 -0
  160. data/examples/sinatra/features/support/env.rb +39 -0
  161. data/examples/sinatra/features/support/paths.rb +24 -0
  162. data/install.rb +0 -0
  163. data/lib/email-spec.rb +1 -0
  164. data/lib/email_spec.rb +15 -0
  165. data/lib/email_spec/address_converter.rb +29 -0
  166. data/lib/email_spec/background_processes.rb +45 -0
  167. data/lib/email_spec/cucumber.rb +26 -0
  168. data/lib/email_spec/deliveries.rb +87 -0
  169. data/lib/email_spec/email_viewer.rb +87 -0
  170. data/lib/email_spec/helpers.rb +164 -0
  171. data/lib/email_spec/matchers.rb +225 -0
  172. data/lib/generators/email_spec/steps/USAGE +5 -0
  173. data/lib/generators/email_spec/steps/steps_generator.rb +14 -0
  174. data/lib/generators/email_spec/steps/templates/email_steps.rb +194 -0
  175. data/rails_generators/email_spec/email_spec_generator.rb +17 -0
  176. data/rails_generators/email_spec/templates/email_steps.rb +182 -0
  177. data/spec/email_spec/helpers_spec.rb +21 -0
  178. data/spec/email_spec/matchers_spec.rb +430 -0
  179. data/spec/spec_helper.rb +16 -0
  180. metadata +407 -0
@@ -0,0 +1,29 @@
1
+ require 'singleton'
2
+
3
+ module EmailSpec
4
+ class AddressConverter
5
+ include Singleton
6
+
7
+ attr_accessor :converter
8
+
9
+ # The block provided to conversion should convert to an email
10
+ # address string or return the input untouched. For example:
11
+ #
12
+ # EmailSpec::AddressConverter.instance.conversion do |input|
13
+ # if input.is_a?(User)
14
+ # input.email
15
+ # else
16
+ # input
17
+ # end
18
+ # end
19
+ #
20
+ def conversion(&block)
21
+ self.converter = block
22
+ end
23
+
24
+ def convert(input)
25
+ return input unless converter
26
+ converter.call(input)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ module EmailSpec
2
+ module BackgroundProcesses
3
+ module DelayedJob
4
+ def all_emails
5
+ work_off_queue
6
+ super
7
+ end
8
+
9
+ def last_email_sent
10
+ work_off_queue
11
+ super
12
+ end
13
+
14
+ def reset_mailer
15
+ work_off_queue
16
+ super
17
+ end
18
+
19
+ def mailbox_for(address)
20
+ work_off_queue
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ # Later versions of DelayedJob switch from using Delayed::Job to Delayed::Worker
27
+ # Support both versions for those who haven't upgraded yet
28
+ def work_off_queue
29
+ if defined?(Delayed::Job)
30
+ Delayed::Job.work_off
31
+ else
32
+ Delayed::Worker.send :public, :work_off
33
+ worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
34
+ worker.work_off
35
+ end
36
+ end
37
+ end
38
+
39
+ module Compatibility
40
+ if defined?(Delayed) && (defined?(Delayed::Job) || defined?(Delayed::Worker))
41
+ include EmailSpec::BackgroundProcesses::DelayedJob
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # require this in your env.rb file after you require cucumber/rails/world
2
+
3
+ # Global Setup
4
+ if defined?(ActionMailer)
5
+ unless [:activerecord, :cache].include?(ActionMailer::Base.delivery_method)
6
+ ActionMailer::Base.delivery_method = :test
7
+ end
8
+ ActionMailer::Base.perform_deliveries = true
9
+
10
+ Before do
11
+ # Scenario setup
12
+ case ActionMailer::Base.delivery_method
13
+ when :test then ActionMailer::Base.deliveries.clear
14
+ when :cache then ActionMailer::Base.clear_cache
15
+ end
16
+ end
17
+ end
18
+
19
+ After do
20
+ EmailSpec::EmailViewer.save_and_open_all_raw_emails if ENV['SHOW_EMAILS']
21
+ EmailSpec::EmailViewer.save_and_open_all_html_emails if ENV['SHOW_HTML_EMAILS']
22
+ EmailSpec::EmailViewer.save_and_open_all_text_emails if ENV['SHOW_TEXT_EMAILS']
23
+ end
24
+
25
+ World(EmailSpec::Helpers)
26
+ World(EmailSpec::Matchers)
@@ -0,0 +1,87 @@
1
+ module EmailSpec
2
+ module MailerDeliveries
3
+ def all_emails
4
+ deliveries
5
+ end
6
+
7
+ def last_email_sent
8
+ deliveries.last || raise("No email has been sent!")
9
+ end
10
+
11
+ def reset_mailer
12
+ deliveries.clear
13
+ end
14
+
15
+ def mailbox_for(address)
16
+ deliveries.select { |email|
17
+ (email.to && email.to.include?(address)) ||
18
+ (email.bcc && email.bcc.include?(address)) ||
19
+ (email.cc && email.cc.include?(address)) }
20
+ end
21
+
22
+ protected
23
+
24
+ def deliveries
25
+ if ActionMailer::Base.delivery_method == :cache
26
+ mailer.cached_deliveries
27
+ else
28
+ mailer.deliveries
29
+ end
30
+ end
31
+ end
32
+
33
+ module ARMailerDeliveries
34
+ def all_emails
35
+ Email.all.map{ |email| parse_to_mail(email) }
36
+ end
37
+
38
+ def last_email_sent
39
+ if email = Email.last
40
+ Mail.read(email.mail)
41
+ else
42
+ raise("No email has been sent!")
43
+ end
44
+ end
45
+
46
+ def reset_mailer
47
+ Email.delete_all
48
+ end
49
+
50
+ def mailbox_for(address)
51
+ Email.all.select { |email|
52
+ (email.to && email.to.include?(address)) ||
53
+ (email.bcc && email.bcc.include?(address)) ||
54
+ (email.cc && email.cc.include?(address)) }.map{ |email| parse_to_mail(email) }
55
+ end
56
+
57
+ def parse_to_mail(email)
58
+ Mail.read(email.mail)
59
+ end
60
+ end
61
+
62
+ if defined?(Pony)
63
+ module ::Pony
64
+ def self.deliveries
65
+ @deliveries ||= []
66
+ end
67
+
68
+ def self.mail(options)
69
+ deliveries << build_mail(options)
70
+ end
71
+ end
72
+ end
73
+
74
+ module Deliveries
75
+ if defined?(Pony)
76
+ def deliveries; Pony::deliveries ; end
77
+ include EmailSpec::MailerDeliveries
78
+ elsif ActionMailer::Base.delivery_method == :activerecord
79
+ include EmailSpec::ARMailerDeliveries
80
+ else
81
+ def mailer; ActionMailer::Base; end
82
+ include EmailSpec::MailerDeliveries
83
+ end
84
+ include EmailSpec::BackgroundProcesses::Compatibility
85
+ end
86
+ end
87
+
@@ -0,0 +1,87 @@
1
+ module EmailSpec
2
+ class EmailViewer
3
+ extend Deliveries
4
+
5
+ def self.save_and_open_all_raw_emails
6
+ filename = tmp_email_filename
7
+
8
+ File.open(filename, "w") do |f|
9
+ all_emails.each do |m|
10
+ f.write m.to_s
11
+ f.write "\n" + '='*80 + "\n"
12
+ end
13
+ end
14
+
15
+ open_in_text_editor(filename)
16
+ end
17
+
18
+ def self.save_and_open_all_html_emails
19
+ all_emails.each_with_index do |m, index|
20
+ if m.multipart? && html_part = m.parts.detect{ |p| p.content_type == 'text/html' }
21
+ filename = tmp_email_filename("-#{index}.html")
22
+ File.open(filename, "w") do |f|
23
+ f.write m.parts[1].body
24
+ end
25
+ open_in_browser(filename)
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.save_and_open_all_text_emails
31
+ filename = tmp_email_filename
32
+
33
+ File.open(filename, "w") do |f|
34
+ all_emails.each do |m|
35
+ if m.multipart? && text_part = m.parts.detect{ |p| p.content_type == 'text/plain' }
36
+ m.ordered_each{|k,v| f.write "#{k}: #{v}\n" }
37
+ f.write text_part.body
38
+ else
39
+ f.write m.to_s
40
+ end
41
+ f.write "\n" + '='*80 + "\n"
42
+ end
43
+ end
44
+
45
+ open_in_text_editor(filename)
46
+ end
47
+
48
+ def self.save_and_open_email(mail)
49
+ filename = tmp_email_filename
50
+
51
+ File.open(filename, "w") do |f|
52
+ f.write mail.to_s
53
+ end
54
+
55
+ open_in_text_editor(filename)
56
+ end
57
+
58
+ def self.save_and_open_email_attachments_list(mail)
59
+ filename = tmp_email_filename
60
+
61
+ File.open(filename, "w") do |f|
62
+ mail.attachments.each_with_index do |attachment, index|
63
+ info = "#{index + 1}:"
64
+ info += "\n\tfilename: #{attachment.original_filename}"
65
+ info += "\n\tcontent type: #{attachment.content_type}"
66
+ info += "\n\tsize: #{attachment.size}"
67
+ f.write info + "\n"
68
+ end
69
+ end
70
+
71
+ open_in_text_editor(filename)
72
+ end
73
+
74
+ # TODO: use the launchy gem for this stuff...
75
+ def self.open_in_text_editor(filename)
76
+ `open #{filename}`
77
+ end
78
+
79
+ def self.open_in_browser(filename)
80
+ `open #{filename}`
81
+ end
82
+
83
+ def self.tmp_email_filename(extension = '.txt')
84
+ "#{RAILS_ROOT}/tmp/email-#{Time.now.to_i}#{extension}"
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,164 @@
1
+ require 'uri'
2
+ require 'email_spec/deliveries'
3
+
4
+ module EmailSpec
5
+
6
+ module Helpers
7
+ include Deliveries
8
+
9
+ def visit_in_email(link_text)
10
+ visit(parse_email_for_link(current_email, link_text))
11
+ end
12
+
13
+ def click_email_link_matching(regex, email = current_email)
14
+ url = links_in_email(email).detect { |link| link =~ regex }
15
+ raise "No link found matching #{regex.inspect} in #{email.default_part_body}" unless url
16
+ request_uri = URI::parse(url).request_uri
17
+ visit request_uri
18
+ end
19
+
20
+ def click_first_link_in_email(email = current_email)
21
+ link = links_in_email(email).first
22
+ request_uri = URI::parse(link).request_uri
23
+ visit request_uri
24
+ end
25
+
26
+ def open_email(address, opts={})
27
+ set_current_email(find_email!(address, opts))
28
+ end
29
+
30
+ alias_method :open_email_for, :open_email
31
+
32
+ def open_last_email
33
+ set_current_email(last_email_sent)
34
+ end
35
+
36
+ def open_last_email_for(address)
37
+ set_current_email(mailbox_for(address).last)
38
+ end
39
+
40
+ def current_email(address=nil)
41
+ email = if address.nil? && email_spec_hash[:current_email]
42
+ email_spec_hash[:current_email]
43
+ else
44
+ address = convert_address(address)
45
+ email_spec_hash[:current_emails][address]
46
+ end
47
+ raise RSpec::Expectations::ExpectationNotMetError, "Expected an open email but none was found. Did you forget to call open_email?" unless email
48
+ email
49
+ end
50
+
51
+ def current_email_attachments(address=nil)
52
+ current_email(address).attachments || Array.new
53
+ end
54
+
55
+ def unread_emails_for(address)
56
+ mailbox_for(address) - read_emails_for(address)
57
+ end
58
+
59
+ def read_emails_for(address)
60
+ email_spec_hash[:read_emails][convert_address(address)] ||= []
61
+ end
62
+
63
+ def find_email(address, opts={})
64
+ address = convert_address(address)
65
+ if opts[:with_subject]
66
+ mailbox_for(address).find { |m| m.subject =~ Regexp.new(opts[:with_subject]) }
67
+ elsif opts[:with_text]
68
+ mailbox_for(address).find { |m| m.default_part_body =~ Regexp.new(opts[:with_text]) }
69
+ else
70
+ mailbox_for(address).first
71
+ end
72
+ end
73
+
74
+ def links_in_email(email)
75
+ URI.extract(email.default_part_body.to_s, ['http', 'https'])
76
+ end
77
+
78
+ private
79
+
80
+ def email_spec_hash
81
+ @email_spec_hash ||= {:read_emails => {}, :unread_emails => {}, :current_emails => {}, :current_email => nil}
82
+ end
83
+
84
+ def find_email!(address, opts={})
85
+ email = find_email(address, opts)
86
+ if email.nil?
87
+ error = "#{opts.keys.first.to_s.humanize unless opts.empty?} #{('"' + opts.values.first.to_s.humanize + '"') unless opts.empty?}"
88
+ raise RSpec::Expectations::ExpectationNotMetError, "Could not find email #{error}. \n Found the following emails:\n\n #{all_emails.to_s}"
89
+ end
90
+ email
91
+ end
92
+
93
+ def set_current_email(email)
94
+ return unless email
95
+ email.to.each do |to|
96
+ read_emails_for(to) << email
97
+ email_spec_hash[:current_emails][to] = email
98
+ end
99
+ email_spec_hash[:current_email] = email
100
+ end
101
+
102
+ def parse_email_for_link(email, text_or_regex)
103
+ email.should have_body_text(text_or_regex)
104
+
105
+ url = parse_email_for_explicit_link(email, text_or_regex)
106
+ url ||= parse_email_for_anchor_text_link(email, text_or_regex)
107
+
108
+ raise "No link found matching #{text_or_regex.inspect} in #{email}" unless url
109
+ url
110
+ end
111
+
112
+ # e.g. confirm in http://confirm
113
+ def parse_email_for_explicit_link(email, regex)
114
+ regex = /#{Regexp.escape(regex)}/ unless regex.is_a?(Regexp)
115
+ url = links_in_email(email).detect { |link| link =~ regex }
116
+ URI::parse(url).request_uri if url
117
+ end
118
+
119
+ # e.g. Click here in <a href="http://confirm">Click here</a>
120
+ def parse_email_for_anchor_text_link(email, link_text)
121
+ if textify_images(email.default_part_body) =~ %r{<a[^>]*href=['"]?([^'"]*)['"]?[^>]*?>[^<]*?#{link_text}[^<]*?</a>}
122
+ URI.split($1)[5..-1].compact!.join("?").gsub("&amp;", "&")
123
+ # sub correct ampersand after rails switches it (http://dev.rubyonrails.org/ticket/4002)
124
+ else
125
+ return nil
126
+ end
127
+ end
128
+
129
+ def textify_images(email_body)
130
+ email_body.to_s.gsub(%r{<img[^>]*alt=['"]?([^'"]*)['"]?[^>]*?/>}) { $1 }
131
+ end
132
+
133
+ def parse_email_count(amount)
134
+ case amount
135
+ when "no"
136
+ 0
137
+ when "an"
138
+ 1
139
+ else
140
+ amount.to_i
141
+ end
142
+ end
143
+
144
+ attr_reader :last_email_address
145
+
146
+ def convert_address(address)
147
+ @last_email_address = (address || current_email_address)
148
+ AddressConverter.instance.convert(@last_email_address)
149
+ end
150
+
151
+
152
+ def mailbox_for(address)
153
+ super(convert_address(address)) # super resides in Deliveries
154
+ end
155
+
156
+ def email_spec_deprecate(text)
157
+ puts ""
158
+ puts "DEPRECATION: #{text.split.join(' ')}"
159
+ puts ""
160
+ end
161
+
162
+ end
163
+ end
164
+
@@ -0,0 +1,225 @@
1
+ module EmailSpec
2
+ module Matchers
3
+ class ReplyTo
4
+ def initialize(email)
5
+ @expected_reply_to = Mail::ReplyToField.new(email).addrs.first
6
+ end
7
+
8
+ def description
9
+ "have reply to as #{@expected_reply_to.address}"
10
+ end
11
+
12
+ def matches?(email)
13
+ @email = email
14
+ @actual_reply_to = (email.reply_to || []).first
15
+ !@actual_reply_to.nil? &&
16
+ @actual_reply_to == @expected_reply_to.address
17
+ end
18
+
19
+ def failure_message
20
+ "expected #{@email.inspect} to reply to #{@expected_reply_to.address.inspect}, but it replied to #{@actual_reply_to.inspect}"
21
+ end
22
+
23
+ def negative_failure_message
24
+ "expected #{@email.inspect} not to deliver to #{@expected_reply_to.address.inspect}, but it did"
25
+ end
26
+ end
27
+
28
+ def reply_to(email)
29
+ ReplyTo.new(email)
30
+ end
31
+
32
+ alias :have_reply_to :reply_to
33
+
34
+ class DeliverTo
35
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
36
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
37
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
38
+ end
39
+
40
+ @expected_email_addresses = emails.sort
41
+ end
42
+
43
+ def description
44
+ "be delivered to #{@expected_email_addresses.inspect}"
45
+ end
46
+
47
+ def matches?(email)
48
+ @email = email
49
+ @actual_recipients = (Array(email.to) || []).sort
50
+ @actual_recipients == @expected_email_addresses
51
+ end
52
+
53
+ def failure_message
54
+ "expected #{@email.inspect} to deliver to #{@expected_email_addresses.inspect}, but it delivered to #{@actual_recipients.inspect}"
55
+ end
56
+
57
+ def negative_failure_message
58
+ "expected #{@email.inspect} not to deliver to #{@expected_email_addresses.inspect}, but it did"
59
+ end
60
+ end
61
+
62
+ def deliver_to(*expected_email_addresses_or_objects_that_respond_to_email)
63
+ DeliverTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
64
+ end
65
+
66
+ alias :be_delivered_to :deliver_to
67
+
68
+ class DeliverFrom
69
+
70
+ def initialize(email)
71
+ @expected_sender = Mail::FromField.new(email).addrs.first
72
+ end
73
+
74
+ def description
75
+ "be delivered from #{@expected_sender}"
76
+ end
77
+
78
+ def matches?(email)
79
+ @email = email
80
+ @actual_sender = (email.header[:from].addrs || []).first
81
+
82
+ !@actual_sender.nil? &&
83
+ @actual_sender.address == @expected_sender.address &&
84
+ @actual_sender.display_name == @expected_sender.display_name
85
+ end
86
+
87
+ def failure_message
88
+ %(expected #{@email.inspect} to deliver from "#{@expected_sender.to_s}", but it delivered from "#{@actual_sender.to_s}")
89
+ end
90
+
91
+ def negative_failure_message
92
+ %(expected #{@email.inspect} not to deliver from "#{@expected_sender.to_s}", but it did)
93
+ end
94
+ end
95
+
96
+ def deliver_from(email)
97
+ DeliverFrom.new(email)
98
+ end
99
+
100
+ alias :be_delivered_from :deliver_from
101
+
102
+ class BccTo
103
+
104
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
105
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
106
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
107
+ end
108
+
109
+ @expected_email_addresses = emails.sort
110
+ end
111
+
112
+ def description
113
+ "be bcc'd to #{@expected_email_addresses.inspect}"
114
+ end
115
+
116
+ def matches?(email)
117
+ @email = email
118
+ @actual_recipients = (Array(email.bcc) || []).sort
119
+ @actual_recipients == @expected_email_addresses
120
+ end
121
+
122
+ def failure_message
123
+ "expected #{@email.inspect} to bcc to #{@expected_email_addresses.inspect}, but it was bcc'd to #{@actual_recipients.inspect}"
124
+ end
125
+
126
+ def negative_failure_message
127
+ "expected #{@email.inspect} not to bcc to #{@expected_email_addresses.inspect}, but it did"
128
+ end
129
+ end
130
+
131
+ def bcc_to(*expected_email_addresses_or_objects_that_respond_to_email)
132
+ BccTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
133
+ end
134
+
135
+ RSpec::Matchers.define :have_subject do
136
+ match do |given|
137
+ given_subject = given.subject
138
+ expected_subject = expected.first
139
+
140
+ if expected_subject.is_a?(String)
141
+ description { "have subject of #{expected_subject.inspect}" }
142
+ failure_message_for_should { "expected the subject to be #{expected_subject.inspect} but was #{given_subject.inspect}" }
143
+ failure_message_for_should_not { "expected the subject not to be #{expected_subject.inspect} but was" }
144
+
145
+ given_subject == expected_subject
146
+ else
147
+ description { "have subject matching #{expected_subject.inspect}" }
148
+ failure_message_for_should { "expected the subject to match #{expected_subject.inspect}, but did not. Actual subject was: #{given_subject.inspect}" }
149
+ failure_message_for_should_not { "expected the subject not to match #{expected_subject.inspect} but #{given_subject.inspect} does match it." }
150
+
151
+ !!(given_subject =~ expected_subject)
152
+ end
153
+ end
154
+ end
155
+
156
+ RSpec::Matchers.define :include_email_with_subject do
157
+ match do |given_emails|
158
+ expected_subject = expected.first
159
+
160
+ if expected_subject.is_a?(String)
161
+ description { "include email with subject of #{expected_subject.inspect}" }
162
+ failure_message_for_should { "expected at least one email to have the subject #{expected_subject.inspect} but none did. Subjects were #{given_emails.map(&:subject).inspect}" }
163
+ failure_message_for_should_not { "expected no email with the subject #{expected_subject.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}" }
164
+
165
+ given_emails.map(&:subject).include?(expected_subject)
166
+ else
167
+ description { "include email with subject matching #{expected_subject.inspect}" }
168
+ failure_message_for_should { "expected at least one email to have a subject matching #{expected_subject.inspect}, but none did. Subjects were #{given_emails.map(&:subject).inspect}" }
169
+ failure_message_for_should_not { "expected no email to have a subject matching #{expected_subject.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}" }
170
+
171
+ !!(given_emails.any?{ |mail| mail.subject =~ expected_subject })
172
+ end
173
+ end
174
+ end
175
+
176
+ RSpec::Matchers.define :have_body_text do
177
+ match do |given|
178
+ expected_text = expected.first
179
+
180
+ if expected_text.is_a?(String)
181
+ normalized_body = given.default_part_body.to_s.gsub(/\s+/, " ")
182
+ normalized_expected = expected_text.gsub(/\s+/, " ")
183
+ description { "have body including #{normalized_expected.inspect}" }
184
+ failure_message_for_should { "expected the body to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}" }
185
+ failure_message_for_should_not { "expected the body not to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}" }
186
+
187
+ normalized_body.include?(normalized_expected)
188
+ else
189
+ given_body = given.default_part_body.to_s
190
+ description { "have body matching #{expected_text.inspect}" }
191
+ failure_message_for_should { "expected the body to match #{expected_text.inspect}, but did not. Actual body was: #{given_body.inspect}" }
192
+ failure_message_for_should_not { "expected the body not to match #{expected_text.inspect} but #{given_body.inspect} does match it." }
193
+
194
+ !!(given_body =~ expected_text)
195
+ end
196
+ end
197
+ end
198
+
199
+ def mail_headers_hash(email_headers)
200
+ email_headers.fields.inject({}) { |hash, field| hash[field.field.class::FIELD_NAME] = field.to_s; hash }
201
+ end
202
+
203
+ RSpec::Matchers.define :have_header do
204
+ match do |given|
205
+ given_header = given.header
206
+ expected_name, expected_value = *expected
207
+
208
+ if expected_value.is_a?(String)
209
+ description { "have header #{expected_name}: #{expected_value}" }
210
+
211
+ failure_message_for_should { "expected the headers to include '#{expected_name}: #{expected_value}' but they were #{mail_headers_hash(given_header).inspect}" }
212
+ failure_message_for_should_not { "expected the headers not to include '#{expected_name}: #{expected_value}' but they were #{mail_headers_hash(given_header).inspect}" }
213
+
214
+ given_header[expected_name].to_s == expected_value
215
+ else
216
+ description { "have header #{expected_name} with value matching #{expected_value.inspect}" }
217
+ failure_message_for_should { "expected the headers to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{mail_headers_hash(given_header).inspect}" }
218
+ failure_message_for_should_not { "expected the headers not to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{mail_headers_hash(given_header).inspect}" }
219
+
220
+ given_header[expected_name].to_s =~ expected_value
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end