hermes 0.2.1 → 0.3.0

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 (61) hide show
  1. data/README.rdoc +4 -0
  2. data/lib/hermes.rb +7 -1
  3. data/lib/hermes/actions.rb +81 -0
  4. data/lib/hermes/assertions.rb +34 -29
  5. data/lib/hermes/builders.rb +46 -21
  6. data/lib/hermes/context.rb +0 -2
  7. data/lib/hermes/integration_case.rb +7 -3
  8. data/lib/hermes/rails.rb +4 -0
  9. data/lib/hermes/scopes.rb +32 -0
  10. data/lib/hermes/version.rb +1 -1
  11. data/test/builders.rb +13 -0
  12. data/test/fixtures/users.yml +4 -0
  13. data/test/hermes/actions_test.rb +118 -0
  14. data/test/hermes/assertions_test.rb +493 -0
  15. data/test/hermes/builders_test.rb +109 -0
  16. data/test/hermes/context_test.rb +5 -0
  17. data/test/hermes/scopes_test.rb +47 -0
  18. data/test/rails_app/Rakefile +7 -0
  19. data/test/rails_app/app/controllers/application_controller.rb +3 -0
  20. data/test/rails_app/app/controllers/users_controller.rb +23 -0
  21. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  22. data/test/rails_app/app/models/user.rb +11 -0
  23. data/test/rails_app/app/views/layouts/application.html.erb +14 -0
  24. data/test/rails_app/app/views/users/index.html.erb +10 -0
  25. data/test/rails_app/app/views/users/new.html.erb +62 -0
  26. data/test/rails_app/config.ru +4 -0
  27. data/test/rails_app/config/application.rb +42 -0
  28. data/test/rails_app/config/boot.rb +10 -0
  29. data/test/rails_app/config/database.yml +20 -0
  30. data/test/rails_app/config/environment.rb +5 -0
  31. data/test/rails_app/config/environments/development.rb +26 -0
  32. data/test/rails_app/config/environments/production.rb +49 -0
  33. data/test/rails_app/config/environments/test.rb +35 -0
  34. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  35. data/test/rails_app/config/initializers/inflections.rb +10 -0
  36. data/test/rails_app/config/initializers/mime_types.rb +5 -0
  37. data/test/rails_app/config/initializers/secret_token.rb +7 -0
  38. data/test/rails_app/config/initializers/session_store.rb +8 -0
  39. data/test/rails_app/config/locales/en.yml +5 -0
  40. data/test/rails_app/config/routes.rb +7 -0
  41. data/test/rails_app/db/migrate/20110228170406_create_users.rb +15 -0
  42. data/test/rails_app/db/schema.rb +21 -0
  43. data/test/rails_app/log/development.log +29 -0
  44. data/test/rails_app/log/production.log +0 -0
  45. data/test/rails_app/log/server.log +0 -0
  46. data/test/rails_app/log/test.log +1519 -0
  47. data/test/rails_app/public/404.html +26 -0
  48. data/test/rails_app/public/422.html +26 -0
  49. data/test/rails_app/public/500.html +26 -0
  50. data/test/rails_app/public/favicon.ico +0 -0
  51. data/test/rails_app/public/javascripts/application.js +2 -0
  52. data/test/rails_app/public/javascripts/controls.js +965 -0
  53. data/test/rails_app/public/javascripts/dragdrop.js +974 -0
  54. data/test/rails_app/public/javascripts/effects.js +1123 -0
  55. data/test/rails_app/public/javascripts/prototype.js +6001 -0
  56. data/test/rails_app/public/javascripts/rails.js +191 -0
  57. data/test/rails_app/script/rails +6 -0
  58. data/test/support/assertions.rb +23 -0
  59. data/test/support/models.rb +11 -0
  60. data/test/test_helper.rb +29 -3
  61. metadata +108 -7
@@ -2,6 +2,10 @@
2
2
 
3
3
  A few testing facilities built on top of Capybara and ActiveSupport::TestCase
4
4
 
