hermes 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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