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.
- 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,50 @@
|
|
1
|
+
module Aygabtu
|
2
|
+
module Scope
|
3
|
+
module NamespaceController
|
4
|
+
def namespace(name)
|
5
|
+
raise "nesting/chaining namespace in/after controller makes no sense" if @data[:controller]
|
6
|
+
raise "nesting/chaining namespace in/after action makes no sense" if @data[:action]
|
7
|
+
|
8
|
+
new_namespace = Pathname(@data[:namespace] || '').join(name.to_s).to_s
|
9
|
+
new_data = @data.dup.merge(namespace: new_namespace)
|
10
|
+
self.class.new(new_data)
|
11
|
+
end
|
12
|
+
|
13
|
+
def controller(name)
|
14
|
+
raise "nesting/chaining controller scopes makes no sense" if @data[:controller]
|
15
|
+
raise "nesting/chaining namespace in/after action makes no sense" if @data[:action]
|
16
|
+
|
17
|
+
new_controller = name.to_s
|
18
|
+
new_data = @data.dup.merge(controller: new_controller)
|
19
|
+
self.class.new(new_data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def matches_route?(route)
|
23
|
+
namespace, controller = @data[:namespace], @data[:controller]
|
24
|
+
|
25
|
+
return false if (namespace || controller) && !route.controller
|
26
|
+
return super unless namespace || controller
|
27
|
+
|
28
|
+
namespace = Pathname(namespace || '')
|
29
|
+
path = namespace.join(controller || '').to_s
|
30
|
+
if controller
|
31
|
+
path == route.controller
|
32
|
+
else
|
33
|
+
controller_namespace = route.controller_namespace || ''
|
34
|
+
(controller_namespace + '/').start_with?(path + '/')
|
35
|
+
end && super
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect_data
|
39
|
+
super.merge(
|
40
|
+
namespace: inspected_or_nil(@data[:namespace]),
|
41
|
+
controller: inspected_or_nil(@data[:controller]))
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.factory_methods
|
45
|
+
[ :namespace, :controller ]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Aygabtu
|
2
|
+
module Scope
|
3
|
+
module Remaining
|
4
|
+
def remaining_at(checkpoint)
|
5
|
+
new_data = @data.dup.merge(remaining_at: checkpoint)
|
6
|
+
self.class.new(new_data)
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches_route?(route)
|
10
|
+
return super unless @data.key?(:remaining_at)
|
11
|
+
|
12
|
+
at_checkpoint = @data[:remaining_at]
|
13
|
+
route_touched = route.marks.any? { |mark| mark.checkpoint <= at_checkpoint }
|
14
|
+
!route_touched && super
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect_data
|
18
|
+
return super unless @data.key?(:remaining_at)
|
19
|
+
super.merge(remaining_at: "CP #{@data[:remaining_at]}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.factory_methods
|
23
|
+
[:remaining_at]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Aygabtu
|
2
|
+
module Scope
|
3
|
+
module Requiring
|
4
|
+
def requiring(*keys)
|
5
|
+
new_requiring = [*@data[:requiring], *keys]
|
6
|
+
new_data = @data.dup.merge(requiring: new_requiring)
|
7
|
+
self.class.new(new_data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches_route?(route)
|
11
|
+
Array(@data[:requiring]).all? do |key|
|
12
|
+
route.really_required_keys.include?(key.to_s)
|
13
|
+
end && super
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect_data
|
17
|
+
return super unless @data[:requiring]
|
18
|
+
super.merge(requiring: @data[:requiring].inspect)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.factory_methods
|
22
|
+
[:requiring]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Aygabtu
|
2
|
+
module Scope
|
3
|
+
module StaticDynamic
|
4
|
+
def static_routes
|
5
|
+
new_static_dynamic_scope(false)
|
6
|
+
end
|
7
|
+
|
8
|
+
def dynamic_routes
|
9
|
+
new_static_dynamic_scope(true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches_route?(route)
|
13
|
+
return super if @data[:dynamic].nil?
|
14
|
+
|
15
|
+
(@data[:dynamic] == route.really_required_keys.present?) && super
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect_data
|
19
|
+
super.merge(dynamic: @data[:dynamic])
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.factory_methods
|
23
|
+
[ :static_routes, :dynamic_routes ]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def new_static_dynamic_scope(dynamic)
|
29
|
+
self.class.new(@data.merge(dynamic: dynamic))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Aygabtu
|
2
|
+
module Scope
|
3
|
+
module VisitingWith
|
4
|
+
def visiting_with(visiting_data)
|
5
|
+
visiting_data = self.visiting_data.merge(visiting_data)
|
6
|
+
new_data = @data.dup.merge(visiting_data: visiting_data)
|
7
|
+
self.class.new(new_data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect_data
|
11
|
+
super.merge(visiting_data: inspected_or_nil(@data[:visiting_data]))
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.factory_methods
|
15
|
+
[:visiting_with]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require_relative 'point_of_call'
|
2
|
+
require_relative 'route_mark'
|
3
|
+
require_relative 'generator'
|
4
|
+
|
5
|
+
module Aygabtu
|
6
|
+
class ScopeActor
|
7
|
+
def initialize(scope, example_group)
|
8
|
+
@scope, @example_group = scope, example_group
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.actions
|
12
|
+
[:visit_with, :visit, :pend, :ignore, :covered!]
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit_with(visiting_data)
|
16
|
+
each_empty_scope_segment do |scope, generator|
|
17
|
+
generator.generate_no_match_failing_example(:visit)
|
18
|
+
end
|
19
|
+
|
20
|
+
each_scope_segment_and_route do |scope, generator, route|
|
21
|
+
visiting_data = @scope.visiting_data.merge(visiting_data)
|
22
|
+
|
23
|
+
mark_route(route, :visit)
|
24
|
+
generator.generate_example(route, visiting_data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit
|
29
|
+
visit_with({})
|
30
|
+
end
|
31
|
+
|
32
|
+
def ignore(reason)
|
33
|
+
raise "Reason for ignoring must be a string" unless reason.is_a?(String)
|
34
|
+
|
35
|
+
each_empty_scope_segment do |scope, generator|
|
36
|
+
generator.generate_no_match_failing_example(:ignore)
|
37
|
+
end
|
38
|
+
|
39
|
+
each_scope_segment_and_route do |scope, generator, route|
|
40
|
+
mark_route(route, :ignore)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def covered!
|
45
|
+
ignore "this is already covered by a non-aygabtu feature"
|
46
|
+
end
|
47
|
+
|
48
|
+
def pend(reason)
|
49
|
+
each_empty_scope_segment do |scope, generator|
|
50
|
+
generator.generate_no_match_failing_example(:pend)
|
51
|
+
end
|
52
|
+
|
53
|
+
each_scope_segment_and_route do |scope, generator, route|
|
54
|
+
mark_route(route, :pend)
|
55
|
+
generator.generate_pending_example(route, reason)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def mark_route(route, action)
|
62
|
+
checkpoint = @example_group.aygabtu_handle.generate_checkpoint
|
63
|
+
mark = RouteMark.new(action, PointOfCall.point_of_call, checkpoint)
|
64
|
+
|
65
|
+
conflicting_marks = route.conflicting_marks(mark)
|
66
|
+
if conflicting_marks.any?
|
67
|
+
conflict_strings = conflicting_marks.map(&:description)
|
68
|
+
raise "Action #{action} for #{route.inspect} conflicts with #{conflict_strings.join ", "}"
|
69
|
+
else
|
70
|
+
route.marks.push mark
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def route_action_valid?(route, action)
|
75
|
+
previous_actions_considered = route.marks.keys
|
76
|
+
previous_actions_considered.delete(:visit) if action == :visit # creating more than one :visit example per route is allowed
|
77
|
+
route.marks.values_at(*previous_actions_considered).all?(&:empty?)
|
78
|
+
end
|
79
|
+
|
80
|
+
def each_scope_segment_and_route
|
81
|
+
segments_generators_routes.each do |segment, generator, routes|
|
82
|
+
routes.each { |route| yield segment, generator, route }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def each_empty_scope_segment
|
87
|
+
segments_generators_routes.each do |segment, generator, routes|
|
88
|
+
next unless routes.empty?
|
89
|
+
|
90
|
+
yield segment, generator
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def segments_generators_routes
|
95
|
+
@segments_and_generators ||= @scope.segments.map do |segment|
|
96
|
+
generator = Generator.new(segment, @example_group)
|
97
|
+
|
98
|
+
routes = @example_group.aygabtu_matching_routes(segment)
|
99
|
+
|
100
|
+
[segment, generator, routes]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'scope/base'
|
2
|
+
require_relative 'scope_actor'
|
3
|
+
|
4
|
+
module Aygabtu
|
5
|
+
class ScopeChain
|
6
|
+
def initialize(example_group, scope)
|
7
|
+
@example_group = example_group
|
8
|
+
@scope = scope
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :scope
|
12
|
+
|
13
|
+
Scope::Base.factory_methods.each do |factory_method|
|
14
|
+
define_method(factory_method) do |*args, &block|
|
15
|
+
new_scope = @scope.public_send(factory_method, *args)
|
16
|
+
|
17
|
+
result = self.class.new(@example_group, new_scope)
|
18
|
+
|
19
|
+
@example_group.aygabtu_enter_context(block, new_scope) if block
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def remaining(&block)
|
26
|
+
remaining_at(@example_group.aygabtu_handle.checkpoint, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.scope_methods
|
30
|
+
[:remaining, *Scope::Base.factory_methods]
|
31
|
+
end
|
32
|
+
|
33
|
+
ScopeActor.actions.each do |action|
|
34
|
+
define_method(action) do |*args|
|
35
|
+
@example_group.aygabtu_action(action, @scope, *args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'support/invokes_rspec'
|
2
|
+
require 'support/matcher_shims'
|
3
|
+
|
4
|
+
describe "actions" do
|
5
|
+
include InvokesRspec
|
6
|
+
include MatcherShims
|
7
|
+
|
8
|
+
def rspec_file_preamble
|
9
|
+
%{
|
10
|
+
require 'rails_application_helper'
|
11
|
+
|
12
|
+
require 'aygabtu/rspec'
|
13
|
+
|
14
|
+
Rails.application.routes.draw do
|
15
|
+
get 'bogus', to: 'bogus#visited', as: :named
|
16
|
+
get 'bogus', to: 'bogus#pended'
|
17
|
+
get 'bogus', to: 'bogus#ignored'
|
18
|
+
|
19
|
+
get 'bogus', to: 'bogus#doubly_pended'
|
20
|
+
get 'bogus', to: 'bogus#doubly_ignored'
|
21
|
+
get 'bogus', to: 'bogus#pended_and_ignored'
|
22
|
+
get 'bogus', to: 'bogus#visited_and_ignored'
|
23
|
+
get 'bogus', to: 'bogus#visited_and_pended'
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def rspec_file_content
|
29
|
+
rspec_file_preamble + get_rspec_file_main_content
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_rspec_file_main_content
|
33
|
+
start_line = method(:rspec_file_main_content).source_location[1]
|
34
|
+
File.open(__FILE__) do |file|
|
35
|
+
lines = file.each_line.to_a.drop(start_line - 1)
|
36
|
+
whitespace = lines.first[/^\s*/]
|
37
|
+
slices = lines.slice_before do |line|
|
38
|
+
line.start_with?(whitespace + 'end')
|
39
|
+
end
|
40
|
+
|
41
|
+
# return only the method body
|
42
|
+
slices.first.drop(1).join('')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when visit is used and a route matches" do
|
47
|
+
def rspec_file_main_content
|
48
|
+
describe "aygabtu examples" do
|
49
|
+
include Aygabtu::RSpec.example_group_module
|
50
|
+
|
51
|
+
def visit(*)
|
52
|
+
end
|
53
|
+
|
54
|
+
def aygabtu_assertions
|
55
|
+
end
|
56
|
+
|
57
|
+
action(:visited).visit
|
58
|
+
named(:named).visit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "generates examples being exercised" do
|
63
|
+
expect(rspec_result.examples).to \
|
64
|
+
contain_exactly(be_passed, be_passed)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when pend is used and a route matches" do
|
69
|
+
def rspec_file_main_content
|
70
|
+
describe "aygabtu examples" do
|
71
|
+
include Aygabtu::RSpec.example_group_module
|
72
|
+
|
73
|
+
action(:pended).pend "bogus reason"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "generates examples which are pending" do
|
78
|
+
expect(rspec_result.examples).to \
|
79
|
+
contain_exactly(be_pending)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when ignore is used and a route matches" do
|
84
|
+
def rspec_file_main_content
|
85
|
+
describe "aygabtu examples" do
|
86
|
+
include Aygabtu::RSpec.example_group_module
|
87
|
+
|
88
|
+
action(:ignored).ignore "bogus reason"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "does not generate examles" do
|
93
|
+
expect(rspec_result.examples).to be_empty
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "when using an action when no route matches" do
|
98
|
+
def rspec_file_main_content
|
99
|
+
describe "aygabtu examples" do
|
100
|
+
include Aygabtu::RSpec.example_group_module
|
101
|
+
|
102
|
+
action(:nonexiting).visit
|
103
|
+
action(:nonexiting).pend "bogus reason"
|
104
|
+
action(:nonexiting).ignore "bogus reason"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "generates failing examples" do
|
109
|
+
expect(rspec_result.examples).to \
|
110
|
+
contain_exactly(be_failed, be_failed, be_failed)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when passing multiple arguments to a splitting scope, one argument not matching a route" do
|
115
|
+
def rspec_file_main_content
|
116
|
+
describe "aygabtu examples" do
|
117
|
+
include Aygabtu::RSpec.example_group_module
|
118
|
+
|
119
|
+
def visit(*)
|
120
|
+
end
|
121
|
+
|
122
|
+
def aygabtu_assertions
|
123
|
+
end
|
124
|
+
|
125
|
+
action(:visited, :nonexisting).visit
|
126
|
+
named(:named, :nonexisting).visit
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "generates a failing example besides what the action produces for the other arguments" do
|
131
|
+
examples_grouped_by_line = rspec_result.examples.group_by(&:line_number).values
|
132
|
+
|
133
|
+
expect(
|
134
|
+
examples_grouped_by_line
|
135
|
+
).to all(
|
136
|
+
include(be_passed).and include(be_failed) # <3 <3 <3
|
137
|
+
)
|
138
|
+
|
139
|
+
# sanity check
|
140
|
+
expect(examples_grouped_by_line.length).to be == 2
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "when calling pend matching the same route a second time" do
|
145
|
+
pending "currently raises an exception at load time"
|
146
|
+
end
|
147
|
+
|
148
|
+
context "when calling ignore matching the same route a second time" do
|
149
|
+
pending "currently raises an exception at load time"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|