5
+ == TODO
6
+
7
+ * Improve this README mentioning each module and how to configure it to work with Rspec and/or Rails.
8
+
5
9
  == Bugs and Features
6
10
 
7
11
  If you discover any bugs or add any features, please let us know using GitHub's tracker.
@@ -1,2 +1,8 @@
1
1
  module Hermes
2
- end
2
+ autoload :Context, 'hermes/context'
3
+ autoload :IntegrationCase, 'hermes/integration_case'
4
+ autoload :Actions, 'hermes/actions'
5
+ autoload :Builders, 'hermes/builders'
6
+ autoload :Scopes, 'hermes/scopes'
7
+ autoload :Assertions, 'hermes/assertions'
8
+ end
@@ -0,0 +1,81 @@
1
+ module Hermes
2
+ # A few actions built on top of Capybara.
3
+ module Actions
4
+
5
+ POSTFIXES = {
6
+ :year => '1i',
7
+ :month => '2i',
8
+ :day => '3i',
9
+ :hour => '4i',
10
+ :minute => '5i'
11
+ }.freeze
12
+
13
+ def select_datetime(datetime, options={})
14
+ select_prefix = select_prefix_from_options(options)
15
+
16
+ select_time(datetime, :id_prefix => select_prefix)
17
+ select_date(datetime, :id_prefix => select_prefix)
18
+ end
19
+
20
+ def select_time(time, options={})
21
+ select_prefix = select_prefix_from_options(options)
22
+
23
+ find_and_select_option(select_prefix,
24
+ POSTFIXES[:hour],
25
+ string_with_double_digits(time.hour))
26
+
27
+ find_and_select_option(select_prefix,
28
+ POSTFIXES[:minute],
29
+ string_with_double_digits(time.min))
30
+ end
31
+
32
+ def select_date(date, options={})
33
+ select_prefix = select_prefix_from_options(options)
34
+
35
+ find_and_select_option(select_prefix,
36
+ POSTFIXES[:year],
37
+ date.year)
38
+
39
+ find_and_select_option(select_prefix,
40
+ POSTFIXES[:month],
41
+ date.month)
42
+
43
+ find_and_select_option(select_prefix,
44
+ POSTFIXES[:day],
45
+ string_with_double_digits(date.day))
46
+ end
47
+
48
+ private
49
+
50
+ def find_and_select_option(select_prefix, postfix, value)
51
+ no_select_msg = "cannot select option, no select box with id prefix '#{select_prefix}' found"
52
+ no_option_msg = "cannot select option, no option with text '#{value}' in select box '#{select_prefix}'"
53
+
54
+ select = find(:css, "##{select_prefix}_#{postfix}", :message => no_select_msg)
55
+ select.find(:xpath, ".//option[contains(./@value, '#{value}')]", :message => no_option_msg).select_option
56
+ end
57
+
58
+ def string_with_double_digits(number)
59
+ "%.2d" % number
60
+ end
61
+
62
+ def select_prefix_from_options(options)
63
+ if options.slice(:id_prefix, :from).values.blank?
64
+ raise(ArgumentError, 'You must supply either :from or :id_prefix option')
65
+ end
66
+
67
+ select_prefix = options[:id_prefix]
68
+ select_prefix ||= find_prefix_by_label(options[:from])
69
+
70
+ select_prefix
71
+ end
72
+
73
+ def find_prefix_by_label(label)
74
+ message = "cannot select option, select with label '#{label}' not found"
75
+ find(:xpath,
76
+ "//label[contains(normalize-space(string(.)), '#{label}')]/@for",
77
+ :message => message
78
+ ).text
79
+ end
80
+ end
81
+ end
@@ -1,25 +1,37 @@
1
1
  module Hermes
2
2
  # A few assertions built on top of Capybara.
3
3
  module Assertions
