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,57 @@
1
+ require 'capybara'
2
+ require 'capybara/dsl'
3
+ require 'active_support'
4
+ require 'active_support/core_ext/module/delegation'
5
+ require 'active_support/core_ext/hash/keys'
6
+ require 'active_support/core_ext/object/try'
7
+ require 'active_support/core_ext/class'
8
+ require 'active_support/dependencies'
9
+
10
+ require 'ae_page_objects/version'
11
+
12
+ module AePageObjects
13
+ autoload :ConstantResolver, 'ae_page_objects/core/constant_resolver'
14
+ autoload :DependenciesHook, 'ae_page_objects/core/dependencies_hook'
15
+ autoload :Installable, 'ae_page_objects/core/installable'
16
+ autoload :Configuration, 'ae_page_objects/core/configuration'
17
+ autoload :Configurable, 'ae_page_objects/core/configurable'
18
+ autoload :Application, 'ae_page_objects/core/application'
19
+ autoload :ApplicationRouter, 'ae_page_objects/core/application_router'
20
+ autoload :RakeRouter, 'ae_page_objects/core/rake_router'
21
+ autoload :InternalHelpers, 'ae_page_objects/core/internal_helpers'
22
+
23
+ module Dsl
24
+ autoload :Element, 'ae_page_objects/core/dsl/element'
25
+ autoload :NestedElement, 'ae_page_objects/core/dsl/nested_element'
26
+ autoload :Collection, 'ae_page_objects/core/dsl/collection'
27
+ autoload :FormFor, 'ae_page_objects/core/dsl/form_for'
28
+ end
29
+
30
+ module Concerns
31
+ autoload :LoadEnsuring, 'ae_page_objects/concerns/load_ensuring'
32
+ autoload :Staleable, 'ae_page_objects/concerns/staleable'
33
+ autoload :Visitable, 'ae_page_objects/concerns/visitable'
34
+ end
35
+
36
+ autoload :Node, 'ae_page_objects/node'
37
+ autoload :Document, 'ae_page_objects/document'
38
+ autoload :Element, 'ae_page_objects/element'
39
+ autoload :ElementProxy, 'ae_page_objects/element_proxy'
40
+
41
+ autoload :HasOne, 'ae_page_objects/elements/has_one'
42
+ autoload :Collection, 'ae_page_objects/elements/collection'
43
+ autoload :Form, 'ae_page_objects/elements/form'
44
+ autoload :Select, 'ae_page_objects/elements/select'
45
+ autoload :Checkbox, 'ae_page_objects/elements/checkbox'
46
+ end
47
+
48
+ ActiveSupport::Dependencies.extend AePageObjects::DependenciesHook
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
@@ -0,0 +1,30 @@
1
+ module AePageObjects
2
+ class LoadingFailed < StandardError
3
+ end
4
+
5
+ module Concerns
6
+ module LoadEnsuring
7
+ extend ActiveSupport::Concern
8
+
9
+ def initialize(*args)
10
+ super
11
+ ensure_loaded!
12
+ end
13
+
14
+ private
15
+
16
+ def loaded_locator
17
+ end
18
+
19
+ def ensure_loaded!
20
+ if locator = loaded_locator
21
+ find(*eval_locator(locator))
22
+ end
23
+
24
+ self
25
+ rescue Capybara::ElementNotFound => e
26
+ raise LoadingFailed, e.message
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ module AePageObjects
2
+ class StalePageObject < StandardError
3
+ end
4
+
5
+ module Concerns
6
+ module Staleable
7
+ extend ActiveSupport::Concern
8
+
9
+ def stale?
10
+ @stale
11
+ end
12
+
13
+ def node
14
+ if stale?
15
+ raise StalePageObject, "Can't access stale page object '#{self}'"
16
+ end
17
+
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def stale!
24
+ @stale = true
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ module AePageObjects
2
+ class PathNotResolvable < StandardError
3
+ end
4
+
5
+ module Concerns
6
+ module Visitable
7
+ extend ActiveSupport::Concern
8
+
9
+ private
10
+
11
+ def ensure_loaded!
12
+ unless self.class.can_load_from_current_url?
13
+ raise LoadingFailed, "#{self.class.name} cannot be loaded with url '#{current_url_without_params}'"
14
+ end
15
+
16
+ super
17
+ end
18
+
19
+ module VisitMethod
20
+ def visit(*args)
21
+ raise ArgumentError, "Cannot pass block to visit()" if block_given?
22
+
23
+ full_path = application.generate_path(paths.first, *args)
24
+ raise PathNotResolvable, "#{self.name} not visitable via #{paths.first}(#{args.inspect})" unless full_path
25
+
26
+ Capybara.current_session.visit(full_path)
27
+ new
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+
33
+ def can_load_from_current_url?
34
+ return true if paths.empty?
35
+
36
+ Capybara.current_session.wait_until do
37
+ url = current_url_without_params
38
+
39
+ paths.any? do |path|
40
+ application.path_recognizes_url?(path, url)
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def paths
48
+ @paths ||= []
49
+ end
50
+
51
+ def path(path_method)
52
+ raise ArgumentError, "path must be a symbol or string" if ! path_method.is_a?(Symbol) && ! path_method.is_a?(String)
53
+
54
+ paths << path_method
55
+
56
+ extend VisitMethod
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,85 @@
1
+ require "active_support/dependencies"
2
+
3
+ module AePageObjects
4
+ class Application
5
+ include Configurable
6
+
7
+ class << self
8
+ attr_accessor :called_from
9
+ private :new
10
+
11
+ def inherited(base)
12
+ super
13
+
14
+ base.called_from = begin
15
+ call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
16
+ File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] })
17
+ end
18
+
19
+ base.parent.send(:include, ConstantResolver)
20
+ end
21
+
22
+ def initialize!
23
+ instance.initialize!
24
+ end
25
+ end
26
+
27
+ def initialize
28
+ set_autoload_paths
29
+ end
30
+
31
+ def config
32
+ @config ||= Configuration.new(self, find_root_with_flag("config.ru", Dir.pwd))
33
+ end
34
+
35
+ def initialize!
36
+ eager_load!
37
+ end
38
+
39
+ delegate :path_recognizes_url?, :to => :router
40
+ delegate :generate_path, :to => :router
41
+
42
+ def all_autoload_paths
43
+ @all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths).uniq
44
+ end
45
+
46
+ private
47
+
48
+ def router
49
+ @router ||= config.router
50
+ end
51
+
52
+ def set_autoload_paths
53
+ ActiveSupport::Dependencies.autoload_paths.unshift(*all_autoload_paths)
54
+
55
+ # Freeze so future modifications will fail rather than do nothing mysteriously
56
+ config.autoload_paths.freeze
57
+ config.eager_load_paths.freeze
58
+ end
59
+
60
+ def eager_load!
61
+ config.eager_load_paths.each do |load_path|
62
+ matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
63
+
64
+ Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
65
+ dependency_name = file.sub(matcher, '\1')
66
+ require_dependency dependency_name
67
+ end
68
+ end
69
+ end
70
+
71
+ def find_root_with_flag(flag, default=nil)
72
+ root_path = self.class.called_from
73
+
74
+ while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}")
75
+ parent = File.dirname(root_path)
76
+ root_path = parent != root_path && parent
77
+ end
78
+
79
+ root = File.exist?("#{root_path}/#{flag}") ? root_path : default
80
+ raise "Could not find root path for #{self}" unless root
81
+
82
+ Pathname.new(root).realpath
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,60 @@
1
+ module AePageObjects
2
+ class ApplicationRouter
3
+ def path_recognizes_url?(path, url)
4
+ if path.is_a?(String)
5
+ path.sub(/\/$/, '') == url.sub(/\/$/, '')
6
+ elsif path.is_a?(Symbol)
7
+ url, router = url_and_router(url)
8
+
9
+ ["GET", "PUT", "POST", "DELETE", "PATCH"].each do |method|
10
+ router.recognize(request_for(url, method)) do |route, matches, params|
11
+ return true if route.name.to_s == path.to_s
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def generate_path(named_route, *args)
18
+ return named_route if named_route.is_a?(String)
19
+
20
+ if routes.respond_to?("#{named_route}_path")
21
+ routes.send("#{named_route}_path", *args)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def request_for(url, method)
28
+ Rails.application.routes.request_class.new(env_for(url, method))
29
+ end
30
+
31
+ def env_for(url, method)
32
+ begin
33
+ Rack::MockRequest.env_for(url, {:method => method})
34
+ rescue URI::InvalidURIError => e
35
+ raise ActionController::RoutingError, e.message
36
+ end
37
+ end
38
+
39
+ def url_and_router(url)
40
+ if Rails.version =~ /^3.[01]/
41
+ url = Rack::Mount::Utils.normalize_path(url) unless url =~ %r{://}
42
+ router = Rails.application.routes.set
43
+ else
44
+ url = Journey::Router::Utils.normalize_path(url) unless url =~ %r{://}
45
+ router = Rails.application.routes.router
46
+ end
47
+
48
+ [url, router]
49
+ end
50
+
51
+ def routes
52
+ @routes ||= begin
53
+ routes_class = Class.new do
54
+ include Rails.application.routes.url_helpers
55
+ end
56
+ routes_class.new
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ module AePageObjects
2
+ module Configurable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ delegate :config, :to => :instance
7
+
8
+ def instance
9
+ @instance ||= new
10
+ end
11
+
12
+ def respond_to?(*args)
13
+ super || instance.respond_to?(*args)
14
+ end
15
+
16
+ def configure(&block)
17
+ class_eval(&block)
18
+ end
19
+
20
+ protected
21
+
22
+ def method_missing(*args, &block)
23
+ instance.send(*args, &block)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ require 'rails/paths'
2
+
3
+ module AePageObjects
4
+ class Configuration
5
+ attr_writer :router
6
+
7
+ def initialize(application, root)
8
+ @application = application
9
+ @root = root
10
+ end
11
+
12
+ def paths
13
+ @paths ||= begin
14
+ paths = Rails::Paths::Root.new(@root)
15
+ paths.add "test/page_objects", :eager_load => true
16
+ paths
17
+ end
18
+ end
19
+
20
+ def eager_load_paths
21
+ @eager_load_paths ||= paths.eager_load
22
+ end
23
+
24
+ def autoload_paths
25
+ @autoload_paths ||= paths.autoload_paths
26
+ end
27
+
28
+ def router
29
+ @router ||= ApplicationRouter.new
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,33 @@
1
+ module AePageObjects
2
+ module ConstantResolver
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def const_name_for(from_mod, const_name)
7
+ name_within_universe = ""
8
+ if self != from_mod
9
+ name_within_universe = from_mod.name.split("#{self.name}::")[1]
10
+ end
11
+
12
+ name_within_universe += "::#{const_name}"
13
+ end
14
+
15
+ def load_missing_constant(from_mod, const_name)
16
+ path_for_constant = self.const_name_for(from_mod, const_name).underscore
17
+
18
+ application = "#{self.name}::Application".constantize
19
+ application.all_autoload_paths.each do |autoload_path|
20
+ file_path = File.join(autoload_path, "#{path_for_constant}.rb").sub(/(\.rb)?$/, ".rb")
21
+
22
+ if File.file?(file_path) && ! ActiveSupport::Dependencies.loaded.include?(File.expand_path(file_path))
23
+ ActiveSupport::Dependencies.require_or_load file_path
24
+ raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless ActiveSupport::Dependencies.local_const_defined?(from_mod, const_name)
25
+ return from_mod.const_get(const_name)
26
+ end
27
+ end
28
+
29
+ nil
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ module AePageObjects
2
+ module DependenciesHook
3
+
4
+ def self.containing_page_object_universe(from_mod)
5
+ until from_mod == Object
6
+ if from_mod < AePageObjects::ConstantResolver
7
+ return from_mod
8
+ end
9
+
10
+ from_mod = from_mod.parent
11
+ end
12
+
13
+ nil
14
+ end
15
+
16
+ def load_missing_constant(from_mod, const_name)
17
+ page_objects = DependenciesHook.containing_page_object_universe(from_mod)
18
+ page_objects && page_objects.load_missing_constant(from_mod, const_name) || super
19
+ end
20
+ end
21
+ end