page_record 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.rubocop.yml +13 -0
  2. data/CHANGES.md +5 -0
  3. data/Gemfile.lock +46 -14
  4. data/Guardfile +24 -0
  5. data/README.md +27 -1
  6. data/bin/autospec +16 -0
  7. data/bin/guard +16 -0
  8. data/bin/rake +16 -0
  9. data/bin/rspec +16 -0
  10. data/lib/page_record/attribute_accessors.rb +57 -54
  11. data/lib/page_record/base.rb +27 -22
  12. data/lib/page_record/class_actions.rb +47 -50
  13. data/lib/page_record/class_methods.rb +252 -261
  14. data/lib/page_record/errors.rb +81 -82
  15. data/lib/page_record/finders.rb +129 -131
  16. data/lib/page_record/form_builder.rb +4 -4
  17. data/lib/page_record/formtastic.rb +20 -9
  18. data/lib/page_record/helpers.rb +192 -131
  19. data/lib/page_record/inspector.rb +36 -0
  20. data/lib/page_record/instance_actions.rb +44 -46
  21. data/lib/page_record/rspec.rb +1 -1
  22. data/lib/page_record/validation.rb +46 -0
  23. data/lib/page_record/version.rb +1 -1
  24. data/lib/page_record.rb +13 -15
  25. data/page_record.gemspec +4 -1
  26. data/spec/.rubocop.yml +4 -0
  27. data/spec/helpers_spec.rb +109 -100
  28. data/spec/inspector_spec.rb +70 -0
  29. data/spec/page_record_spec.rb +357 -388
  30. data/spec/spec_helper.rb +1 -3
  31. data/spec/support/shared_contexts.rb +24 -2
  32. data/spec/support/shared_examples.rb +41 -45
  33. data/spec/support/team.rb +4 -4
  34. data/spec/support/test_app.rb +10 -13
  35. data/spec/support/views/page-with-1-error.erb +5 -0
  36. data/spec/support/views/page-with-2-errors-on-different-attributes.erb +9 -0
  37. data/spec/support/views/page-with-2-errors-on-same-attribute.erb +6 -0
  38. data/spec/support/views/page-without-errors.erb +4 -0
  39. data/spec/validation_spec.rb +142 -0
  40. data/tmp/rspec_guard_result +1 -0
  41. metadata +80 -5
@@ -1,88 +1,87 @@
1
1
  module PageRecord
2
2
 
3
+ ##
4
+ # This {::Exception} is raised when the specified record is not found
5
+ # on the page. Check your selector, filter and HTML code for details.
6
+ #
7
+ # ```html
8
+ # <div data-team-id='10'>
9
+ # <div data-attribute-for='name'>Ajax</div>
10
+ # </div>
11
+ # ```
12
+ # When the following code is executed, the {RecordNotFound} exception is raised.
13
+ #
14
+ # ```ruby
15
+ # TeamPage.find(11)
16
+ # ```
17
+ #
18
+ class RecordNotFound < Exception
19
+ end
3
20
 
4
- ##
5
- # This {::Exception} is raised when the specified record is not found
6
- # on the page. Check your selector, filter and HTML code for details.
7
- #
8
- # ```html
9
- # <div data-team-id='10'>
10
- # <div data-attribute-for='name'>Ajax</div>
11
- # </div>
12
- # ```
13
- # When the following code is executed, the {RecordNotFound} exception is raised.
14
- #
15
- # ```ruby
16
- # TeamPage.find(11)
17
- #```
18
- #
19
- class RecordNotFound < Exception
20
- end
21
+ ##
22
+ # This {::Exception} is raised when the specfied attribute is not found
23
+ # on the page. Check your selector, filter and HTML code for details.
24
+ #
25
+ # ```html
26
+ # <div data-team-id='10'>
27
+ # <div data-attribute-for='name'>Ajax</div>
28
+ # </div>
29
+ # ```
30
+ # When the following code is executed, the {AttributeNotFound} exception is raised.
31
+ #
32
+ # ```ruby
33
+ # TeamPage.find(10).ranking
34
+ # ```
35
+ #
36
+ class AttributeNotFound < Exception
37
+ end
21
38
 
22
- ##
23
- # This {::Exception} is raised when the specfied attribute is not found
24
- # on the page. Check your selector, filter and HTML code for details.
25
- #
26
- # ```html
27
- # <div data-team-id='10'>
28
- # <div data-attribute-for='name'>Ajax</div>
29
- # </div>
30
- # ```
31
- # When the following code is executed, the {AttributeNotFound} exception is raised.
32
- #
33
- # ```ruby
34
- # TeamPage.find(10).ranking
35
- #```
36
- #
37
- class AttributeNotFound < Exception
38
- end
39
+ ##
40
+ # This {::Exception} is raised when you have not set the page variable
41
+ # of the class. Check {PageRecord::Base.page} for details
42
+ #
43
+ class PageNotSet < Exception
44
+ end
39
45
 