4
+
5
+ METHODS = {
6
+ 'xpath' => 'no_xpath',
7
+ 'css' => 'no_css',
8
+ 'link' => 'no_link',
9
+ 'select' => 'no_select',
10
+ 'field' => 'no_field',
11
+ 'button' => 'no_button',
12
+ 'checked_field' => 'unchecked_field',
13
+ 'selector' => 'no_selector',
14
+ 'table' => 'no_table'
15
+ }.freeze
16
+
4
17
  def assert_current_path(expected)
5
18
  assert_equal expected, current_path
6
19
  end
7
20
 
8
21
  def assert_difference_on_click_button(button, *args)
9
- assert_difference *args do
22
+ assert_difference(*args) do
10
23
  click_button button
11
24
  end
12
25
  end
13
26
 
14
27
  def assert_difference_on_click_link(link, *args)
15
- assert_difference *args do
28
+ assert_difference(*args) do
16
29
  click_link link
17
30
  end
18
31
  end
19
32
 
20
33
  def assert_content(content)
21
- boolean = has_content?(content)
22
- if boolean
34
+ if has_content?(content)
23
35
  assert_block("assert_content") { true }
24
36
  else
25
37
  assert_block("#{body_inner_text}\nExpected to have #{content}"){ false }
@@ -29,29 +41,29 @@ module Hermes
29
41
  def assert_no_content(content)
30
42
  boolean = has_content?(content)
31
43
  if boolean
32
- assert_block("#{body_inner_text}\nDid not expect to have content #{content}"){ false }
44
+ assert_block("#{body_inner_text}\nExpected to have no content #{content}"){ false }
33
45
  else
34
46
  assert_block("assert_no_content") { true }
35
47
  end
36
48
  end
37
49
 
38
- %w(xpath css link select field button checked_field unchecked_field selector table).each do |method|
50
+ METHODS.each do |inclusion_method, rejection_method|
39
51
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
40
- def assert_#{method}(*args, &block)
41
- boolean = has_#{method}?(*args, &block)
42
- if boolean
43
- assert_block("assert_#{method}") { true }
52
+ def assert_#{inclusion_method}(*args, &block)
53
+ match = has_#{inclusion_method}?(*args, &block)
54
+ if match
55
+ assert_block("assert_#{inclusion_method}") { true }
44
56
  else
45
- assert_block("\#{page.body}\nExpected to have #{method} \#{args.inspect}"){ false }
57
+ assert_block("\#{page.body}\nExpected to have #{inclusion_method.gsub('_', ' ')} \#{args.inspect}"){ false }
46
58
  end
47
59
  end
48
60
 
49
- def assert_no_#{method}(*args, &block)
50
- boolean = has_#{method}?(*args, &block)
51
- if boolean
52
- assert_block("\#{page.body}\nDid not expect to have #{method} \#{args.inspect}"){ false }
61
+ def assert_#{rejection_method}(*args, &block)
62
+ match = has_#{rejection_method}?(*args, &block)
63
+ if match
64
+ assert_block("assert_#{rejection_method}") { true }
53
65
  else
54
- assert_block("assert_no_#{method}") { true }
66
+ assert_block("\#{page.body}\Expected to have #{rejection_method.gsub('_', ' ')} \#{args.inspect}"){ false }
55
67
  end
56
68
  end
57
69
  RUBY
@@ -61,13 +73,11 @@ module Hermes
61
73
  assert_difference("ActionMailer::Base.deliveries.size", many){ yield }
62
74
  end
63
75
 
64
- def assert_link_to(url, options)
65
- assert_css "a[href='%s']" % url, options
66
- end
67
-
68
- def within(scope, prefix=nil)
69
- scope = '#' << ActionController::RecordIdentifier.dom_id(scope, prefix) if scope.is_a?(ActiveRecord::Base)
70
- super(scope)
76
+ def assert_link_to(url, options={})
77
+ msg = "#{page.body}\nExpected to have link to #{url}"
78
+ assert_block(msg) do
79
+ has_css? "a[href*='%s']" % url, options
80
+ end
71
81
  end
72
82
 
73
83
  protected
@@ -75,10 +85,5 @@ module Hermes
75
85
  def body_inner_text
