page_ez 0.1.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. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +3 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +148 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +575 -0
  9. data/Rakefile +10 -0
  10. data/lib/page_ez/configuration.rb +33 -0
  11. data/lib/page_ez/delegates_to.rb +25 -0
  12. data/lib/page_ez/errors.rb +17 -0
  13. data/lib/page_ez/has_many_result.rb +35 -0
  14. data/lib/page_ez/has_one_result.rb +14 -0
  15. data/lib/page_ez/method_generators/define_has_many_result_methods.rb +48 -0
  16. data/lib/page_ez/method_generators/define_has_one_predicate_methods.rb +42 -0
  17. data/lib/page_ez/method_generators/define_has_one_result_methods.rb +35 -0
  18. data/lib/page_ez/method_generators/has_many_dynamic_selector.rb +39 -0
  19. data/lib/page_ez/method_generators/has_many_ordered_dynamic_selector.rb +39 -0
  20. data/lib/page_ez/method_generators/has_many_ordered_selector.rb +47 -0
  21. data/lib/page_ez/method_generators/has_many_static_selector.rb +41 -0
  22. data/lib/page_ez/method_generators/has_one_composed_class.rb +40 -0
  23. data/lib/page_ez/method_generators/has_one_dynamic_selector.rb +39 -0
  24. data/lib/page_ez/method_generators/has_one_static_selector.rb +25 -0
  25. data/lib/page_ez/method_generators/identity_processor.rb +11 -0
  26. data/lib/page_ez/null_logger.rb +12 -0
  27. data/lib/page_ez/options.rb +37 -0
  28. data/lib/page_ez/page.rb +162 -0
  29. data/lib/page_ez/page_visitor.rb +72 -0
  30. data/lib/page_ez/parameters.rb +54 -0
  31. data/lib/page_ez/pluralization.rb +25 -0
  32. data/lib/page_ez/selector_evaluator.rb +76 -0
  33. data/lib/page_ez/version.rb +5 -0
  34. data/lib/page_ez/visitors/debug_visitor.rb +59 -0
  35. data/lib/page_ez/visitors/depth_visitor.rb +44 -0
  36. data/lib/page_ez/visitors/macro_pluralization_visitor.rb +70 -0
  37. data/lib/page_ez/visitors/matcher_collision_visitor.rb +75 -0
  38. data/lib/page_ez/visitors/registered_name_visitor.rb +100 -0
  39. data/lib/page_ez.rb +41 -0
  40. data/page_ez.gemspec +43 -0
  41. metadata +238 -0