40
- ##
41
- # This {::Exception} is raised when you have not set the page variable
42
- # of the class. Check {PageRecord::Base.page} for details
43
- #
44
- class PageNotSet < Exception
45
- end
46
+ ##
47
+ # This {::Exception} is raised when the page contains multiple instances
48
+ # of the specfied record type. Use a selector to narrow the search.
49
+ #
50
+ # ```html
51
+ # <div id='first-table' data-team-id='10'>
52
+ # <div data-attribute-for='name'>Ajax</div>
53
+ # </div>
54
+ # <div id='second-table' data-team-id='10'>
55
+ # <div data-attribute-for='name'>Ajax</div>
56
+ # </div>
57
+ # ```
58
+ # When the following code is executed, the {MultipleRecords} exception is raised.
59
+ #
60
+ # ```ruby
61
+ # TeamPage.find(10)
62
+ # ```
63
+ #
64
+ # To fix this, use the `#first-table` in the selector
65
+ #
66
+ # ```ruby
67
+ # TeamPage.find(10, '#first-table')
68
+ # ```
69
+ #
70
+ class MultipleRecords < Exception
71
+ end
46
72
 
47
- ##
48
- # This {::Exception} is raised when the page contains multiple instances
49
- # of the specfied record type. Use a selector to narrow the search.
50
- #
51
- # ```html
52
- # <div id='first-table' data-team-id='10'>
53
- # <div data-attribute-for='name'>Ajax</div>
54
- # </div>
55
- # <div id='second-table' data-team-id='10'>
56
- # <div data-attribute-for='name'>Ajax</div>
57
- # </div>
58
- # ```
59
- # When the following code is executed, the {MultipleRecords} exception is raised.
60
- #
61
- # ```ruby
62
- # TeamPage.find(10)
63
- #```
64
- #
65
- # To fix this, use the `#first-table` in the selector
66
- #
67
- # ```ruby
68
- # TeamPage.find(10, '#first-table')
69
- #```
70
- #
71
- class MultipleRecords < Exception
72
- end
73
-
74
- # This {::Exception} is raised when you try to set a non input field.
75
- #
76
- # ```ruby
77
- # <div data-team-id=1>
78
- # <div data-attribute-for='name'>PSV</div>
79
- # </div>
80
- # ```
81
- # When the following code is executed, the {NotInputField} exception is raised.
82
- #
83
- # ```ruby
84
- # team_on_page.name = 'Ajax'
85
- # ```
86
- class NotInputField < Exception
87
- end
88
- end
73
+ # This {::Exception} is raised when you try to set a non input field.
74
+ #
75
+ # ```ruby
76
+ # <div data-team-id=1>
77
+ # <div data-attribute-for='name'>PSV</div>
78
+ # </div>
79
+ # ```
80
+ # When the following code is executed, the {NotInputField} exception is raised.
81
+ #
82
+ # ```ruby
83
+ # team_on_page.name = 'Ajax'
84
+ # ```
85
+ class NotInputField < Exception
86
+ end
87
+ end
@@ -1,135 +1,133 @@
1
1
  module PageRecord