76
86
  Nokogiri::HTML(page.body).inner_text.gsub(/^\s*$/, "").squeeze("\n")
77
87
  end
78
-
79
- # Provide a shortcut to show body inner text. It helps debugging.
80
- def pbit
81
- puts body_inner_text
82
- end
83
88
  end
84
- end
89
+ end
@@ -1,6 +1,6 @@
1
1
  # Rails developers have had bad experience with fixtures since a long time
2
2
  # due to several reasons, including misuse.
3
- #
3
+ #
4
4
  # This misuse of fixtures is often characterized by a huge ammount of
5
5
  # fixtures, causing a lot of data to maintain and dependence between tests.
6
6
  # In my experience working (and rescueing) different applications, 80% of
@@ -11,7 +11,7 @@
11
11
  # we need a huge amount of data in this test which we usually don't need in
12
12
  # order tests.
13
13
  #
14
- # For such scenarios, factories are a fine solution. They won't cluster
14
+ # For such scenarios, factories are a fine solution. They won't clutter
15
15
  # all your database since they are created for these specific tests and they
16
16
  # are also easier to maintain.
17
17
  #
@@ -69,7 +69,7 @@
69
69
  # a DSL on top of it, it allows us to use Ruby in case we need to do
70
70
  # something more complex, like supporting sequences.
71
71
  #
72
- # module Builders
72
+ # module Hermes::Builders
73
73
  # @@sequence = 0
74
74
  #
75
75
  # def sequence
@@ -79,40 +79,67 @@
79
79
  #
80
80
  module Hermes
81
81
  module Builders
82
+ ATTRIBUTES_REGEX = /valid_(.*?)_attributes$/
83
+ BUILDER_REGEX = /(create|new)_(.*?)(!)?$/
84
+
82
85
  @@builders = ActiveSupport::OrderedHash.new
83
86
 
84
- def self.build(name, options={}, &block)
85
- klass = options[:as] || name.to_s.classify.constantize
87
+ class << self
86
88
 
87
- builder = if options[:like]
88
- lambda { send(name.to_s.pluralize, options[:like]).attributes.merge(block.call) }
89
- else
90
- block
89
+ def build(name, options={}, &block)
90
+ klass = options[:class] || name.to_s.classify.constantize
91
+
92
+ builder = if options[:like]
93
+ load_attributes_from_fixture(name.to_s.pluralize, options[:like], block)
94
+ else
95
+ block
96
+ end
97
+
98
+ @@builders[name] = [klass, builder]
91
99
  end
92
100
 
93
- @@builders[name] = [klass, builder]
101
+ def retrieve(scope, name, method, options)
102
+ if builder = @@builders[name.to_sym]
103
+ klass, block = builder
104
+
105
+ hash = block ? block.bind(scope).call : {}
106
+ hash.symbolize_keys!
107
+ hash.merge!(options || {})
108
+ hash.delete(:id)
109
+
110
+ [klass, hash]
111
+ else
112
+ raise NoMethodError, "No builder #{name.inspect} for `#{method}'"
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def load_attributes_from_fixture(fixture_type, fixture_name, block)
119
+ lambda { send(fixture_type, fixture_name).attributes.symbolize_keys.merge!(block ? block.call : {}) }
120
+ end
94
121
  end
95
122
 
96
- def self.retrieve(scope, name, method, options)
97
- if builder = @@builders[name.to_sym]
98
- klass, block = builder
99
- hash = block.bind(scope).call.merge(options || {})
100
- hash.delete("id")
101
- [klass, hash]
123
+ def respond_to?(method)
124
+ case method.to_s
125
+ when BUILDER_REGEX
126
+ true
127
+ when ATTRIBUTES_REGEX
128
+ true
102
129
  else
103
- raise NoMethodError, "No builder #{name.inspect} for `#{method}'"
130
+ super
104
131
  end
105
132
  end
106
133
 
107
134
  def method_missing(method, *args, &block)
108
135
  case method.to_s
