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,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,3 @@
1
+ module Aygabtu
2
+ VERSION = "0.2.0"
3
+ 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
+