2
- ##
3
- # PageRecord is a specific sort of {http://assertselenium.com/automation-design-practices/page-object-pattern/ PageObject pattern}. Where a "normal"
4
- # {http://assertselenium.com/automation-design-practices/page-object-pattern/ PageObject} tries to make the page accessible with business like functions.
5
- # We have taken a different approach. We've noticed that a lot of WebPage are
6
- # mainly luxury CRUD pages. This means that almost every page shows one or more
7
- # records of a certain type and has the ability to create, read update and delete
8
- # one or more records. Sounds familiar? Yes, it is the same as an ActiveRecord
9
- # pattern. So we tried to make accessing a page as close as possible to accessing
10
- # an ActiveRecord.
11
- #
12
- # To make this work, however, we need to add extra information to the HTML page.
13
- # With HTML5, , you can do this easily. HTML-5 supports the data- attributes on any
14
- # tag. We use these tags to identify the records on the page.
15
- #
16
- #
17
- class Base
18
- ##
19
- #
20
- # Searches the page and returns an {::Array} of {PageRecord::Base} of instances of
21
- # that match the selector and the filter. See {file:README.md#markup markup} for more
22
- # details about formatting the page.
23
- #
24
- # example:
25
- #
26
- # ```ruby
27
- # TeamPage.all
28
- # ```
29
- #
30
- # returns all records on the page
31
- #
32
- # @param selector [String] selector to use for searching the table on the page
33
- # @param filter [String] filter to use on the records on the page
34
- #
35
- # @return [Array Pagerecord] The Array containing instances of [PageRecord::Base]
36
- # with records that fit the selector and the filter
37
- #
38
- # @raise [MultipleRecords] if the page contains more then on set of records
39
- # @raise [RecordNotFound] if the page does not contain any of the specified records
40
- #
41
- def self.all(selector = nil, filter = nil)
42
- selector ||= self.instance_variable_get('@selector')
43
- filter ||= self.instance_variable_get('@filter')
44
- records = []
45
- context = context_for_selector(selector)
46
- context.all("[data-#{@type}-id]#{filter}").each do | record|
47
- id = record["data-#{@type}-id"]
48
- records << self.new(id, selector)
49
- end
50
- records
51
- end
2
+ ##
3
+ # PageRecord is a specific sort of {http://assertselenium.com/automation-design-practices/page-object-pattern/ PageObject pattern}. Where a "normal"
4
+ # {http://assertselenium.com/automation-design-practices/page-object-pattern/ PageObject} tries to make the page accessible with business like functions.
5
+ # We have taken a different approach. We've noticed that a lot of WebPage are
6
+ # mainly luxury CRUD pages. This means that almost every page shows one or more
7
+ # records of a certain type and has the ability to create, read update and delete
8
+ # one or more records. Sounds familiar? Yes, it is the same as an ActiveRecord
9
+ # pattern. So we tried to make accessing a page as close as possible to accessing
10
+ # an ActiveRecord.
11
+ #
12
+ # To make this work, however, we need to add extra information to the HTML page.
13
+ # With HTML5, , you can do this easily. HTML-5 supports the data- attributes on any
14
+ # tag. We use these tags to identify the records on the page.
15
+ #
16
+ #
17
+ class Base
18
+ ##
19
+ #
20
+ # Searches the page and returns an {::Array} of {PageRecord::Base} of instances of
21
+ # that match the selector and the filter. See {file:README.md#markup markup} for more
22
+ # details about formatting the page.
23
+ #
24
+ # example:
25
+ #
26
+ # ```ruby
27
+ # TeamPage.all
28
+ # ```
29
+ #
30
+ # returns all records on the page
31
+ #
32
+ # @param selector [String] selector to use for searching the table on the page
33
+ # @param filter [String] filter to use on the records on the page
34
+ #
35
+ # @return [Array Pagerecord] The Array containing instances of [PageRecord::Base]
36
+ # with records that fit the selector and the filter
37
+ #
38
+ # @raise [MultipleRecords] if the page contains more then on set of records
39
+ # @raise [RecordNotFound] if the page does not contain any of the specified records
40
+ #
41
+ def self.all(selector = nil, filter = nil)
42
+ selector ||= @selector
43
+ filter ||= @filter
44
+ records = []
45
+ context = context_for_selector(selector)
46
+ context.all("[data-#{@type}-id]#{filter}").each do | record|
47
+ id = record["data-#{@type}-id"]
48
+ records << new(id, selector)
49
+ end
50
+ records
51
+ end
52
52
 
