summaryrefslogtreecommitdiff
path: root/spec/ruby/core/module/const_added_spec.rb
blob: 90cd36551aa7b75270fdf3600b2f64792e630052 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'fixtures/const_added'

describe "Module#const_added" do
  it "is a private instance method" do
    Module.should have_private_instance_method(:const_added)
  end

  it "returns nil in the default implementation" do
    Module.new do
      const_added(:TEST).should == nil
    end
  end

  it "for a class defined with the `class` keyword, const_added runs before inherited" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(_)
        ScratchPad << :const_added
      end
    end

    parent = Class.new do
      def self.inherited(_)
        ScratchPad << :inherited
      end
    end

    class mod::C < parent; end

    ScratchPad.recorded.should == [:const_added, :inherited]
  end

  it "the superclass of a class assigned to a constant is set before const_added is called" do
    ScratchPad.record []

    parent = Class.new do
      def self.const_added(name)
        ScratchPad << name
        ScratchPad << const_get(name).superclass
      end
    end

    class parent::C < parent; end

    ScratchPad.recorded.should == [:C, parent]
  end

  it "is called when a new constant is assigned on self" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        ScratchPad << name
      end
    end

    mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
      TEST = 1
    RUBY

    ScratchPad.recorded.should == [:TEST]
  end

  it "is called when a new constant is assigned on self through const_set" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        ScratchPad << name
      end
    end

    mod.const_set(:TEST, 1)

    ScratchPad.recorded.should == [:TEST]
  end

  it "is called when a new module is defined under self" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        ScratchPad << name
      end
    end

    mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
      module SubModule
      end

      module SubModule
      end
    RUBY

    ScratchPad.recorded.should == [:SubModule]
  end

  it "is called when a new module is defined under a named module (assigned to a constant)" do
    ScratchPad.record []

    ModuleSpecs::ConstAddedSpecs::NamedModule = Module.new do
      def self.const_added(name)
        ScratchPad << name
      end

      module self::A
        def self.const_added(name)
          ScratchPad << name
        end

        module self::B
        end
      end
    end

    ScratchPad.recorded.should == [:A, :B]
    ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModule
  end

  it "is called when a new class is defined under self" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        ScratchPad << name
      end
    end

    mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
      class SubClass
      end

      class SubClass
      end
    RUBY

    ScratchPad.recorded.should == [:SubClass]
  end

  it "is called when a new class is defined under a named module (assigned to a constant)" do
    ScratchPad.record []

    ModuleSpecs::ConstAddedSpecs::NamedModuleB = Module.new do
      def self.const_added(name)
        ScratchPad << name
      end

      class self::A
        def self.const_added(name)
          ScratchPad << name
        end

        class self::B
        end
      end
    end

    ScratchPad.recorded.should == [:A, :B]
    ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModuleB
  end

  it "is called when an autoload is defined" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        ScratchPad << name
      end
    end

    mod.autoload :Autoload, "foo"
    ScratchPad.recorded.should == [:Autoload]
  end

  it "is called with a precise caller location with the line of definition" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        location = caller_locations(1, 1)[0]
        ScratchPad << location.lineno
      end
    end

    line = __LINE__
    mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
      TEST = 1

      module SubModule
      end

      class SubClass
      end
    RUBY

    mod.const_set(:CONST_SET, 1)

    ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11]
  end

  it "is called when the constant is already assigned a value" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        ScratchPad.record const_get(name)
      end
    end

    mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
      TEST = 123
    RUBY

    ScratchPad.recorded.should == 123
  end

  it "records re-definition of existing constants" do
    ScratchPad.record []

    mod = Module.new do
      def self.const_added(name)
        ScratchPad << const_get(name)
      end
    end

    -> {
      mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
        TEST = 123
        TEST = 456
      RUBY
    }.should complain(/warning: already initialized constant .+::TEST/)

    ScratchPad.recorded.should == [123, 456]
  end
end