Skip to content

Commit 878c42b

Browse files
shugoclaude
authored andcommitted
[Feature #9779] Add Module#descendants
Module#descendants returns an array of classes and modules that have the receiver in their ancestors; it is the inverse of Module#ancestors. The receiver itself, singleton classes, and refinements are not included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent f2ee1b4 commit 878c42b

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

core/module/descendants_spec.rb

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'fixtures/classes'
3+
4+
ruby_version_is "4.1" do
5+
describe "Module#descendants" do
6+
it "returns a list of classes inheriting from self" do
7+
parent = Class.new
8+
child = Class.new(parent)
9+
grandchild = Class.new(child)
10+
11+
assert_descendants(parent, [child, grandchild])
12+
assert_descendants(child, [grandchild])
13+
assert_descendants(grandchild, [])
14+
end
15+
16+
it "returns a list of modules and classes including self" do
17+
mod = Module.new
18+
including_mod = Module.new { include mod }
19+
klass = Class.new { include including_mod }
20+
subclass = Class.new(klass)
21+
22+
assert_descendants(mod, [including_mod, klass, subclass])
23+
assert_descendants(including_mod, [klass, subclass])
24+
end
25+
26+
it "returns modules and classes prepending self" do
27+
mod = Module.new
28+
prepending_mod = Module.new { prepend mod }
29+
klass = Class.new { prepend mod }
30+
31+
assert_descendants(mod, [prepending_mod, klass])
32+
end
33+
34+
it "returns modules and classes including self after prepending another module" do
35+
prepended = Module.new
36+
mod = Module.new
37+
klass = Class.new { prepend prepended; include mod }
38+
including_mod = Module.new { prepend prepended; include mod }
39+
40+
assert_descendants(mod, [klass, including_mod])
41+
end
42+
43+
it "does not return the internal nodes created by prepend" do
44+
parent = Class.new { prepend Module.new }
45+
child = Class.new(parent) { prepend Module.new }
46+
47+
assert_descendants(parent, [child])
48+
end
49+
50+
it "does not return modules and classes including a clone of self" do
51+
mod = Module.new
52+
clone = mod.clone
53+
klass = Class.new { include clone }
54+
55+
mod.descendants.should_not.include?(klass)
56+
clone.descendants.should.include?(klass)
57+
end
58+
59+
it "returns modules and classes into which self is included later" do
60+
mod = Module.new
61+
including_mod = Module.new
62+
klass = Class.new { include including_mod }
63+
including_mod.include(mod)
64+
65+
assert_descendants(mod, [including_mod, klass])
66+
end
67+
68+
it "does not return self" do
69+
mod = Module.new
70+
Class.new { include mod }
71+
72+
mod.descendants.should_not.include?(mod)
73+
ModuleSpecs::Parent.descendants.should_not.include?(ModuleSpecs::Parent)
74+
end
75+
76+
it "returns an empty Array if there is no descendant" do
77+
Module.new.descendants.should == []
78+
Class.new.descendants.should == []
79+
end
80+
81+
it "does not return singleton classes" do
82+
mod = Module.new
83+
obj = Object.new
84+
obj.extend(mod)
85+
klass = Class.new { extend mod }
86+
87+
mod.descendants.should_not.include?(obj.singleton_class)
88+
mod.descendants.should_not.include?(klass.singleton_class)
89+
ModuleSpecs::Internal.descendants.should_not.include?(ModuleSpecs::Child.singleton_class)
90+
end
91+
92+
it "does not return refinements" do
93+
klass = Class.new
94+
refinement = nil
95+
Module.new do
96+
refinement = refine(klass) do
97+
def refined_method; end
98+
end
99+
end
100+
101+
klass.descendants.should_not.include?(refinement)
102+
refinement.descendants.should_not.include?(klass)
103+
end
104+
105+
it "has 1 entry per module or class" do
106+
mod = Module.new
107+
including_mod = Module.new { include mod }
108+
Class.new do
109+
include mod
110+
include including_mod
111+
end
112+
113+
descendants = mod.descendants
114+
descendants.should == descendants.uniq
115+
116+
descendants = ModuleSpecs::Parent.descendants
117+
descendants.should == descendants.uniq
118+
end
119+
120+
it "returns the modules and classes whose ancestors include self" do
121+
mods = [ModuleSpecs::Basic, ModuleSpecs::Super, ModuleSpecs::Parent,
122+
ModuleSpecs::Child, ModuleSpecs::Child2, ModuleSpecs::Grandchild]
123+
124+
mods.each do |mod|
125+
descendants = mod.descendants
126+
mods.each do |other|
127+
next if mod.equal?(other)
128+
descendants.include?(other).should == other.ancestors.include?(mod)
129+
end
130+
end
131+
end
132+
133+
def assert_descendants(mod, descendants)
134+
mod.descendants.sort_by(&:object_id).should == descendants.sort_by(&:object_id)
135+
end
136+
end
137+
end

0 commit comments

Comments
 (0)