aygabtu 0.2.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,38 @@
1
+ require_relative 'route_wrapper'
2
+ require_relative 'scope_actor'
3
+
4
+ module Aygabtu
5
+ class Handle
6
+
7
+ def routes
8
+ @routes ||= rails_application_routes.set.map do |journey_route|
9
+ RouteWrapper.new(journey_route)
10
+ end.select(&:get?).reject(&:internal?)
11
+ end
12
+
13
+ def checkpoint
14
+ @checkpoint || 0
15
+ end
16
+
17
+ def generate_checkpoint
18
+ @checkpoint = (checkpoint + 1).tap do |new_checkpoint|
19
+ puts "New checkpoint #{new_checkpoint}" if verbose?
20
+ end
21
+ end
22
+
23
+ def verbose!
24
+ @verbose = true
25
+ end
26
+
27
+ def verbose?
28
+ !!@verbose
29
+ end
30
+
31
+ private
32
+
33
+ def rails_application_routes
34
+ @rails_application_routes ||= Rails.application.routes
35
+ end
36
+ attr_writer :rails_application_routes
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'scope/base'
2
+
3
+ module Aygabtu
4
+
5
+ module PointOfCall
6
+ extend self
7
+
8
+ def point_of_call
9
+ caller.drop_while { |point| point.start_with?(PointOfCall.gem_root) }.first
10
+ end
11
+
12
+ def file_and_line_at_point_of_call
13
+ filename, line_and_context = point_of_call.split(':', 2)
14
+ [filename, line_and_context.to_i] # make use of the fact to_i tolerates being passed a string only _beginning_ with a number
15
+ end
16
+
17
+ def self.gem_root
18
+ return @gem_root if @gem_root
19
+
20
+ path = Pathname(__FILE__)
21
+ while new_path = path.parent and new_path.to_s.include?('lib/aygabtu')
22
+ path = new_path
23
+ end
24
+
25
+ @gem_root= path.to_s
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module Aygabtu
2
+
3
+ class RouteMark
4
+ def initialize(action, poc, checkpoint)
5
+ @action, @poc, @checkpoint = action, poc, checkpoint
6
+ end
7
+ attr_reader :action, :poc, :checkpoint
8
+
9
+ def conflicting?(other)
10
+ !(action == :visit && other.action == :visit)
11
+ end
12
+
13
+ def description
14
+ "#{action} action at #{poc} (CP #{checkpoint})"
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,96 @@
1
+ module Aygabtu
2
+ class RouteWrapper
3
+ # Wraps a Journey route
4
+
5
+ attr_reader :journey_route # ease debugging
6
+
7
+ def initialize(journey_route)
8
+ @journey_route = journey_route
9
+
10
+ @marks = []
11
+ end
12
+
13
+ def conflicting_marks(mark)
14
+ @marks.select { |m| m.conflicting?(mark) }
15
+ end
16
+
17
+ attr_reader :marks
18
+
19
+ def get?
20
+ @journey_route.verb.match('GET')
21
+ end
22
+
23
+ def internal?
24
+ # this particular route is hard to filter by any sensible criterion
25
+ @journey_route.path.to_regexp == %r{\A/assets}
26
+ end
27
+
28
+ # array of parameter names (symbols) required for generating URL
29
+ def required_parts
30
+ @journey_route.required_parts
31
+ end
32
+
33
+ def controller
34
+ @journey_route.requirements[:controller]
35
+ end
36
+
37
+ def controller_namespace
38
+ return @controller_namespace if defined? @controller_namespace
39
+ return @controller_namespace = nil unless controller
40
+
41
+ @controller_namespace = Pathname('/').join(controller).dirname.to_s[1..-1]
42
+ @controller_namespace = nil if @controller_namespace.empty?
43
+ @controller_namespace
44
+ end
45
+
46
+ def controller_basename
47
+ Pathname(controller).basename.to_s if controller
48
+ end
49
+
50
+ def action
51
+ # sanity condition needed for Rails 4.1
52
+ @journey_route.requirements[:action] if controller
53
+ end
54
+
55
+ def name
56
+ @journey_route.name.to_s if @journey_route.name
57
+ end
58
+
59
+ # def matches_string?(string)
60
+ # @journey_route.path.to_regexp.match(string)
61
+ # end
62
+
63
+ # this assumes parameters.keys == required_parts
64
+ def generate_url_with_proper_parameters(parameters)
65
+ @journey_route.format(parameters)
66
+ end
67
+
68
+ def inspect
69
+ if @journey_route.name
70
+ "route named :#{@journey_route.name}"
71
+ else
72
+ "route matching #{@journey_route.path.to_regexp.inspect}"
73
+ end
74
+ end
75
+
76
+ def format(visiting_data)
77
+ visiting_data = visiting_data.stringify_keys
78
+
79
+ query_data = visiting_data.except(*@journey_route.parts.map(&:to_s))
80
+ visiting_data = visiting_data.except(*query_data.keys)
81
+
82
+ visiting_data.symbolize_keys! # format expects symbols, but we deal with strings in all other places
83
+ path = @journey_route.format(visiting_data)
84
+
85
+ if query_data.empty?
86
+ path
87
+ else
88
+ "#{path}?#{Rack::Utils.build_query(query_data)}"
89
+ end
90
+ end
91
+
92
+ def really_required_keys
93
+ @journey_route.path.required_names
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,119 @@
1
+ require_relative 'handle'
2
+ require_relative 'scope/base'
3
+ require_relative 'scope_chain'
4
+ require_relative 'point_of_call'
5
+
6
+ module Aygabtu
7
+ module RSpec
8
+
9
+ module ExampleGroupMethods
10
+ delegate(*ScopeChain.scope_methods, to: :aygabtu_scope_chain)
11
+
12
+ def aygabtu_scope
13
+ @aygabtu_scope ||= if superclass.respond_to?(:aygabtu_scope)
14
+ superclass.aygabtu_scope
15
+ else
16
+ Scope::Base.blank_slate
17
+ end
18
+ end
19
+
20
+ def aygabtu_scope_chain
21
+ @aygabtu_scope_chain ||= ScopeChain.new(self, aygabtu_scope)
22
+ end
23
+
24
+ def aygabtu_enter_context(block, scope)
25
+ context "Context defined at #{PointOfCall.point_of_call}" do
26
+ self.aygabtu_scope = scope
27
+ class_exec(&block)
28
+ end
29
+ end
30
+
31
+ def aygabtu_action(action, scope, *args)
32
+ puts "Action #{action} after checkpoint #{aygabtu_handle.checkpoint} from #{PointOfCall.point_of_call}" if aygabtu_handle.verbose?
33
+ ScopeActor.new(scope, self).public_send(action, *args)
34
+ end
35
+
36
+ def aygabtu_handle
37
+ if superclass.respond_to?(:aygabtu_handle)
38
+ superclass.aygabtu_handle
39
+ else
40
+ @_aygabtu_handle ||= Handle.new
41
+ end
42
+ end
43
+
44
+ def aygabtu_matching_routes(scope = aygabtu_scope)
45
+ scope = scope.scope if scope.respond_to?(:scope) # a scope chain can be pased as well
46
+ aygabtu_handle.routes.select do |route|
47
+ scope.matches_route?(route)
48
+ end
49
+ end
50
+
51
+ ScopeActor.actions.each do |action|
52
+ define_method(action) do |*args|
53
+ aygabtu_action(action, aygabtu_scope, *args)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ attr_writer :aygabtu_scope
60
+ end
61
+
62
+ module ExampleGroupModule
63
+ def self.included(group)
64
+ group.extend ExampleGroupMethods
65
+ end
66
+
67
+ def aygabtu_example_for(path)
68
+ visit path
69
+ aygabtu_assertions
70
+ end
71
+
72
+ attr_accessor :aygabtu_path_to_visit
73
+
74
+ def aygabtu_pass_to_route(id, visiting_data)
75
+ route = self.class.aygabtu_handle.routes.find { |a_route| a_route.object_id == id }
76
+
77
+ visiting_data = visiting_data.clone
78
+ visiting_data.keys.each do |key|
79
+ value = visiting_data[key]
80
+ visiting_data[key] = aygabtu_fetch_symbolic_pass_value(value) if value.is_a?(Symbol)
81
+ end
82
+
83
+ missing_keys = route.really_required_keys - visiting_data.keys.map(&:to_s)
84
+
85
+ if missing_keys.empty?
86
+ route.format(visiting_data)
87
+ else
88
+ raise "Route is missing required key(s) #{missing_keys.map(&:inspect).join(', ')}"
89
+ end
90
+ end
91
+
92
+ def aygabtu_fetch_symbolic_pass_value(symbol)
93
+ raise "Symbolic pass value #{symbol} given, but no such method defined" unless respond_to?(symbol)
94
+ send(symbol)
95
+ end
96
+
97
+ def aygabtu_assert_status_success
98
+ expect(page.status_code).to be 200
99
+ end
100
+
101
+ def aygabtu_assert_not_redirected_away # @TODO create custom rspec matcher to provide cleaner error messages
102
+ uri = URI(current_url)
103
+ uri.host = nil
104
+ uri.scheme = nil
105
+ expect(uri.to_s).to be == aygabtu_path_to_visit
106
+ end
107
+
108
+ def aygabtu_assertions
109
+ raise "Hey aygabtu user, please implement this yourself by overriding the aygabtu_assertions method!"
110
+ end
111
+ end
112
+
113
+ class << self
114
+ def example_group_module
115
+ ExampleGroupModule
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,38 @@
1
+ module Aygabtu
2
+ module Scope
3
+ module Action
4
+ def action(*actions)
5
+ raise "nesting/chaining action scopes makes no sense" if @data[:action]
6
+
7
+ new_data = @data.dup.merge(actions: actions.map(&:to_s))
8
+ self.class.new(new_data)
9
+ end
10
+
11
+ def matches_route?(route)
12
+ if @data[:actions]
13
+ @data[:actions].include?(route.action)
14
+ else
15
+ true
16
+ end && super
17
+ end
18
+
19
+ def segments_split_once
20
+ return super unless Array(@data[:actions]).length > 1
21
+
22
+ @data[:actions].map do |action|
23
+ new_data = @data.merge(actions: [action])
24
+ self.class.new(new_data)
25
+ end
26
+ end
27
+
28
+ def inspect_data
29
+ return super unless actions = @data[:actions]
30
+ super.merge(action: actions.map(&:inspect).join('; '))
31
+ end
32
+
33
+ def self.factory_methods
34
+ [:action]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,85 @@
1
+ require_relative 'namespace_controller'
2
+ require_relative 'action'
3
+ require_relative 'named'
4
+ require_relative 'visiting_with'
5
+ require_relative 'requiring'
6
+ require_relative 'static_dynamic'
7
+ require_relative 'remaining'
8
+
9
+ module Aygabtu
10
+ module Scope
11
+ class Base
12
+ def initialize(data)
13
+ @data = data
14
+ end
15
+
16
+ def visiting_data
17
+ @data.fetch(:visiting_data, {})
18
+ end
19
+
20
+ def self.blank_slate
21
+ new(filters: [])
22
+ end
23
+
24
+ COMPONENTS = [
25
+ NamespaceController,
26
+ Action,
27
+ Named,
28
+ VisitingWith,
29
+ Requiring,
30
+ StaticDynamic,
31
+ Remaining
32
+ ]
33
+
34
+ module BasicBehaviour
35
+ # defines methods below COMPONENTS in the inheritance chain
36
+ # so components can override and call super
37
+
38
+ def matches_route?(route)
39
+ true
40
+ end
41
+
42
+ def segments_split_once
43
+ end
44
+
45
+ def inspect_data
46
+ {}
47
+ end
48
+ end
49
+ include BasicBehaviour
50
+
51
+ include(*COMPONENTS)
52
+
53
+ def segments
54
+ if split_once = segments_split_once
55
+ split_once.map(&:segments).reduce(:+)
56
+ else
57
+ [self]
58
+ end
59
+ end
60
+
61
+ def inspect
62
+ data = inspect_data
63
+ data.keys.each { |key| data.delete(key) if data[key].nil? }
64
+ message = if data.empty?
65
+ "nothing specified"
66
+ else
67
+ data.map { |key, value| "#{key}: #{value}" }.join(', ')
68
+ end
69
+ "\#<Aygabtu scope (#{message})>"
70
+ end
71
+
72
+ @factory_methods = COMPONENTS.map(&:factory_methods).reduce([], :+)
73
+
74
+ class << self
75
+ attr_reader :factory_methods
76
+ end
77
+
78
+ private
79
+
80
+ def inspected_or_nil(obj)
81
+ obj.inspect unless obj.nil?
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,38 @@
1
+ module Aygabtu
2
+ module Scope
3
+ module Named
4
+ def named(*names)
5
+ raise "nesting/chaining named in/after named makes no sense" if @data[:names]
6
+
7
+ new_data = @data.dup.merge(names: names.map(&:to_s))
8
+ self.class.new(new_data)
9
+ end
10
+
11
+ def matches_route?(route)
12
+ if @data[:names]
13
+ @data[:names].include?(route.name)
14
+ else
15
+ true
16
+ end && super
17
+ end
18
+
19
+ def segments_split_once
20
+ return super unless Array(@data[:names]).length > 1
21
+
22
+ @data[:names].map do |name|
23
+ new_data = @data.merge(names: [name])
24
+ self.class.new(new_data)
25
+ end
26
+ end
27
+
28
+ def inspect_data
29
+ return super unless names = @data[:names]
30
+ super.merge(name: names.map(&:inspect).join('; '))
31
+ end
32
+
33
+ def self.factory_methods
34
+ [:named]
35
+ end
36
+ end
37
+ end
38
+ end