ae_page_objects 0.0.1.beta.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.
@@ -0,0 +1,135 @@
1
+ module AePageObjects
2
+ module Dsl
3
+ module Collection
4
+ extend ActiveSupport::Concern
5
+ include Dsl::Element
6
+
7
+ module ClassMethods
8
+ include InternalHelpers
9
+
10
+ # Defines a collection of elements. Blocks are evaluated on the item class used by the
11
+ # collection. collection() defines a method on the class that returns an instance of a collection
12
+ # class which contains instances of the collection's item class.
13
+ #
14
+ # Supported signatures are described below.
15
+ #
16
+ # ------------------------------------------------
17
+ # Signature: (:is, no :contains, no block)
18
+ #
19
+ # collection :addresses, :is => AddressList
20
+ #
21
+ # Collection class: AddressList
22
+ # Item class: AddressList.item_class
23
+ #
24
+ # ------------------------------------------------
25
+ # Signature: (no :is, :contains, no block)
26
+ #
27
+ # collection :addresses, :contains => Address
28
+ #
29
+ # Collection class: one-off subclass of ::AePageObjects::Collection
30
+ # Item class: Address
31
+ #
32
+ # ------------------------------------------------
33
+ # Signature: (:is, :contains, no block)
34
+ #
35
+ # collection :addresses, :is => AddressList, :contains => ExtendedAddress
36
+ #
37
+ # Collection class: one-off subclass ofAddressList
38
+ # Item class: ExtendedAddress
39
+ #
40
+ # ------------------------------------------------
41
+ # Signature: (no :is, no :contains, block)
42
+ #
43
+ # collection :addresses do
44
+ # element :city
45
+ # element :state
46
+ # end
47
+ #
48
+ # Collection class: one-off subclass of ::AePageObjects::Collection
49
+ # Item class: one-off subclass of ::AePageObjects::Element
50
+ # Methods defined on item class:
51
+ # city() # -> instance of ::AePageObjects::Element
52
+ # state() # -> instance of ::AePageObjects::Element
53
+ #
54
+ # ------------------------------------------------
55
+ # Signature: (:is, no :contains, block)
56
+ #
57
+ # collection :addresses, :is => AddressList do
58
+ # element :longitude
59
+ # element :latitude
60
+ # end
61
+ #
62
+ # Collection class: one-off subclass of AddressList
63
+ # Item class: one-off subclass of AddressList.item_class
64
+ # Methods defined on item class:
65
+ # longitude() # -> instance of ::AePageObjects::Element
66
+ # latitude() # -> instance of ::AePageObjects::Element
67
+ #
68
+ # ------------------------------------------------
69
+ # Signature: (no :is, :contains, block)
70
+ #
71
+ # collection :addresses, :contains => Address do
72
+ # element :longitude
73
+ # element :latitude
74
+ # end
75
+ #
76
+ # Collection class: one-off subclass of ::AePageObjects::Collection element
77
+ # Item class: one-off subclass of Address
78
+ # Methods defined on item class:
79
+ # longitude() # -> instance of ::AePageObjects::Element
80
+ # latitude() # -> instance of ::AePageObjects::Element
81
+ #
82
+ # ------------------------------------------------
83
+ # Signature: (:is, :contains, block)
84
+ #
85
+ # collection :addresses, :is => AddressList, :contains => Address do
86
+ # element :longitude
87
+ # element :latitude
88
+ # end
89
+ #
90
+ # Collection class: one-off subclass of AddressList
91
+ # Item class: one-off subclass of Address
92
+ # Methods defined on item class:
93
+ # longitude() # -> instance of ::AePageObjects::Element
94
+ # latitude() # -> instance of ::AePageObjects::Element
95
+ #
96
+ def collection(name, options = {}, &block)
97
+ options ||= {}
98
+
99
+ # only a collection class is specified or the item class
100
+ # specified matches the collection's item class
101
+ if block.blank? && options[:is] && (
102
+ options[:contains].blank? || options[:is].item_class == options[:contains]
103
+ )
104
+ return element(name, options)
105
+ end
106
+
107
+ options = options.dup
108
+
109
+ # create/get the collection class
110
+ if options[:is]
111
+ ensure_class_for_param!(:is, options[:is], ::AePageObjects::Collection)
112
+ else
113
+ options[:is] = ::AePageObjects::Collection
114
+
115
+ raise ArgumentError, "Must specify either a block or a :contains option." if options[:contains].blank? && block.blank?
116
+ end
117
+
118
+ item_class = options.delete(:contains) || options[:is].item_class
119
+ if block.present?
120
+ item_class = item_class.new_subclass(&block).tap do |new_item_class|
121
+ new_item_class.element_attributes.merge!(item_class.element_attributes)
122
+ end
123
+ end
124
+
125
+ # since we are creating a new item class, we need to subclass the collection class
126
+ # so we can parameterize the collection class with an item class
127
+ options[:is] = options[:is].new_subclass
128
+ options[:is].item_class = item_class
129
+
130
+ element(name, options)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,45 @@
1
+ module AePageObjects
2
+ module Dsl
3
+ module Element
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ def inherited(subclass)
9
+ subclass.class_eval do
10
+ class << self
11
+ def element_attributes
12
+ @element_attributes ||= {}
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def element(name, options = {})
19
+ options = options.dup
20
+ klass = field_klass(options)
21
+
22
+ self.element_attributes[name.to_sym] = klass
23
+
24
+ define_method name do |&block|
25
+ ElementProxy.new(klass, self, name, options, &block)
26
+ end
27
+
28
+ klass
29
+ end
30
+
31
+ private
32
+
33
+ def field_klass(options)
34
+ field_type = options.delete(:is)
35
+
36
+ if field_type.is_a? Class
37
+ field_type
38
+ else
39
+ ::AePageObjects::Element
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ module AePageObjects
2
+ module Dsl
3
+ module FormFor
4
+ extend ActiveSupport::Concern
5
+ include Dsl::Element
6
+
7
+ module ClassMethods
8
+
9
+ def form_for(form_name, options = {}, &block)
10
+ options ||= {}
11
+
12
+ raise ArgumentError, ":is option not supported" if options[:is]
13
+ raise ArgumentError, "Block required." unless block.present?
14
+
15
+ klass = ::AePageObjects::Form.new_subclass(&block)
16
+
17
+ options = options.dup
18
+ options[:is] = klass
19
+
20
+ element(form_name, options)
21
+
22
+ klass.element_attributes.each do |node_name, node_klazz|
23
+ delegate node_name, :to => form_name
24
+ self.element_attributes[node_name] = node_klazz
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module AePageObjects
2
+ module Dsl
3
+ module NestedElement
4
+ extend ActiveSupport::Concern
5
+ include Dsl::Element
6
+
7
+ module ClassMethods
8
+
9
+ def element(name, options = {}, &block)
10
+ raise ArgumentError, ":is option and block not supported together" if options[:is].present? && block_given?
11
+
12
+ if block_given?
13
+ klass = ::AePageObjects::HasOne.new_subclass(&block)
14
+
15
+ options = options.dup
16
+ options[:is] = klass
17
+ end
18
+
19
+ super(name, options)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ module AePageObjects
2
+ module InternalHelpers
3
+ def ensure_class_for_param!(param_name, klass, ancestor_class)
4
+ if klass && ! (klass < ancestor_class)
5
+ raise "#{param_name} <#{klass}> must extend #{ancestor_class}, ->#{klass.ancestors.inspect}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,148 @@
1
+ module AePageObjects
2
+ class RakeRouter
3
+
4
+ attr_reader :routes
5
+
6
+ def initialize(rake_routes, mounted_prefix = '')
7
+ @mounted_prefix = mounted_prefix || ""
8
+ @routes = ActiveSupport::OrderedHash.new
9
+ route_line_regex = /(\w+)(?:\s[A-Z]+)?\s+(\/.*)\(.:format\).*$/
10
+
11
+ rake_routes.split("\n").each do |line|
12
+ line = line.strip
13
+ matches = route_line_regex.match(line)
14
+ if matches
15
+ @routes[matches[1].to_sym] = Route.new(matches[2], @mounted_prefix)
16
+ end
17
+ end
18
+ end
19
+
20
+ def path_recognizes_url?(path, url)
21
+ if path.is_a?(String)
22
+ path.sub(/\/$/, '') == url.sub(/\/$/, '')
23
+ elsif path.is_a?(Symbol)
24
+ route = @routes[path]
25
+ route && route.matches?(url)
26
+ end
27
+ end
28
+
29
+ def generate_path(named_route, *args)
30
+ if named_route.is_a?(String)
31
+ return Path.new(@mounted_prefix + named_route)
32
+ end
33
+
34
+ if route = @routes[named_route]
35
+ options = args.extract_options!
36
+ route.generate_path(options)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ class Path < String
43
+ attr_reader :params, :regex
44
+
45
+ def initialize(value)
46
+ super(value.gsub(/(\/)+/, '/').sub(/\(\.\:format\)$/, ''))
47
+
48
+ @params = parse_params
49
+ @regex = generate_regex
50
+ end
51
+
52
+ def generate(param_values)
53
+ param_values = param_values.symbolize_keys
54
+ @params.values.inject(self) do |path, param|
55
+ param.substitute(path, param_values)
56
+ end
57
+ end
58
+
59
+ private
60
+ def parse_params
61
+ # overwrite the required status with the optional
62
+ {}.merge(required_params).merge(optional_params)
63
+ end
64
+
65
+ def find_params(using_regex)
66
+ scan(using_regex).flatten.map(&:to_sym)
67
+ end
68
+
69
+ def optional_params
70
+ {}.tap do |optional_params|
71
+ find_params(/\(\/\:(\w+)\)/).each do |param_name|
72
+ optional_params[param_name] = Param.new(param_name, true)
73
+ end
74
+ end
75
+ end
76
+
77
+ def required_params
78
+ {}.tap do |required_params|
79
+ find_params(/\:(\w+)/).each do |param_name|
80
+ required_params[param_name] = Param.new(param_name, false)
81
+ end
82
+ end
83
+ end
84
+
85
+ def generate_regex
86
+ regex_spec = @params.values.inject(self) do |regex_spec, param|
87
+ param.replace_param_in_url(regex_spec)
88
+ end
89
+ Regexp.new regex_spec
90
+ end
91
+ end
92
+
93
+ class Param < Struct.new(:name, :optional)
94
+ include Comparable
95
+
96
+ alias :optional? :optional
97
+
98
+ def <=>(other)
99
+ name.to_s <=> other.name.to_s
100
+ end
101
+
102
+ def eql?(other)
103
+ name == other.name
104
+ end
105
+
106
+ def hash
107
+ name.hash
108
+ end
109
+
110
+ def replace_param_in_url(url)
111
+ if optional?
112
+ url.gsub("(/:#{name})", '(\/.+)?')
113
+ else
114
+ url.gsub(":#{name}", '(.+)')
115
+ end
116
+ end
117
+
118
+ def substitute(url, values)
119
+ if optional?
120
+ if values[name]
121
+ url.sub("(/:#{name})", "/#{values[name]}")
122
+ else
123
+ url.sub("(/:#{name})", '')
124
+ end
125
+ else
126
+ raise ArgumentError, "Missing required parameter '#{name}' for '#{url}' in #{values.inspect}" unless values.key? name
127
+ url.sub(":#{name}", values[name])
128
+ end
129
+ end
130
+ end
131
+
132
+ class Route
133
+ def initialize(spec, mounted_prefix)
134
+ @path = Path.new(mounted_prefix + spec)
135
+ @path.freeze
136
+ end
137
+
138
+ def matches?(url)
139
+ url =~ @path.regex
140
+ end
141
+
142
+ def generate_path(options)
143
+ options = options.symbolize_keys
144
+ @path.generate(options)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,19 @@
1
+ module AePageObjects
2
+ class Document < Node
3
+ include Concerns::Visitable
4
+
5
+ def document
6
+ self
7
+ end
8
+
9
+ class << self
10
+ private
11
+ def application
12
+ @application ||= begin
13
+ universe = AePageObjects::DependenciesHook.containing_page_object_universe(self)
14
+ "#{universe.name}::Application".constantize.instance
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,86 @@
1
+ module AePageObjects
2
+ class Element < Node
3
+ attr_reader :parent, :default_name
4
+
5
+ def initialize(parent, name, options_or_locator = {})
6
+ @parent = parent
7
+ @default_name = name.to_s
8
+ @locator = nil
9
+ @name = nil
10
+
11
+ configure(parse_options(options_or_locator))
12
+
13
+ @locator ||= default_locator
14
+
15
+ super(scoped_node)
16
+ end
17
+
18
+ def document
19
+ @document ||= begin
20
+ node = self.parent
21
+
22
+ until node.is_a?(::AePageObjects::Document)
23
+ node = node.parent
24
+ end
25
+
26
+ node
27
+ end
28
+ end
29
+
30
+ def __full_name__
31
+ if parent.respond_to?(:__full_name__)
32
+ "#{parent.__full_name__}_#{__name__}"
33
+ else
34
+ __name__
35
+ end
36
+ end
37
+
38
+ alias_method :full_name, :__full_name__
39
+
40
+ def __name__
41
+ @name || default_name
42
+ end
43
+
44
+ alias_method :name, :__name__
45
+
46
+ def to_s
47
+ super.tap do |str|
48
+ str << "#name:<#{__name__}>"
49
+ end
50
+ end
51
+
52
+ def using_default_locator?
53
+ @locator == default_locator
54
+ end
55
+
56
+ private
57
+
58
+ def configure(options)
59
+ @locator = options.delete(:locator)
60
+ @name = options.delete(:name).try(:to_s)
61
+ end
62
+
63
+ def parse_options(options_or_locator)
64
+ if options_or_locator.is_a?( Hash )
65
+ options_or_locator.symbolize_keys
66
+ else
67
+ {:locator => options_or_locator}
68
+ end
69
+ end
70
+
71
+ def default_locator
72
+ @default_locator ||= Proc.new { "##{__full_name__}" }
73
+ end
74
+
75
+ def scoped_node
76
+ if @locator
77
+ locator = eval_locator(@locator)
78
+ if locator.present?
79
+ return parent.find(*locator)
80
+ end
81
+ end
82
+
83
+ parent.node
84
+ end
85
+ end
86
+ end