109
- when /(create|new)_(.*?)(!)?$/
136
+ when BUILDER_REGEX
110
137
  klass, hash = Builders.retrieve(self, $2, method, args.first)
111
138
  object = klass.new
112
139
  object.send("attributes=", hash, false)
113
140
  object.send("save#{$3}") if $1 == "create"
114
141
  object
115
- when /valid_(.*?)_attributes$/
142
+ when ATTRIBUTES_REGEX
116
143
  Builders.retrieve(self, $1, method, args.first)[1]
117
144
  else
118
145
  super
@@ -120,5 +147,3 @@ module Hermes
120
147
  end
121
148
  end
122
149
  end
123
-
124
- ActiveSupport::TestCase.send :include, Hermes::Builders
@@ -33,5 +33,3 @@ module Hermes
33
33
  end
34
34
  end
35
35
  end
36
-
37
- ActiveSupport::TestCase.extend Hermes::Context
@@ -1,11 +1,15 @@
1
- require 'hermes/assertions'
2
-
3
1
  class Hermes::IntegrationCase < ActiveSupport::TestCase
4
2
  include Capybara
5
3
  include ActionController::RecordIdentifier
6
4
  include Rails.application.routes.url_helpers
5
+
6
+ extend Hermes::Context
7
+
7
8
  include Hermes::Assertions
9
+ include Hermes::Actions
10
+ include Hermes::Scopes
11
+ include Hermes::Builders
8
12
 
9
13
  teardown { Capybara.reset_sessions! }
10
14
  class << self; alias :scenario :test; end
11
- end
15
+ end
@@ -0,0 +1,4 @@
1
+ ActiveSupport::TestCase.class_eval do
2
+ include Hermes::Builders
3
+ extend Hermes::Context
4
+ end
@@ -0,0 +1,32 @@
1
+ module Hermes
2
+ module Scopes
3
+ def within(*args)
4
+ object = args[0]
5
+ prefix = args[1]
6
+
7
+ scope = if object.is_a?(ActiveRecord::Base)
8
+ ['#' + ActionController::RecordIdentifier.dom_id(object, prefix)]
9
+ else
10
+ args
11
+ end
12
+ super(*scope)
13
+ end
14
+
15
+ def click_link_within(scope, link)
16
+ within(scope) { click_link(link) }
17
+ end
18
+
19
+ %w(check uncheck).each do |method|
20
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
21
+ def #{method}(*args)
22
+ if args[0].is_a?(ActiveRecord::Base)
23
+ scope = ActionController::RecordIdentifier.dom_id(args[0], args[1])
24
+ super(scope)
25
+ else
26
+ super
27
+ end
28
+ end
29
+ RUBY
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Hermes
2
- VERSION = "0.2.1".freeze
2
+ VERSION = "0.3.0".freeze
3
3
  end
