aygabtu 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +245 -0
- data/Rakefile +10 -0
- data/aygabtu.gemspec +23 -0
- data/lib/aygabtu.rb +5 -0
- data/lib/aygabtu/generator.rb +64 -0
- data/lib/aygabtu/handle.rb +38 -0
- data/lib/aygabtu/point_of_call.rb +28 -0
- data/lib/aygabtu/route_mark.rb +18 -0
- data/lib/aygabtu/route_wrapper.rb +96 -0
- data/lib/aygabtu/rspec.rb +119 -0
- data/lib/aygabtu/scope/action.rb +38 -0
- data/lib/aygabtu/scope/base.rb +85 -0
- data/lib/aygabtu/scope/named.rb +38 -0
- data/lib/aygabtu/scope/namespace_controller.rb +50 -0
- data/lib/aygabtu/scope/remaining.rb +27 -0
- data/lib/aygabtu/scope/requiring.rb +26 -0
- data/lib/aygabtu/scope/static_dynamic.rb +33 -0
- data/lib/aygabtu/scope/visiting_with.rb +19 -0
- data/lib/aygabtu/scope_actor.rb +104 -0
- data/lib/aygabtu/scope_chain.rb +39 -0
- data/lib/aygabtu/version.rb +3 -0
- data/spec/actions_spec.rb +152 -0
- data/spec/example_spec.rb +61 -0
- data/spec/lib/route_wrapper_spec.rb +140 -0
- data/spec/matching_routes_spec.rb +384 -0
- data/spec/nesting_spec.rb +59 -0
- data/spec/rails_application_helper.rb +8 -0
- data/spec/support/aygabtu_sees_routes.rb +17 -0
- data/spec/support/identifies_routes.rb +18 -0
- data/spec/support/invokes_rspec.rb +93 -0
- data/spec/support/matcher_shims.rb +39 -0
- data/spec/support_spec/identifies_routes_spec.rb +49 -0
- data/spec/visiting_routes_spec.rb +57 -0
- metadata +123 -0
@@ -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
|