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,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