interjectable 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: bf87a04b27e693b7dec66842c4c51ea738a7942e
4
- data.tar.gz: 6c8fdb8852fa3fc8dea1bb3f17ae47dbab4d29b5
2
+ SHA256:
3
+ metadata.gz: e624aa99a5f6994b0c9809839aee1bd44861c288312a6d0787214478726b587b
4
+ data.tar.gz: d771fc28918888b29df2df4c9fea225e0d74a6dd5bb62860a351de37fcb10816
5
5
  SHA512:
6
- metadata.gz: cbe3d00e061ff7b5a39ba5f49564ee58f7cd07b69b9f927b4f2bf766d7cef3656ea452ef5305bbb2c75c4f0280fb1785c190bd2836d8547098af91d378b454ad
7
- data.tar.gz: 980a68ad0da41b00ad1d0663f049137d9984bcc0be716dd592bd985247396ef8f6fd0657d802fe683415785643433c7fe7993a805cac93fec08844a07ef9ac7b
6
+ metadata.gz: 158e96c14b09efe755a3c21a79a6c975ca66fb0a0eb5f07fe6652639163ff2eb61cb91003c53d459e321bd88d86fe27451901c461eb54b0453548f2f54af43fb
7
+ data.tar.gz: e2dc0909265a90c82f134ed09704742fc9fc65d2cb44721803788625c303f6c5eb72393d04f1a6cb867fbc4c09c0ffbd9533372702e933f3fac108253158b6a5
@@ -0,0 +1,31 @@
1
+ version: 2.1
2
+
3
+ orbs:
4
+ ruby: circleci/ruby@2.1.1
5
+
6
+ jobs:
7
+ build:
8
+ docker:
9
+ - image: cimg/ruby:3.3
10
+ steps:
11
+ - checkout
12
+ - run:
13
+ name: Which bundler?
14
+ command: bundle -v
15
+ - ruby/install-deps
16
+ test:
17
+ docker:
18
+ - image: cimg/ruby:3.3
19
+ steps:
20
+ - checkout
21
+ - ruby/install-deps
22
+ - ruby/rspec-test:
23
+ include: spec/**/*_spec.rb
24
+
25
+ workflows:
26
+ tests:
27
+ jobs:
28
+ - build
29
+ - test:
30
+ requires:
31
+ - build
data/CHANGES.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Change Log
2
2
 
3
+ # v1.3.0
4
+
5
+ - Add an `injected_methods(include_super = true)` instance and singleton helper method to track what dependency methods
6
+ have been created. This method includes itself in the list of injected methods. The instance method will return both
7
+ injected and static injected methods, while the singleton method will only return static injected methods.
8
+
9
+ # v1.2.0
10
+
11
+ - Ruby 3.x made it an error to override a class variable in a parent class. There was a bug with `inject_static` where
12
+ if a subclass was the first to call a static dependency, the class variable would only be set on that subclass (and
13
+ children of that subsclass). If the injecting class then called the static dependency, it would override the already
14
+ set child's class variable, which is now an error.
15
+
16
+ ```ruby
17
+ class Parent
18
+ include Interjectable
19
+ inject_static(:boom) { "goats" }
20
+ end
21
+
22
+ class Child < Parent; end
23
+
24
+ Child.boom # => sets Child's @@boom = "goats"
25
+ Parent.boom # => sets Parent's @@boom = "goats" and *clear* Child's @@boom.
26
+ Child.boom # => Error on Ruby 3.x because you are trying to read an overriden class variable.
27
+ ```
28
+
29
+ Fix: always set the class variable on the class that called `inject_static`.
30
+
31
+ # v1.1.3
32
+
33
+ - Fix `test_inject` for sub-sub-classes.
34
+
35
+ # v1.1.2
36
+
37
+ - Fix visibility issue with `Module.define_method` for Ruby < 2.5.0.
38
+
39
+ # v1.1.1
40
+
41
+ - Fix typo in RSpec helper loading error message
42
+
3
43
  # v1.1.0