53
- ##
54
- #
55
- # Searches the page and returns an instance of {PageRecord::Base} of instances of
56
- # that matches the given id, selector and the filter. See {file:README.md#markup markup} for more
57
- # details about formatting the page.
58
- #
59
- # example:
60
- #
61
- # ```ruby
62
- # TeamPage.find(1)
63
- # ```
64
- #
65
- # returns the record with id
66
- #
67
- # When you don't specify an id, `find` returns the only record on the page.
68
- # If you have more than one record on the page, `find` raises {MultipleRecords}.
69
- #
70
- # example:
71
- #
72
- # ```ruby
73
- # TeamPage.find()
74
- # ```
75
- #
76
- # @param selector [String] selector to use for searching the table on the page
77
- # @param filter [String] filter to use on the records on the page
78
- #
79
- # @return [Pagerecord] An instance of [PageRecord::Base]
80
- #
81
- # @raise [MultipleRecords] if the page contains more then on set of records
82
- # @raise [RecordNotFound] if the page does not contain any of the specified records
83
- #
84
- def self.find(id=nil, selector = nil, filter= nil)
85
- selector ||= self.instance_variable_get('@selector')
86
- filter ||= self.instance_variable_get('@filter')
87
- self.new(id, selector, filter)
88
- end
53
+ ##
54
+ #
55
+ # Searches the page and returns an instance of {PageRecord::Base} of instances of
56
+ # that matches the given id, selector and the filter. See {file:README.md#markup markup} for more
57
+ # details about formatting the page.
58
+ #
59
+ # example:
60
+ #
61
+ # ```ruby
62
+ # TeamPage.find(1)
63
+ # ```
64
+ #
65
+ # returns the record with id
66
+ #
67
+ # When you don't specify an id, `find` returns the only record on the page.
68
+ # If you have more than one record on the page, `find` raises {MultipleRecords}.
69
+ #
70
+ # example:
71
+ #
72
+ # ```ruby
73
+ # TeamPage.find()
74
+ # ```
75
+ #
76
+ # @param selector [String] selector to use for searching the table on the page
77
+ # @param filter [String] filter to use on the records on the page
78
+ #
79
+ # @return [Pagerecord] An instance of [PageRecord::Base]
80
+ #
81
+ # @raise [MultipleRecords] if the page contains more then on set of records
82
+ # @raise [RecordNotFound] if the page does not contain any of the specified records
83
+ #
84
+ def self.find(id = nil, selector = nil, filter = nil)
85
+ selector ||= @selector
86
+ filter ||= @filter
87
+ new(id, selector, filter)
88
+ end
89
89
 
90
- ##
91
- #
92
- # Searches the page and returns an instance of {PageRecord::Base} of instances of
93
- # that matches the given attribute. See {file:README.md#markup markup} for more
94
- # details about formatting the page.
95
- #
96
- # Although you can call this yourself, {PageRecord::Base} uses this method for defining a
97
- # finder for all attributes when you define your page class, {PageRecord::Base}
98
- # See {PageRecord::Base.attributes} for more details.
99
- #
100
- # example:
101
- #
102
- # ```ruby
103
- # TeamPage.find_by_name('Ajax')
104
- # ```
105
- #
106
- # returns the record where the name is set to Ajax
107
- #
108
- # @param attribute [String] The attribute name
109
- # @param value [String] The value to search for
110
- # @param selector [String] selector to use for searching the table on the page
111
- # @param filter [String] filter to use on the records on the page
112
- #
113
- # @return [Pagerecord] An instance of [PageRecord::Base].
114
- #
115
- # @raise [MultipleRecords] if the page contains more then on set of records
116
- # @raise [RecordNotFound] if the page does not contain any of the specified records
117
- #
118
- def self.find_by_attribute(attribute, value, selector, filter)
119
- begin
120
- selector ||= self.instance_variable_get('@selector')
121
- filter ||= self.instance_variable_get('@filter')
122
-
123
- context = self.context_for_selector(selector)
124
- record = context.find("[data-#{@type}-id]#{filter} > [data-attribute-for='#{attribute}']", :text => value)
125
- parent = record.find(:xpath, "..")
126
- id = parent["data-#{@type}-id"]
127
- self.new(id, selector)
128
- rescue Capybara::Ambiguous
129
- raise MultipleRecords, "Found multiple #{@type} record with #{attribute} #{value} on page"
130
- rescue Capybara::ElementNotFound
131
- raise RecordNotFound, "#{@type} record with #{attribute} #{value} not found on page"
132
- end
133
- end
134
- end
90
+ ##
91
+ #
92
+ # Searches the page and returns an instance of {PageRecord::Base} of instances of
93
+ # that matches the given attribute. See {file:README.md#markup markup} for more
94
+ # details about formatting the page.
95
+ #
96
+ # Although you can call this yourself, {PageRecord::Base} uses this method for defining a
97
+ # finder for all attributes when you define your page class, {PageRecord::Base}
98
+ # See {PageRecord::Base.attributes} for more details.
99
+ #
100
+ # example:
101
+ #
102
+ # ```ruby
103
+ # TeamPage.find_by_name('Ajax')
104
+ # ```
105
+ #
106
+ # returns the record where the name is set to Ajax
107
+ #
108
+ # @param attribute [String] The attribute name
109
+ # @param value [String] The value to search for
110
+ # @param selector [String] selector to use for searching the table on the page
111
+ # @param filter [String] filter to use on the records on the page
112
+ #
113
+ # @return [Pagerecord] An instance of [PageRecord::Base].
114
+ #
115
+ # @raise [MultipleRecords] if the page contains more then on set of records
116
+ # @raise [RecordNotFound] if the page does not contain any of the specified records
117
+ #
118
+ def self.find_by_attribute(attribute, value, selector, filter)
119
+ selector ||= @selector
120
+ filter ||= @filter
121
+
122
+ context = context_for_selector(selector)
123
+ record = context.find("[data-#{@type}-id]#{filter} > [data-attribute-for='#{attribute}']", text: value)
124
+ parent = record.find(:xpath, '..')
125
+ id = parent["data-#{@type}-id"]
126
+ new(id, selector)
127
+ rescue Capybara::Ambiguous
128
+ raise MultipleRecords, "Found multiple #{@type} record with #{attribute} #{value} on page"
129
+ rescue Capybara::ElementNotFound
130
+ raise RecordNotFound, "#{@type} record with #{attribute} #{value} not found on page"
131
+ end
132
+ end
135
133
  end
@@ -3,18 +3,18 @@ module PageRecord
3
3
  class FormBuilder < ActionView::Helpers::FormBuilder
4
4
  include PageRecord::Helpers
5
5
 
6
- helpers = field_helpers + %w(time_zone_select date_select) - %w(hidden_field fields_for label)
6
+ helpers = field_helpers + [:time_zone_select, :date_select] - [:hidden_field, :fields_for, :label]
7
7
  helpers.each do |helper|
8
8
  define_method helper do |field, *args|
9
- options = args.detect{ |a| a.is_a?(Hash) } || {}
9
+ options = args.find { |a| a.is_a?(Hash) } || {}
10
10
  super field, options.merge(attribute_for(field))
11
11
  end
12
12
  end
13
13
 
14
- actions = ['submit','button']
14
+ actions = %w(submit button)
15
15
  actions.each do |helper|
16
16
  define_method helper do |field, *args|
17
- options = args.detect{ |a| a.is_a?(Hash) } || {}
17
+ options = args.find { |a| a.is_a?(Hash) } || {}
18
18
  super field, options.merge(action_for(field.downcase))
19
19
  end
20
20
  end
@@ -2,6 +2,18 @@ module Formtastic
2
2
 
3
3
  module Inputs
4
4
  module Base
5
+
6
+ module Errors
7
+
8
+ def error_sentence_html
9
+ extend(PageRecord::Helpers)
10
+ error_class = options[:error_class] || builder.default_inline_error_class
11
+ options = { :class => error_class }.merge(error_for(@method))
12
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.html_safe), options)
13
+ end
14
+ end
15
+
16
+
5
17
  module Html
6
18
 
7
19
  alias_method :input_html_options_org, :input_html_options
@@ -13,14 +25,13 @@ module Formtastic
13
25
  #
14
26
 
15
27
  def input_html_options
16
- self.extend(PageRecord::Helpers)
17
- input_html_options_org.merge(attribute_for(@method))
28
+ extend(PageRecord::Helpers)
29
+ input_html_options_org.merge(attribute_for(input_name))
18
30
  end
19
31
  end
20
32
  end
21
33
  end
22
34
 
23
-
24
35
  module Actions
25
36
  module Buttonish
26
37
 
@@ -32,7 +43,7 @@ module Formtastic
32
43
  # for all action methods
33
44
  #
34
45
  def extra_button_html_options
35
- self.extend(PageRecord::Helpers)
46
+ extend(PageRecord::Helpers)
36
47
  extra_button_html_options_org.merge(action_for(@method))
37
48
  end
38
49
 
@@ -46,21 +57,21 @@ module Formtastic
46
57
 
47
58
  #
48
59
  # This is a replacement method for the original `semantic_form_for` method from `Formtastic`.
49
- # The only thing it does, is merge the `form_record_for` into the output.
60
+ # The only thing it does, is merge the `form_record_for` into the output.
50
61
  #
51
62
  def semantic_form_for(record_or_name_or_array, *args, &proc)
52
- self.extend(PageRecord::Helpers)
63
+ extend(PageRecord::Helpers)
53
64
  options = args.extract_options!
54
- options||= {}
65
+ options ||= {}
55
66
  case record_or_name_or_array
56
67
  when String, Symbol
57
68
  object_name = record_or_name_or_array
58
69
  else
59
70
  object = record_or_name_or_array.is_a?(Array) ? record_or_name_or_array.last : record_or_name_or_array
60
- raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
71
+ raise ArgumentError, 'First argument in form cannot contain nil or be empty' unless object
61
72
  object_name = options[:as] || model_name_from_record_or_class(object).param_key
62
73
  end
63
- options = options.merge(html:form_record_for(object_name))
74
+ options = options.merge(html: form_record_for(object_name))
64
75
  args << options
65
76
  semantic_form_for_org(record_or_name_or_array, *args, &proc)
66
77
  end