ae_page_objects 0.0.1.beta.0

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