4
44
 
5
45
  - Add another RSpec helper `test_inject` to avoid needing a local variable for
data/README.md CHANGED
@@ -12,7 +12,8 @@ gem 'interjectable'
12
12
 
13
13
  ## Usage
14
14
 
15
- Interjectable has one module (`Interjectable`) and two methods. Use them like so!
15
+ Interjectable has one module (`Interjectable`) and two main methods for defining dependencies,
16
+ `inject` and `inject_static`. Use them like so!
16
17
 
17
18
  ```ruby
18
19
  class MyClass
@@ -28,6 +29,16 @@ class MyClass
28
29
  end
29
30
  ```
30
31
 
32
+ It also includes introspection method `injected_methods(include_super = true)` (both instance and class-level)
33
+ to track what dependency methods have been created.
34
+
35
+ ```ruby
36
+ MyClass.injected_methods
37
+ # => [:injected_methods, :shared_value, :shared_value=]
38
+ MyClass.new.injected_methods
39
+ # => [:injected_methods, :dependency, :dependency=, :other_dependency, :other_dependency=, :shared_value, :shared_value=]
40
+ ```
41
+
31
42
  This replaces a pattern we've used before, adding default dependencies in the constructor, or as memoized methods.
32
43
 
33
44
  ```ruby
@@ -39,7 +50,7 @@ class MyClass
39
50
  @dependency=dependency
40
51
  end
41
52
 
42
- def other_depency
53
+ def other_dependency
43
54
  @other_dependency ||= AnotherClass.new
44
55
  end
45
56
  end
@@ -4,43 +4,43 @@ module Interjectable
4
4
  module ClassMethods
5
5
  BLANK = Object.new
6
6
 
7
- class SuperclassInjectStatic < Struct.new(:klass, :dependency)
7
+ SuperclassInjectStatic = Struct.new(:klass, :dependency) do
8
8
  def override(value, &setter)
9
- cvar = "@@#{dependency}"
10
- klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
9
+ var = ::Interjectable::ClassMethods::BLANK
11
10
  klass.define_singleton_method(dependency) do
12
- if class_variable_defined?(cvar)
13
- class_variable_get(cvar)
14
- else
15
- class_variable_set(cvar, value != ::Interjectable::ClassMethods::BLANK ? value : instance_eval(&setter))
16
- end
11
+ return var if var != ::Interjectable::ClassMethods::BLANK
12
+
13
+ var = value != ::Interjectable::ClassMethods::BLANK ? value : instance_eval(&setter)
14
+ end
15
+
16
+ klass.define_singleton_method("#{dependency}=") do |new_value|
17
+ var = new_value
17
18
  end
18
19
  end
19
20
 
20
21
  def restore
21
- cvar = "@@#{dependency}"
22
- klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
23
22
  klass.singleton_class.remove_method(dependency)
23
+ klass.singleton_class.remove_method("#{dependency}=")
24
24
  end
25
25
  end
26
26
 
27
27
  class InjectStatic < SuperclassInjectStatic
28
28
  def override(*)
29
- @meth = klass.singleton_method(dependency)
29
+ @getter = klass.singleton_method(dependency)
30
+ @setter = klass.singleton_method("#{dependency}=")
30
31
  super
31
32
  end
32
33
 
33
34
  def restore
34
- cvar = "@@#{dependency}"
35
- klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
36
- klass.define_singleton_method(dependency, @meth)
35
+ klass.define_singleton_method(dependency, @getter)
36
+ klass.define_singleton_method("#{dependency}=", @setter)
37
37
  end
38
38
  end
39
39
 
40
- class SuperclassInject < Struct.new(:klass, :dependency)
40
+ SuperclassInject = Struct.new(:klass, :dependency) do
41
41
  def override(value, &setter)
42
42
  ivar = "@#{dependency}"
43
- klass.define_method(dependency) do
43
+ klass.send(:define_method, dependency) do
44
44
  if instance_variable_defined?(ivar)
45
45
  instance_variable_get(ivar)
46
46
  else
@@ -56,12 +56,12 @@ module Interjectable
56
56
 
57
57
  class Inject < SuperclassInject
58
58
  def override(*)
59
- @meth = klass.instance_method(dependency)
59
+ @getter = klass.instance_method(dependency)
60
60
  super
61
61
  end
62
62
 
63
63
  def restore
64
- klass.define_method(dependency, @meth)
64
+ klass.send(:define_method, dependency, @getter)
65
65
  end
66
66
  end
67
67
 
@@ -86,18 +86,18 @@ module Interjectable
86
86
  raise "#test_inject can only be called from an RSpec ExampleGroup (e.g.: it, before, after)"
87
87
  end
88
88
 
89
- injector = if target.singleton_methods(false).include?(dependency) # inject_static(dependency) on this class
90
- InjectStatic.new(target, dependency)
91
- elsif target.singleton_methods.include?(dependency) # inject_static(dependency) on a superclass of this class
92
- SuperclassInjectStatic.new(target, dependency)
93
- elsif target.instance_methods(false).include?(dependency) # inject(dependency) on this class
94
- Inject.new(target, dependency)
95
- elsif target.instance_methods.include?(dependency) # inject(dependency) on a superclass of this class
96
- SuperclassInject.new(target, dependency)
97
- else
98
- raise ArgumentError, "tried to override a non-existent dependency: #{dependency.inspect}"
99
- end
100
-
89
+ injector =
90
+ if target.singleton_methods(false).include?(dependency) # inject_static(dependency) on this class
91
+ InjectStatic.new(target, dependency)
92
+ elsif target.singleton_methods.include?(dependency) # inject_static(dependency) on a superclass of this class
93
+ SuperclassInjectStatic.new(target, dependency)
94
+ elsif target.instance_methods(false).include?(dependency) # inject(dependency) on this class
95
+ Inject.new(target, dependency)
96
+ elsif target.instance_methods.include?(dependency) # inject(dependency) on a superclass of this class
97
+ SuperclassInject.new(target, dependency)
98
+ else
99
+ raise ArgumentError, "tried to override a non-existent dependency: #{dependency.inspect}"
100
+ end
101
101
 
102
102
  injector.override(value, &setter)
103
103
 
@@ -114,6 +114,7 @@ module Interjectable
114
114
  # for the same #test_inject call since those before hooks only run once,
115
115
  # and therefore only setup a single after hook.
116
116
  return if scope == :each && RESTORE_HOOKS[key].any? { |group| rspec_example_group <= group }
117
+
117
118
  RESTORE_HOOKS[key] << rspec_example_group
118
119
 
119
120
  rspec_example_group.after(scope) do
@@ -138,5 +139,5 @@ if defined?(RSpec)
138
139
  c.include(Interjectable::RSpecHelper)
139
140
  end
140
141
  else
141
- raise "RSpec helper was required but RSpec has not beed defined"
142
+ raise "RSpec helper was required but RSpec has not been defined"
142
143
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Interjectable
4
- VERSION = "1.1.0"
4
+ VERSION = "1.3.0"
5
5
  end
data/lib/interjectable.rb CHANGED
@@ -7,10 +7,34 @@ module Interjectable
7
7
 
8
8
  def self.included(mod)
9
9
  mod.send(:extend, ClassMethods)
10
+ mod.send(:include, InstanceMethods)
10
11
  end
11
12
 
12
13
  def self.extended(mod)
13
14
  mod.send(:extend, ClassMethods)
15
+ mod.send(:include, InstanceMethods)
16
+ end
17
+
18
+ module InstanceMethods
19
+ def injected_methods(include_super = true)
20
+ injected = self.class.instance_variable_get(:@injected_methods).to_a +
21
+ self.class.instance_variable_get(:@static_injected_methods).to_a
22
+
23
+ if include_super
24
+ super_injected = self.class.ancestors.flat_map do |klass|
25
+ klass.instance_variable_get(:@injected_methods).to_a +
26
+ klass.instance_variable_get(:@static_injected_methods).to_a
27
+ end
28
+
29
+ [
30
+ :injected_methods,
31
+ *super_injected,
32
+ *injected,
33
+ ].uniq
34
+ else
35
+ [:injected_methods, *injected]
36
+ end
37
+ end
14
38
  end
15
39
 
16
40
  module ClassMethods
@@ -42,6 +66,9 @@ module Interjectable
42
66
  instance_variable_set(ivar_name, instance_eval(&default_block))
43
67
  end
44
68
  end
69
+
70
+ @injected_methods ||= []
71
+ @injected_methods += [dependency, :"#{dependency}="]
45
72
  end
46
73
 
47
74
  # Defines helper methods on instances that memoize values per-class.
@@ -64,27 +91,51 @@ module Interjectable
64
91
  raise MethodAlreadyDefined, "#{dependency} is already defined"
65
92
  end
66
93
 
94
+ injecting_class = self
95
+
67
96
  cvar_name = :"@@#{dependency}"
68
97
  setter = :"#{dependency}="
69
98
 
70
99
  define_method(setter) do |value|
71
- self.class.send(setter, value)
100
+ injecting_class.send(setter, value)
72
101
  end
73
102
 
74
103
  define_singleton_method(setter) do |value|
75
- class_variable_set(cvar_name, value)
104
+ injecting_class.class_variable_set(cvar_name, value)
76
105
  end
77
106
 
78
107
  define_method(dependency) do
79
- self.class.send(dependency)
108
+ injecting_class.send(dependency)
80
109
  end
81
110
 
82
111
  define_singleton_method(dependency) do
83
112
  if class_variable_defined?(cvar_name)
84
- class_variable_get(cvar_name)
113
+ injecting_class.class_variable_get(cvar_name)
85
114
  else
86
- class_variable_set(cvar_name, instance_eval(&default_block))
115
+ injecting_class.class_variable_set(cvar_name, instance_eval(&default_block))
116
+ end
117
+ end
118
+
119
+ @static_injected_methods ||= []
120
+ @static_injected_methods += [dependency, :"#{dependency}="]
121
+ end
122
+
123
+ # @return [Array<Symbol>]
124
+ def injected_methods(include_super = true)
125
+ injected = @static_injected_methods.to_a
126
+
127
+ if include_super
128
+ super_injected = ancestors.flat_map do |klass|
129
+ klass.instance_variable_get(:@static_injected_methods).to_a
87
130
  end
131
+
132
+ [
133
+ :injected_methods,
134
+ *super_injected,
135
+ *injected,
136
+ ].uniq
137
+ else
138
+ [:injected_methods, *injected]
88
139
  end
89
140
  end
90
141
  end
@@ -2,19 +2,30 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe "RSpec test helper #test_inject" do
6
- class Klass
7
- extend Interjectable
8
- inject(:dependency) { :dependency }
9
- inject_static(:static_dependency) { :static_dependency }
5
+ class Klass
6
+ extend Interjectable
7
+ inject(:dependency) { :dependency }
8
+ inject_static(:static_dependency) { :static_dependency }
10
9
 
11
- define_method(:foo) { :foo }
12
- end
13
- SubKlass = Class.new(Klass)
10
+ define_method(:foo) { :foo }
11
+ end
12
+ SubKlass = Class.new(Klass)
13
+ SubSubKlass = Class.new(SubKlass)
14
14
 
15
+ describe "RSpec test helper #test_inject" do
15
16
  let(:instance) { Klass.new }
16
17
  let(:subklass_instance) { SubKlass.new }
17
18
 
19
+ before(:all) do
20
+ Klass.static_dependency = :boom
21
+ SubKlass.static_dependency = :boom1
22
+ SubSubKlass.static_dependency = :boom2
23
+ expect(Klass.static_dependency).to eq(:boom2)
24
+ expect(SubKlass.static_dependency).to eq(:boom2)
25
+ expect(SubSubKlass.static_dependency).to eq(:boom2)
26
+ Klass.static_dependency = :static_dependency
27
+ end
28
+
18
29
  after(:all) do
19
30
  # Sanity check after running the tests, verify #test_inject cleans up after itself
20
31
  instance = Klass.new
@@ -25,12 +36,16 @@ describe "RSpec test helper #test_inject" do
25
36
  expect(subklass_instance.dependency).to eq(:dependency)
26
37
  expect(subklass_instance.static_dependency).to eq(:static_dependency)
27
38
  expect(SubKlass.static_dependency).to eq(:static_dependency)
28
-
29
- # Don't leak our test classes
30
- Object.instance_eval do
31
- remove_const(:SubKlass)
32
- remove_const(:Klass)
33
- end
39
+ subsubklass_instance = SubSubKlass.new
40
+ expect(subsubklass_instance.dependency).to eq(:dependency)
41
+ expect(subsubklass_instance.static_dependency).to eq(:static_dependency)
42
+ expect(SubSubKlass.static_dependency).to eq(:static_dependency)
43
+ Klass.static_dependency = :boom
44
+ SubKlass.static_dependency = :boom1
45
+ SubSubKlass.static_dependency = :boom2
46
+ expect(Klass.static_dependency).to eq(:boom2)
47
+ expect(SubKlass.static_dependency).to eq(:boom2)
48
+ expect(SubSubKlass.static_dependency).to eq(:boom2)
34
49
  end
35
50
 
36
51
  context "isoloated context" do
@@ -64,6 +79,15 @@ describe "RSpec test helper #test_inject" do
64
79
  it "still respects instance setters" do
65
80
  instance.dependency = :bar
66
81
  expect(instance.dependency).to eq(:bar)
82
+ expect(Klass.new.dependency).to eq(:foo)
83
+ end
84
+
85
+ it "still respects static setters in the overriden test_injected context only" do
86
+ instance.static_dependency = :bar
87
+ expect(instance.static_dependency).to eq(:bar)
88
+ expect(Klass.static_dependency).to eq(:bar)
89
+ expect(SubKlass.static_dependency).to eq(:bar)
90
+ expect(SubSubKlass.static_dependency).to eq(:bar)
67
91
  end
68
92
 
69
93
  it "errors if you inject a non static default into a static injection" do
@@ -101,7 +125,7 @@ describe "RSpec test helper #test_inject" do
101
125
  SubKlass.test_inject(:static_dependency) { :subklass_double_overriden_static_dependency }
102
126
  end
103
127
 
104
- it "sets the dependency" do
128
+ pending "sets the dependency", aggregate_failures: true do
105
129
  expect(subklass_instance.dependency).to eq(:subklass_override)
106
130
  expect(subklass_instance.static_dependency).to eq(:subklass_double_overriden_static_dependency)
107
131
  expect(Klass.static_dependency).to eq(:double_overriden_static_dependency)
@@ -195,4 +219,86 @@ describe "RSpec test helper #test_inject" do
195
219
  expect { Klass.test_inject(:bad_dependency) { 1 } }.to raise_error(ArgumentError)
196
220
  end
197
221
  end
222
+
223
+ context "double subclass" do
224
+ context "1" do
225
+ it "sets the dependency" do
226
+ calls = 0
227
+ Klass.test_inject(:dependency) { calls += 1; :baz }
228
+ expect(Klass.new.dependency).to eq(:baz)
229
+ subklass = SubKlass.new
230
+ expect(subklass.dependency).to eq(:baz)
231
+ expect(subklass.dependency).to eq(:baz)
232
+ expect(SubSubKlass.new.dependency).to eq(:baz)
233
+ expect(calls).to eq(3)
234
+ end
235
+ end
236
+
237
+ context "2" do
238
+ it "sets the dependency" do
239
+ calls = 0
240
+ SubKlass.test_inject(:dependency) { calls += 1; :baz }
241
+ expect(Klass.new.dependency).to eq(:dependency)
242
+ subklass = SubKlass.new
243
+ expect(subklass.dependency).to eq(:baz)
244
+ expect(subklass.dependency).to eq(:baz)
245
+ expect(SubSubKlass.new.dependency).to eq(:baz)
246
+ expect(calls).to eq(2)
247
+ end
248
+ end
249
+
250
+ context "3" do
251
+ it "sets the dependency" do
252
+ sub_klass_calls = 0
253
+ sub_sub_klass_calls = 0
254
+ SubKlass.test_inject(:dependency) { sub_klass_calls += 1; :bar }
255
+ SubSubKlass.test_inject(:dependency) { sub_sub_klass_calls += 1; :baz }
256
+ expect(Klass.new.dependency).to eq(:dependency)
257
+ subklass = SubKlass.new
258
+ expect(subklass.dependency).to eq(:bar)
259
+ expect(subklass.dependency).to eq(:bar)
260
+ expect(SubSubKlass.new.dependency).to eq(:baz)
261
+ expect(sub_klass_calls).to eq(1)
262
+ expect(sub_sub_klass_calls).to eq(1)
263
+ end
264
+ end
265
+ end
266
+
267
+ context "double subclass static" do
268
+ context "1" do
269
+ it "sets the dependency" do
270
+ calls = 0
271
+ Klass.test_inject(:static_dependency) { calls += 1; :baz }
272
+ expect(Klass.new.static_dependency).to eq(:baz)
273
+ expect(SubKlass.new.static_dependency).to eq(:baz)
274
+ expect(SubSubKlass.new.static_dependency).to eq(:baz)
275
+ expect(calls).to eq(1)
276
+ end
277
+ end
278
+
279
+ context "2" do
280
+ pending "sets the dependency", aggregate_failures: true do
281
+ calls = 0
282
+ SubKlass.test_inject(:static_dependency) { calls += 1; :baz }
283
+ expect(Klass.new.static_dependency).to eq(:static_dependency)
284
+ expect(SubKlass.new.static_dependency).to eq(:baz)
285
+ expect(SubSubKlass.new.static_dependency).to eq(:baz)
286
+ expect(calls).to eq(1)
287
+ end
288
+ end
289
+
290
+ context "3" do
291
+ pending "sets the dependency", aggregate_failures: true do
292
+ sub_klass_calls = 0
293
+ sub_sub_klass_calls = 0
294
+ SubKlass.test_inject(:static_dependency) { sub_klass_calls += 1; :bar }
295
+ SubSubKlass.test_inject(:static_dependency) { sub_sub_klass_calls += 1; :baz }
296
+ expect(Klass.new.static_dependency).to eq(:static_dependency)
297
+ expect(SubKlass.new.static_dependency).to eq(:bar)
298
+ expect(SubSubKlass.new.static_dependency).to eq(:baz)
299
+ expect(sub_klass_calls).to eq(1)
300
+ expect(sub_sub_klass_calls).to eq(1)
301
+ end
302
+ end
303
+ end
198
304
  end
@@ -81,8 +81,24 @@ describe Interjectable do
81
81
  let(:subclass_instance) { subclass.new }
82
82
 
83
83
  it "does not error if the method exists on the superclass" do
84
- subclass.inject(:dependency) { :some_other_value }
85
- expect(subclass_instance.dependency).to eq(:some_other_value)
84
+ subclass.inject(:some_dependency) { :some_other_value }
85
+ expect(subclass_instance.some_dependency).to eq(:some_other_value)
86
+ end
87
+
88
+ it "allows injection on the subclass without injecting on the superclass" do
89
+ subclass.inject(:subclass_dependency) { :brand_new_value }
90
+ expect(subclass_instance.subclass_dependency).to eq(:brand_new_value)
91
+ expect { instance.subclass_dependency }.to raise_error(NoMethodError)
92
+ end
93
+
94
+ context "with a chain of subclasses" do
95
+ let(:lower_subclass) { Class.new(subclass) }
96
+ let(:lower_subclass_instance) { lower_subclass.new }
97
+
98
+ it "retrieves injected methods from all ancestors when requested" do
99
+ subclass.inject(:subclass_dependency) { :subclass_value }
100
+ lower_subclass.inject(:lower_subclass_dependency) { :lower_subclass_value }
101
+ end
86
102
  end
87
103
  end
88
104
  end
@@ -170,6 +186,168 @@ describe Interjectable do
170
186
  expect(subclass_instance.static_dependency).to eq(:some_other_value)
171
187
  expect(subclass.static_dependency).to eq(:some_other_value)
172
188
  end
189
+
190
+ it "does not error when lazily setting the dep from a subclass first" do
191
+ expect(subclass.static_dependency).to eq(:some_value)
192
+ expect(klass.static_dependency).to eq(:some_value)
193
+ expect(subclass.static_dependency).to eq(:some_value)
194
+ end
195
+
196
+ it "only defines the class variable on the injecting class" do
197
+ expect(subclass.static_dependency).to eq(:some_value)
198
+ expect(klass.class_variable_get(:@@static_dependency)).to eq(:some_value)
199
+ end
200
+
201
+ it "allows injection on the subclass without injecting on the superclass" do
202
+ subclass.inject_static(:static_subclass_dependency) { :brand_new_value }
203
+ expect(subclass_instance.static_subclass_dependency).to eq(:brand_new_value)
204
+ expect { klass.static_subclass_dependency }.to raise_error(NoMethodError)
205
+ expect { instance.static_subclass_dependency }.to raise_error(NoMethodError)
206
+ end
207
+
208
+ context "with a chain of subclasses" do
209
+ let(:lower_subclass) { Class.new(subclass) }
210
+ let(:lower_subclass_instance) { lower_subclass.new }
211
+
212
+ it "retrieves injected methods from all ancestors when requested" do
213
+ subclass.inject_static(:static_subclass_dependency) { :subclass_value }
214
+ lower_subclass.inject_static(:static_lower_subclass_dependency) { :lower_subclass_value }
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ describe "#injected_methods" do
221
+ before do
222
+ klass.inject(:a) { :a }
223
+ klass.inject_static(:b) { :b }
224
+ end
225
+
226
+ it "lists injected methods on the instance and static ones too" do
227
+ injected_methods = instance.injected_methods
228
+
229
+ expect(injected_methods).to match_array(
230
+ [
231
+ :injected_methods, :a, :a=, :b, :b=,
232
+ ],
233
+ )
234
+ end
235
+
236
+ context "with a subclass" do
237
+ let(:subclass) do
238
+ Class.new(klass) do
239
+ inject(:c) { :c }
240
+ end
241
+ end
242
+ let(:include_super) { true }
243
+ let(:subclass_instance) { subclass.new }
244
+
245
+ it "includes super methods by default" do
246
+ injected_methods = subclass_instance.injected_methods(include_super)
247
+
248
+ expect(injected_methods).to match_array(
249
+ [
250
+ :injected_methods,
251
+ :a,
252
+ :a=,
253
+ :b,
254
+ :b=,
255
+ :c,
256
+ :c=,
257
+ ],
258
+ )
259
+ end
260
+
261
+ context "with include_super = false" do
262
+ let(:include_super) { false }
263
+
264
+ it "does not include super methods" do
265
+ injected_methods = subclass_instance.injected_methods(include_super)
266
+
267
+ expect(injected_methods).to_not include(:a)
268
+ expect(injected_methods).to_not include(:a=)
269
+ expect(injected_methods).to_not include(:b)
270
+ expect(injected_methods).to_not include(:b=)
271
+
272
+ expect(injected_methods).to match_array(
273
+ [
274
+ :injected_methods,
275
+ :c,
276
+ :c=,
277
+ ],
278
+ )
279
+ end
280
+ end
281
+ end
282
+ end
283
+
284
+ describe ".injected_methods" do
285
+ before do
286
+ klass.inject(:a) { :a }
287
+ klass.inject_static(:b) { :b }
288
+ end
289
+
290
+ it "lists static injected methods class" do
291
+ injected_methods = klass.injected_methods
292
+
293
+ expect(injected_methods).to match_array(
294
+ [
295
+ :injected_methods, :b, :b=,
296
+ ],
297
+ )
298
+ expect(injected_methods).to_not include(:a)
299
+ expect(injected_methods).to_not include(:a=)
300
+ end
301
+
302
+ context "with a subclass" do
303
+ let(:subclass) do
304
+ Class.new(klass) do
305
+ inject(:c) { :c }
306
+ inject_static(:d) { :d }
307
+ end
308
+ end
309
+ let(:include_super) { true }
310
+
311
+ it "includes super methods by default" do
312
+ injected_methods = subclass.injected_methods(include_super)
313
+
314
+ expect(injected_methods).to match_array(
315
+ [
316
+ :injected_methods,
317
+ :b,
318
+ :b=,
319
+ :d,
320
+ :d=
321
+ ],
322
+ )
323
+ expect(injected_methods).to_not include(:a), 'skips instance methods'
324
+ expect(injected_methods).to_not include(:a=), 'skips instance methods'
325
+ expect(injected_methods).to_not include(:c), 'skips instance methods'
326
+ expect(injected_methods).to_not include(:c=), 'skips instance methods'
327
+ end
328
+
329
+ context "with include_super = false" do
330
+ let(:include_super) { false }
331
+
332
+ it "does not include super methods" do
333
+ injected_methods = subclass.injected_methods(include_super)
334
+
335
+ expect(injected_methods).to_not include(:a), 'skips instance methods'
336
+ expect(injected_methods).to_not include(:a=), 'skips instance methods'
337
+ expect(injected_methods).to_not include(:b), 'skips super methods'
338
+ expect(injected_methods).to_not include(:b=), 'skips super methods'
339
+ expect(injected_methods).to_not include(:c), 'skips instance methods'
340
+ expect(injected_methods).to_not include(:c=), 'skips instance methods'
341
+
342
+ expect(injected_methods).to match_array(
343
+ [
344
+ :injected_methods,
345
+ :d,
346
+ :d=,
347
+ ],
348
+ )
349
+ end
350
+ end
173
351
  end
174
352
  end
175
353
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pry'
3
4
  require 'rspec'
4
5
 
5
6
  $LOAD_PATH.unshift(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interjectable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Margolis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-17 00:00:00.000000000 Z
11
+ date: 2024-07-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple dependency injection library for unit testing
14
14
  email:
@@ -17,6 +17,7 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - ".circleci/config.yml"
20
21
  - ".gitignore"
21
22
  - CHANGES.md
22
23
  - Gemfile
@@ -34,7 +35,7 @@ homepage: https://github.com/zachmargolis/interjectable
34
35
  licenses:
35
36
  - MIT
36
37
  metadata: {}
37
- post_install_message:
38
+ post_install_message:
38
39
  rdoc_options: []
39
40
  require_paths:
40
41
  - lib
@@ -49,9 +50,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
50
  - !ruby/object:Gem::Version
50
51
  version: '0'
51
52
  requirements: []
52
- rubyforge_project:
53
- rubygems_version: 2.6.14
54
- signing_key:
53
+ rubygems_version: 3.4.6
54
+ signing_key:
55
55
  specification_version: 4
56
56
  summary: A simple dependency injection library for unit testing
57
57
  test_files: