page_record 0.4.0 → 0.5.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 (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