@@ -0,0 +1,14 @@
1
+ module PageEz
2
+ class HasOneResult
3
+ include DelegatesTo[:@result]
4
+
5
+ def initialize(container:, selector:, options:, constructor:)
6
+ @result = constructor.call(
7
+ container.find(
8
+ selector,
9
+ **options
10
+ )
11
+ )
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ require_relative "identity_processor"
2
+
3
+ module PageEz
4
+ module MethodGenerators
5
+ class DefineHasManyResultMethods
6
+ def initialize(name, evaluator_class:, constructor:, processor: IdentityProcessor)
7
+ @name = name
8
+ @evaluator_class = evaluator_class
9
+ @constructor = constructor
10
+ @processor = processor
11
+ end
12
+
13
+ def run(target)
14
+ name = @name
15
+ evaluator_class = @evaluator_class
16
+ constructor = @constructor
17
+ processor = @processor
18
+
19
+ target.logged_define_method(name) do |*args|
20
+ evaluator = evaluator_class.run(processor.run_args(args), target: self)
21
+
22
+ selector = processor.selector(evaluator.selector, args)
23
+
24
+ PageEz.reraise_selector_error(selector) do
25
+ PageEz::HasManyResult.new(
26
+ container: container,
27
+ selector: selector,
28
+ options: evaluator.options,
29
+ constructor: constructor.method(:new)
30
+ )
31
+ end
32
+ end
33
+
34
+ target.logged_define_method("has_#{name}_count?") do |count, *args|
35
+ send(name, *args).has_count_of?(count)
36
+ end
37
+
38
+ target.logged_define_method("has_#{name}?") do |*args|
39
+ send(name, *args).has_any_elements?
40
+ end
41
+
42
+ target.logged_define_method("has_no_#{name}?") do |*args|
43
+ send(name, *args).has_no_elements?
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class DefineHasOnePredicateMethods
4
+ def initialize(name, evaluator_class:, processor: IdentityProcessor)
5
+ @name = name
6
+ @evaluator_class = evaluator_class
7
+ @processor = processor
8
+ end
9
+
10
+ def run(target)
11
+ evaluator_class = @evaluator_class
12
+ processor = @processor
13
+
14
+ target.logged_define_method("has_#{@name}?") do |*args|
15
+ evaluator = evaluator_class.run(processor.run_args(args), target: self)
16
+
17
+ selector = processor.selector(evaluator.selector, args)
18
+
19
+ PageEz.reraise_selector_error(selector) do
20
+ has_css?(
21
+ selector,
22
+ **evaluator.options
23
+ )
24
+ end
25
+ end
26
+
27
+ target.logged_define_method("has_no_#{@name}?") do |*args|
28
+ evaluator = evaluator_class.run(processor.run_args(args), target: self)
29
+
30
+ selector = processor.selector(evaluator.selector, args)
31
+
32
+ PageEz.reraise_selector_error(selector) do
33
+ has_no_css?(
34
+ selector,
35
+ **evaluator.options
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ require_relative "identity_processor"
2
+
3
+ module PageEz
4
+ module MethodGenerators
5
+ class DefineHasOneResultMethods
6
+ def initialize(name, evaluator_class:, constructor:, processor: IdentityProcessor)
7
+ @name = name
8
+ @evaluator_class = evaluator_class
9
+ @processor = processor
10
+ @constructor = constructor
11
+ end
12
+
13
+ def run(target)
14
+ evaluator_class = @evaluator_class
15
+ processor = @processor
16
+ constructor = @constructor
17
+
18
+ target.logged_define_method(@name) do |*args|
19
+ evaluator = evaluator_class.run(processor.run_args(args), target: self)
20
+
21
+ selector = processor.selector(evaluator.selector, args)
22
+
23
+ PageEz.reraise_selector_error(selector) do
24
+ PageEz::HasOneResult.new(
25
+ container: container,
26
+ selector: selector,
27
+ options: evaluator.options,
28
+ constructor: constructor.method(:new)
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class HasManyDynamicSelector
4
+ attr_reader :selector
5
+
6
+ def initialize(name, options, &block)
7
+ @run = false
8
+ @name = name
9
+ @options = options
10
+ @block = block
11
+ end
12
+
13
+ def run(target)
14
+ return if run?
15
+
16
+ if target.method_defined?(@name)
17
+ target.rename_method from: @name, to: :"_#{@name}"
18
+ @run = true
19
+
20
+ @selector = target.instance_method(:"_#{@name}")
21
+ else
22
+ @selector = @name.to_s
23
+ end
24
+
25
+ HasManyStaticSelector.new(@name, @selector, nil, @options, &@block).run(target)
26
+ end
27
+
28
+ def selector_type
29
+ :dynamic
30
+ end
31
+
32
+ private
33
+
34
+ def run?
35
+ @run
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class HasManyOrderedDynamicSelector
4
+ attr_reader :selector
5
+
6
+ def initialize(name, options, &block)
7
+ @run = false
8
+ @name = name
9
+ @options = options
10
+ @block = block
11
+ end
12
+
13
+ def run(target)
14
+ return if run?
15
+
16
+ if target.method_defined?(@name)
17
+ target.rename_method from: @name, to: :"_#{@name}"
18
+ @run = true
19
+
20
+ @selector = target.instance_method(:"_#{@name}")
21
+ else
22
+ @selector = @name.to_s
23
+ end
24
+
25
+ HasManyOrderedSelector.new(@name, @selector, nil, @options, &@block).run(target)
26
+ end
27
+
28
+ def selector_type
29
+ :dynamic
30
+ end
31
+
32
+ private
33
+
34
+ def run?
35
+ @run
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class HasManyOrderedSelector
4
+ attr_reader :selector
5
+
6
+ def initialize(name, selector, dynamic_options, options, &block)
7
+ @name = name
8
+ @selector = selector
9
+ @core_selector = HasManyStaticSelector.new(name, selector, dynamic_options, options, &block)
10
+ @evaluator_class = SelectorEvaluator.build(@name, dynamic_options: dynamic_options, options: options, selector: selector)
11
+ end
12
+
13
+ def run(target)
14
+ singularized_name = Pluralization.new(@name).singularize
15
+
16
+ constructor = @core_selector.run(target)
17
+
18
+ DefineHasOneResultMethods.new(
19
+ "#{singularized_name}_at",
20
+ evaluator_class: @evaluator_class,
21
+ constructor: constructor,
22
+ processor: IndexedProcessor
23
+ ).run(target)
24
+
25
+ DefineHasOnePredicateMethods.new(
26
+ "#{singularized_name}_at",
27
+ evaluator_class: @evaluator_class,
28
+ processor: IndexedProcessor
29
+ ).run(target)
30
+ end
31
+
32
+ def selector_type
33
+ @core_selector.selector_type
34
+ end
35
+
36
+ class IndexedProcessor
37
+ def self.run_args(args)
38
+ args[1..]
39
+ end
40
+
41
+ def self.selector(selector, args)
42
+ "#{selector}:nth-of-type(#{args[0] + 1})"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class HasManyStaticSelector
4
+ attr_reader :selector
5
+
6
+ def initialize(name, selector, dynamic_options, options, &block)
7
+ @name = name
8
+ @selector = selector
9
+ @block = block
10
+ @evaluator_class = SelectorEvaluator.build(name, dynamic_options: dynamic_options, options: options, selector: selector)
11
+ end
12
+
13
+ def run(target)
14
+ target.constructor_from_block(&@block).tap do |constructor|
15
+ DefineHasManyResultMethods.new(
16
+ @name,
17
+ evaluator_class: @evaluator_class,
18
+ constructor: constructor
19
+ ).run(target)
20
+
21
+ singularized_name = Pluralization.new(@name).singularize
22
+
23
+ DefineHasOneResultMethods.new(
24
+ "#{singularized_name}_matching",
25
+ evaluator_class: @evaluator_class,
26
+ constructor: constructor
27
+ ).run(target)
28
+
29
+ DefineHasOnePredicateMethods.new(
30
+ "#{singularized_name}_matching",
31
+ evaluator_class: @evaluator_class
32
+ ).run(target)
33
+ end
34
+ end
35
+
36
+ def selector_type
37
+ :static
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class HasOneComposedClass
4
+ attr_reader :selector
5
+
6
+ def initialize(name, composed_class, options, &block)
7
+ @name = name
8
+ @composed_class = composed_class
9
+ @options = options
10
+ @block = block
11
+ end
12
+
13
+ def run(target)
14
+ constructor = target.constructor_from_block(@composed_class, &@block)
15
+
16
+ base_selector = @options.delete(:base_selector)
17
+
18
+ target.logged_define_method(@name) do |*args|
19
+ if base_selector
20
+ Class.new(constructor).tap do |new_constructor|
21
+ new_constructor.base_selector base_selector
22
+ end.new(self)
23
+ else
24
+ constructor.new(self)
25
+ end
26
+ end
27
+
28
+ if base_selector
29
+ evaluator_class = SelectorEvaluator.build(
30
+ @name,
31
+ dynamic_options: nil,
32
+ options: @options,
33
+ selector: base_selector
34
+ )
35
+ DefineHasOnePredicateMethods.new(@name, evaluator_class: evaluator_class).run(target)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class HasOneDynamicSelector
4
+ attr_reader :selector
5
+
6
+ def initialize(name, options, &block)
7
+ @run = false
8
+ @name = name
9
+ @options = options
10
+ @block = block
11
+ end
12
+
13
+ def run(target)
14
+ return if run?
15
+
16
+ if target.method_defined?(@name)
17
+ target.rename_method from: @name, to: :"_#{@name}"
18
+ @run = true
19
+
20
+ @selector = target.instance_method(:"_#{@name}")
21
+ else
22
+ @selector = @name.to_s
23
+ end
24
+
25
+ HasOneStaticSelector.new(@name, @selector, nil, @options, &@block).run(target)
26
+ end
27
+
28
+ def selector_type
29
+ :dynamic
30
+ end
31
+
32
+ private
33
+
34
+ def run?
35
+ @run
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module PageEz
2
+ module MethodGenerators
3
+ class HasOneStaticSelector
4
+ attr_reader :selector
5
+
6
+ def initialize(name, selector, dynamic_options, options, &block)
7
+ @name = name
8
+ @selector = selector
9
+ @evaluator_class = SelectorEvaluator.build(name, dynamic_options: dynamic_options, options: options, selector: selector)
10
+ @block = block
11
+ end
12
+
13
+ def run(target)
14
+ constructor = target.constructor_from_block(&@block)
15
+
16
+ DefineHasOneResultMethods.new(@name, evaluator_class: @evaluator_class, constructor: constructor).run(target)
17
+ DefineHasOnePredicateMethods.new(@name, evaluator_class: @evaluator_class).run(target)
18
+ end
19
+
20
+ def selector_type
21
+ :static
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module PageEz::MethodGenerators
2
+ class IdentityProcessor
3
+ def self.run_args(args)
4
+ args
5
+ end
6
+
7
+ def self.selector(value, _)
8
+ value
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module PageEz
2
+ class NullLogger
3
+ def debug(*)
4
+ end
5
+
6
+ def info(*)
7
+ end
8
+
9
+ def warn(*)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ require "active_support/core_ext/hash/keys"
2
+
3
+ module PageEz
4
+ class Options
5
+ def self.merge(options, dynamic_options = nil, *args)
6
+ dynamic_options ||= -> { {} }
7
+
8
+ keys_to_extract = dynamic_options.parameters.filter_map do |type, name|
9
+ if type == :keyreq || type == :key
10
+ name
11
+ end
12
+ end
13
+
14
+ if args.last.is_a?(Hash)
15
+ if dynamic_options.arity == 0
16
+ options.merge(*args)
17
+ else
18
+ kwargs = args.pop
19
+
20
+ if keys_to_extract.empty?
21
+ options.merge(dynamic_options.call(*args, **kwargs))
22
+ else
23
+ sliced = kwargs.slice(*keys_to_extract)
24
+ except = kwargs.except(*keys_to_extract)
25
+ options.merge(
26
+ dynamic_options.call(*args, **sliced)
27
+ ).merge(
28
+ except
29
+ )
30
+ end
31
+ end
32
+ else
33
+ options.merge(dynamic_options.call(*args))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,162 @@
1
+ require "capybara/dsl"
2
+ require "active_support/core_ext/class/attribute"
3
+ require "active_support/core_ext/module/delegation"
4
+
5
+ module PageEz
6
+ class Page
7
+ include DelegatesTo[:container]
8
+ class_attribute :visitor, :macro_registrar, :nested_macro, :container_base_selector
9
+
10
+ self.visitor = PageVisitor.new
11
+ self.macro_registrar = {}
12
+ self.nested_macro = false
13
+ self.container_base_selector = nil
14
+
15
+ undef_method :select
16
+
17
+ def container
18
+ if container_base_selector
19
+ @container.find(container_base_selector)
20
+ else
21
+ @container
22
+ end
23
+ end
24
+
25
+ def self.base_selector(value)
26
+ self.container_base_selector = value
27
+ end
28
+
29
+ def self.contains(page_object, only: nil)
30
+ delegation_target = :"__page_object_#{page_object.object_id}__"
31
+
32
+ has_one(delegation_target, page_object)
33
+
34
+ if only
35
+ methods_delegated_that_do_not_exist = only - page_object.instance_methods(false)
36
+ if methods_delegated_that_do_not_exist.any?
37
+ raise NoMethodError, "Attempting to delegate non-existent method(s) to #{page_object}: #{methods_delegated_that_do_not_exist.join(", ")}"
38
+ end
39
+ end
40
+
41
+ delegate(*(only || page_object.instance_methods(false)), to: delegation_target)
42
+ end
43
+
44
+ def initialize(container = nil)
45
+ @container = container || Class.new do
46
+ include Capybara::DSL
47
+ end.new
48
+ end
49
+
50
+ def self.method_added(name)
51
+ visitor.track_method_added(name, macro_registrar[name])
52
+
53
+ if macro_registrar.key?(name)
54
+ macro_registrar[name].run(self)
55
+ end
56
+ end
57
+
58
+ def self.delegate(...)
59
+ super(...).tap do |method_names|
60
+ method_names.each do |method_name|
61
+ visitor.track_method_delegated(method_name)
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.method_undefined(name)
67
+ visitor.track_method_undefined(name)
68
+ end
69
+
70
+ def self.rename_method(from:, to:)
71
+ alias_method to, from
72
+ undef_method from
73
+ visitor.track_method_renamed(from, to)
74
+ end
75
+
76
+ def self.has_one(name, *args, **options, &block)
77
+ construction_strategy = case [args.length, args.first]
78
+ in [2, _] then
79
+ MethodGenerators::HasOneStaticSelector.new(name, args.first.to_s, args[1], options, &block)
80
+ in [1, Class] then
81
+ MethodGenerators::HasOneComposedClass.new(name, args.first, options, &block)
82
+ in [1, String] | [1, Symbol] then
83
+ MethodGenerators::HasOneStaticSelector.new(name, args.first.to_s, nil, options, &block)
84
+ in [0, _] then
85
+ MethodGenerators::HasOneDynamicSelector.new(name, options, &block)
86
+ end
87
+
88
+ visitor.process_macro(:has_one, name, construction_strategy)
89
+
90
+ construction_strategy.run(self)
91
+
92
+ self.macro_registrar = macro_registrar.merge(name => construction_strategy)
93
+ end
94
+
95
+ def self.has_many(name, *args, **options, &block)
96
+ construction_strategy = case [args.length, args.first]
97
+ in [2, _] then
98
+ MethodGenerators::HasManyStaticSelector.new(name, args.first.to_s, args[1], options, &block)
99
+ in [1, String] | [1, Symbol] then
100
+ MethodGenerators::HasManyStaticSelector.new(name, args.first.to_s, nil, options, &block)
101
+ in [0, _] then
102
+ MethodGenerators::HasManyDynamicSelector.new(name, options, &block)
103
+ end
104
+
105
+ visitor.process_macro(:has_many, name, construction_strategy)
106
+
107
+ construction_strategy.run(self)
108
+
109
+ self.macro_registrar = macro_registrar.merge(name => construction_strategy)
110
+ end
111
+
112
+ def self.has_many_ordered(name, *args, **options, &block)
113
+ construction_strategy = case [args.length, args.first]
114
+ in [2, _] then
115
+ MethodGenerators::HasManyOrderedSelector.new(name, args.first.to_s, args[1], options, &block)
116
+ in [1, String] | [1, Symbol] then
117
+ MethodGenerators::HasManyOrderedSelector.new(name, args.first.to_s, nil, options, &block)
118
+ in [0, _] then
119
+ MethodGenerators::HasManyOrderedDynamicSelector.new(name, options, &block)
120
+ end
121
+
122
+ visitor.process_macro(:has_many_ordered, name, construction_strategy)
123
+
124
+ construction_strategy.run(self)
125
+
126
+ self.macro_registrar = macro_registrar.merge(name => construction_strategy)
127
+ end
128
+
129
+ def self.inherited(subclass)
130
+ if !nested_macro
131
+ visitor.reset
132
+ end
133
+
134
+ visitor.inherit_from(subclass)
135
+ end
136
+
137
+ def self.constructor_from_block(superclass = nil, &block)
138
+ if block
139
+ self.nested_macro = true
140
+ Class.new(superclass || self).tap do |klass|
141
+ visitor.begin_block_evaluation
142
+ klass.macro_registrar = {}
143
+ klass.class_eval(&block)
144
+ visitor.end_block_evaluation
145
+ end
146
+ elsif superclass
147
+ superclass
148
+ else
149
+ Class.new(BasicObject) do
150
+ def self.new(value)
151
+ value
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ def self.logged_define_method(name, &block)
158
+ visitor.define_method(name)
159
+ define_method(name, &block)
160
+ end
161
+ end
162
+ end