@@ -0,0 +1,13 @@
1
+
2
+ module Hermes::Builders
3
+ @@sequence = 0
4
+ build :user, :like => :the_user do
5
+ {:name => "The Super User", :counter => (@@sequence += 1) }
6
+ end
7
+
8
+ build :another_user, :class => User do
9
+ {:name => 'Another user', :active_at => Time.parse("Thu, 03 Mar 2011 18:11:26 UTC +00:00") }
10
+ end
11
+
12
+ build :blockless_user, :class => User
13
+ end
@@ -0,0 +1,4 @@
1
+ the_user:
2
+ name: The User
3
+ active_at: Thu, 03 Mar 2011 18:11:26 UTC +00:00
4
+
@@ -0,0 +1,118 @@
1
+ require 'test_helper'
2
+
3
+ class ActionsTest < Hermes::IntegrationCase
4
+
5
+ test "select date and time should raise error when id_prefix is not supplied" do
6
+ visit '/users/new'
7
+ assert_raise ArgumentError do
8
+ select_datetime(Time.current, {})
9
+ end
10
+ end
11
+
12
+ test "select date and time using id_prefix" do
13
+ date = DateTime.new(2011, 2, 28, 16, 0)
14
+ visit '/users/new?datetime=true'
15
+
16
+ within '#datetime' do
17
+ select_datetime(date, :id_prefix => 'user_active_at')
18
+
19
+ assert find_selected_option('user[active_at(1i)]', '2011')
20
+ assert find_selected_option('user[active_at(2i)]', '2')
21
+ assert find_selected_option('user[active_at(3i)]', '28')
22
+ assert find_selected_option('user[active_at(4i)]', '16')
23
+ assert find_selected_option('user[active_at(5i)]', '00')
24
+ end
25
+ end
26
+
27
+ test "select date and time using from as label" do
28
+ date = DateTime.new(2011, 2, 28, 16, 0)
29
+ visit '/users/new?datetime=true'
30
+
31
+ within '#datetime' do
32
+ select_datetime(date, :from => 'Active at')
33
+
34
+ assert find_selected_option('user[active_at(1i)]', '2011')
35
+ assert find_selected_option('user[active_at(2i)]', '2')
36
+ assert find_selected_option('user[active_at(3i)]', '28')
37
+ assert find_selected_option('user[active_at(4i)]', '16')
38
+ assert find_selected_option('user[active_at(5i)]', '00')
39
+ end
40
+ end
41
+
42
+ test "select time using id_prefix" do
43
+ date = DateTime.new(2011, 2, 28, 16, 0)
44
+
45
+ visit '/users/new?time=true'
46
+
47
+ within '#time' do
48
+ select_time(date, :id_prefix => 'user_active_at')
49
+
50
+ assert find_selected_option('user[active_at(4i)]', '16')
51
+ assert find_selected_option('user[active_at(5i)]', '00')
52
+ end
53
+ end
54
+
55
+ test "select time using label" do
56
+ date = DateTime.new(2011, 2, 28, 16, 0)
57
+
58
+ visit '/users/new?time=true'
59
+
60
+ within '#time' do
61
+ select_time(date, :from => 'Active at')
62
+
63
+ assert find_selected_option('user[active_at(4i)]', '16')
64
+ assert find_selected_option('user[active_at(5i)]', '00')
65
+ end
66
+ end
67
+
68
+ test "select date using id_prefix" do
69
+ date = DateTime.new(2011, 2, 28, 16, 0)
70
+
71
+ visit '/users/new?date=true'
72
+
73
+ within '#date' do
74
+ select_date(date, :id_prefix => 'user_active_at')
75
+
76
+ assert find_selected_option('user[active_at(1i)]', '2011')
77
+ assert find_selected_option('user[active_at(2i)]', '2')
78
+ assert find_selected_option('user[active_at(3i)]', '28')
79
+ end
80
+ end
81
+
82
+ test "select date using label" do
83
+ date = DateTime.new(2011, 2, 28, 16, 0)
84
+
85
+ visit '/users/new?date=true'
86
+
87
+ within '#date' do
88
+ select_date(date, :from => 'Active at')
89
+
90
+ assert find_selected_option('user[active_at(1i)]', '2011')
91
+ assert find_selected_option('user[active_at(2i)]', '2')
92
+ assert find_selected_option('user[active_at(3i)]', '28')
93
+ end
94
+ end
95
+
96
+
97
+ test "select date and time didn't find the label" do
98
+ msg = "cannot select option, select with label 'Unexistant' not found"
99
+ visit '/users/new'
100
+ assert_raise_with_message Capybara::ElementNotFound, msg do
101
+ select_datetime(Time.current, :from => 'Unexistant')
102
+ end
103
+ end
104
+
105
+ test "select date and time didn't find the id prefix" do
106
+ msg = "cannot select option, no select box with id prefix 'unexistant' found"
107
+ visit '/users/new'
108
+ assert_raise_with_message Capybara::ElementNotFound, msg do
109
+ select_datetime(Time.current, :id_prefix => 'unexistant')
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def find_selected_option(select_name, value)
116
+ find("select[name='#{select_name}'] > option[selected][value='#{value}']")
117
+ end
118
+ end