aygabtu 0.2.0

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