ryo.rb 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/specs.yml +23 -0
  3. data/.gitignore +6 -0
  4. data/.gitlab-ci.yml +9 -0
  5. data/.rubocop.yml +56 -0
  6. data/.yardoc-template/default/fulldoc/html/css/0x1eef.css +15 -0
  7. data/.yardoc-template/default/layout/html/setup.rb +5 -0
  8. data/.yardoc-template/default/module/setup.rb +7 -0
  9. data/.yardopts +4 -0
  10. data/Gemfile +5 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +373 -0
  13. data/Rakefile +3 -0
  14. data/lib/ryo/basic_object.rb +58 -0
  15. data/lib/ryo/builder.rb +106 -0
  16. data/lib/ryo/enumerable.rb +214 -0
  17. data/lib/ryo/function.rb +68 -0
  18. data/lib/ryo/keywords.rb +67 -0
  19. data/lib/ryo/lazy.rb +4 -0
  20. data/lib/ryo/object.rb +58 -0
  21. data/lib/ryo/reflect.rb +379 -0
  22. data/lib/ryo/version.rb +5 -0
  23. data/lib/ryo.rb +197 -0
  24. data/ryo.rb.gemspec +21 -0
  25. data/share/ryo.rb/examples/1.0_prototypes_point_object.rb +12 -0
  26. data/share/ryo.rb/examples/1.1_prototypes_ryo_fn.rb +14 -0
  27. data/share/ryo.rb/examples/2.0_iteration_each.rb +13 -0
  28. data/share/ryo.rb/examples/2.1_iteration_map.rb +16 -0
  29. data/share/ryo.rb/examples/2.2_iteration_ancestors.rb +13 -0
  30. data/share/ryo.rb/examples/3.0_recursion_ryo_from.rb +13 -0
  31. data/share/ryo.rb/examples/3.1_recursion_ryo_from_with_array.rb +19 -0
  32. data/share/ryo.rb/examples/3.2_recursion_ryo_from_with_openstruct.rb +14 -0
  33. data/share/ryo.rb/examples/4.0_basicobject_ryo_basicobject.rb +12 -0
  34. data/share/ryo.rb/examples/4.1_basicobject_ryo_basicobject_from.rb +13 -0
  35. data/share/ryo.rb/examples/5_collisions_resolution_strategy.rb +8 -0
  36. data/share/ryo.rb/examples/6_beyond_hash_objects.rb +20 -0
  37. data/share/ryo.rb/examples/7_ryo_lazy.rb +14 -0
  38. data/share/ryo.rb/examples/setup.rb +3 -0
  39. data/spec/readme_spec.rb +79 -0
  40. data/spec/ryo_basic_object_spec.rb +60 -0
  41. data/spec/ryo_enumerable_spec.rb +197 -0
  42. data/spec/ryo_keywords_spec.rb +86 -0
  43. data/spec/ryo_object_spec.rb +71 -0
  44. data/spec/ryo_prototypes_spec.rb +45 -0
  45. data/spec/ryo_reflect_spec.rb +175 -0
  46. data/spec/ryo_spec.rb +130 -0
  47. data/spec/setup.rb +5 -0
  48. metadata +173 -0
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+ require "ostruct"
6
+
7
+ point = Ryo.from(
8
+ OpenStruct.new(x: {to_i: 5}),
9
+ Ryo.from(y: {to_i: 10})
10
+ )
11
+ p [point.x.to_i, point.y.to_i]
12
+
13
+ ##
14
+ # [5, 10]
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ point_x = Ryo::BasicObject(x: 0)
7
+ point_y = Ryo::BasicObject({y: 0}, point_x)
8
+ point = Ryo::BasicObject({}, point_y)
9
+ p [point.x, point.y]
10
+
11
+ ##
12
+ # [0, 0]
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ point = Ryo::BasicObject.from({
7
+ x: {to_i: 2},
8
+ y: {to_i: 4}
9
+ })
10
+ p [point.x.to_i, point.y.to_i]
11
+
12
+ ##
13
+ # [2, 4]
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ ryo = Ryo::Object(then: 12)
7
+ p ryo.then # => 12
8
+ p ryo.then { 34 } # => 34
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ class Point
7
+ def initialize
8
+ @x = 5
9
+ @y = 10
10
+ end
11
+
12
+ def each_pair
13
+ yield("x", @x)
14
+ yield("y", @y)
15
+ end
16
+ end
17
+
18
+ option = Ryo(Point.new)
19
+ p option.x # => 5
20
+ p option.y # => 10
@@ -0,0 +1,14 @@
1
+ require_relative "setup"
2
+ require "ryo"
3
+
4
+ point_x = Ryo(x: Ryo.lazy { 5 })
5
+ point_y = Ryo({y: Ryo.lazy { 10 }}, point_x)
6
+ point = Ryo({sum: Ryo.lazy { x + y }}, point_y)
7
+ print "point.x = ", point.x, "\n"
8
+ print "point.y = ", point.y, "\n"
9
+ print "point.sum = ", point.sum, "\n"
10
+
11
+ ##
12
+ # point.x = 5
13
+ # point.y = 10
14
+ # point.sum = 15
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "test-cmd"
5
+
6
+ RSpec.describe "README.md examples" do
7
+ run_example = ->(file) do
8
+ Test::Cmd.cmd("ruby share/ryo.rb/examples/#{file}")
9
+ end
10
+
11
+ subject do
12
+ run_example.(file).stdout.chomp
13
+ end
14
+
15
+ context "when given 1.0_prototypes_point_object.rb" do
16
+ let(:file) { "1.0_prototypes_point_object.rb" }
17
+ it { is_expected.to eq("[5, 10]") }
18
+ end
19
+
20
+ context "when given 1.1_prototypes_ryo_fn.rb" do
21
+ let(:file) { "1.1_prototypes_ryo_fn.rb" }
22
+ it { is_expected.to eq("[10, 20]") }
23
+ end
24
+
25
+ context "when given 2.0_iteration_each.rb" do
26
+ let(:file) { "2.0_iteration_each.rb" }
27
+ it { is_expected.to eq(%(["x", 10]\n["y", 20])) }
28
+ end
29
+
30
+ context "when given 2.1_iteration_map.rb" do
31
+ let(:file) { "2.1_iteration_map.rb" }
32
+ it { is_expected.to eq("[4, 8]\n[4, 8]") }
33
+ end
34
+
35
+ context "when given 2.2_iteration_ancestors.rb" do
36
+ let(:file) { "2.2_iteration_ancestors.rb" }
37
+ it { is_expected.to eq("nil\nnil\n5\n5") }
38
+ end
39
+
40
+ context "when given 3.0_recursion_ryo_from.rb" do
41
+ let(:file) { "3.0_recursion_ryo_from.rb" }
42
+ it { is_expected.to eq("[0, 10]") }
43
+ end
44
+
45
+ context "when given 3.1_recursion_ryo_from_with_array.rb" do
46
+ let(:file) { "3.1_recursion_ryo_from_with_array.rb" }
47
+ it { is_expected.to eq(%(2\n"foobar"\n4)) }
48
+ end
49
+
50
+ context "when given 3.2_recursion_ryo_from_with_openstruct.rb" do
51
+ let(:file) { "3.2_recursion_ryo_from_with_openstruct.rb" }
52
+ it { is_expected.to eq("[5, 10]") }
53
+ end
54
+
55
+ context "when given 4.0_basicobject_ryo_basicobject.rb" do
56
+ let(:file) { "4.0_basicobject_ryo_basicobject.rb" }
57
+ it { is_expected.to eq("[0, 0]") }
58
+ end
59
+
60
+ context "when given 4.1_basicobject_ryo_basicobject_from.rb" do
61
+ let(:file) { "4.1_basicobject_ryo_basicobject_from.rb" }
62
+ it { is_expected.to eq("[2, 4]") }
63
+ end
64
+
65
+ context "when given 5_collisions_resolution_strategy.rb" do
66
+ let(:file) { "5_collisions_resolution_strategy.rb" }
67
+ it { is_expected.to eq("12\n34") }
68
+ end
69
+
70
+ context "when given 6_beyond_hash_objects.rb" do
71
+ let(:file) { "6_beyond_hash_objects.rb" }
72
+ it { is_expected.to eq("5\n10") }
73
+ end
74
+
75
+ context "when given 7_ryo_lazy.rb" do
76
+ let(:file) { "7_ryo_lazy.rb" }
77
+ it { is_expected.to eq("point.x = 5\npoint.y = 10\npoint.sum = 15") }
78
+ end
79
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ostruct"
5
+
6
+ RSpec.describe Ryo::BasicObject do
7
+ describe "Ryo::BasicObject()" do
8
+ context "when given a Hash" do
9
+ subject { [point.x, point.y] }
10
+ let(:point) { Ryo::BasicObject(x: 0, y: 0) }
11
+ it { is_expected.to eq([0, 0]) }
12
+ end
13
+
14
+ context "when given an OpenStruct" do
15
+ subject { [point.x, point.y] }
16
+ let(:point) { Ryo::BasicObject(OpenStruct.new(x: 0, y: 0)) }
17
+ it { is_expected.to eq([0, 0]) }
18
+
19
+ context "when verifying the object is a Ryo object" do
20
+ subject { Ryo === point }
21
+ it { is_expected.to be(true) }
22
+ end
23
+ end
24
+ end
25
+
26
+ describe ".from" do
27
+ context "when given an instance of Ryo::BasicObject" do
28
+ subject { Ryo.from(point) }
29
+ let(:point) { Ryo::BasicObject(x: 5, y: 10) }
30
+ it { is_expected.to eq(point) }
31
+ end
32
+
33
+ context "when given nested Hash objects" do
34
+ subject { point.x.to_i }
35
+ let(:point) { Ryo::BasicObject.from({x: {to_i: 4}}) }
36
+ it { is_expected.to eq(4) }
37
+ end
38
+
39
+ context "when given an Array that contains nested Hash objects" do
40
+ subject { points[0].x.to_i }
41
+ let(:points) { Ryo::BasicObject.from([{x: {to_i: 4}}]) }
42
+ it { is_expected.to eq(4) }
43
+ end
44
+
45
+ context "with a prototype" do
46
+ let(:point_a) { Ryo::BasicObject.from(x: {to_i: 0}) }
47
+ let(:point_b) { Ryo::BasicObject.from({y: {to_i: 2}}, point_a) }
48
+
49
+ context "when traversing to the prototype (point_a)" do
50
+ subject { point_b.x.to_i }
51
+ it { is_expected.to eq(0) }
52
+ end
53
+
54
+ context "when verifying a nested Hash doesn't inherit the prototype (point_a)" do
55
+ subject { point_b.y.x }
56
+ it { is_expected.to eq(nil) }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe Ryo::Enumerable do
6
+ describe ".each" do
7
+ context "when verifying each traverses through the prototype chain" do
8
+ subject { Ryo.each(point_c).map { [_1, _2] } }
9
+ let(:point_a) { Ryo(x: 0) }
10
+ let(:point_b) { Ryo({y: 5}, point_a) }
11
+ let(:point_c) { Ryo({}, point_b) }
12
+ it { is_expected.to eq([["y", 5], ["x", 0]]) }
13
+ end
14
+ end
15
+
16
+ describe ".map" do
17
+ subject(:point_c) { Ryo.map(point_b) { _2 * 2 } }
18
+ let(:point_a) { Ryo::BasicObject(x: 4, y: 4) }
19
+ let(:point_b) { Ryo::BasicObject({x: 2, y: 2}, point_a) }
20
+
21
+ context "when verifying the map operation" do
22
+ it { is_expected.to eq({x: 4, y: 4}) }
23
+ end
24
+
25
+ context "when verifying the map operation on the prototype" do
26
+ subject { point_a }
27
+ before { Ryo.map!(point_b) { _2 * 2 } }
28
+ it { is_expected.to eq({x: 8, y: 8}) }
29
+ end
30
+
31
+ context "when verifying the map operation returns a new object" do
32
+ subject { Ryo.kernel(:equal?).bind_call(point_b, point_c) }
33
+ it { is_expected.to be(false) }
34
+ end
35
+ end
36
+
37
+ describe ".select" do
38
+ context "with prototype chain traversal" do
39
+ subject { Ryo.select(point_b) { _1 == "y" and _2 == 4 } }
40
+ let(:point_a) { Ryo::BasicObject(x: 1, y: 2) }
41
+ let(:point_b) { Ryo::BasicObject({x: 3, y: 4}, point_a) }
42
+
43
+ context "when verifying the filter operation" do
44
+ it { is_expected.to eq(y: 4) }
45
+ end
46
+
47
+ context "when verifying the filter operation on the prototype" do
48
+ subject { point_a.y }
49
+ before { Ryo.select!(point_b) { _1 == "x" } }
50
+ it { is_expected.to eq(nil) }
51
+ end
52
+ end
53
+ end
54
+
55
+ describe ".reject" do
56
+ context "with prototype chain traversal" do
57
+ subject { Ryo.reject(point_b) { _1 == "x" } }
58
+ let(:point_a) { Ryo::BasicObject(x: 1, y: 2) }
59
+ let(:point_b) { Ryo::BasicObject({x: 3, y: 4}, point_a) }
60
+
61
+ context "when verifying the filter operation" do
62
+ it { is_expected.to eq(y: 4) }
63
+ end
64
+
65
+ context "when verifying the filter operation on the prototype" do
66
+ subject { point_a.y }
67
+ before { Ryo.reject!(point_b) { _1 == "y" } }
68
+ it { is_expected.to eq(nil) }
69
+ end
70
+ end
71
+ end
72
+
73
+ describe ".any?" do
74
+ let(:point_a) { Ryo::BasicObject(y: 10) }
75
+ let(:point_b) { Ryo::BasicObject({x: 5}, point_a) }
76
+
77
+ context "when an iteration returns a truthy value" do
78
+ subject { Ryo.any?(point_b) { _2 > 5 } }
79
+ it { is_expected.to be(true) }
80
+ end
81
+
82
+ context "when an iteration fails to return a truthy value" do
83
+ subject { Ryo.any?(point_b) { _2 > 20 } }
84
+ it { is_expected.to be(false) }
85
+ end
86
+ end
87
+
88
+ describe ".all?" do
89
+ let(:point_a) { Ryo::BasicObject(y: 10) }
90
+ let(:point_b) { Ryo::BasicObject({x: 5}, point_a) }
91
+ let(:point_c) { Ryo::BasicObject({z: 0}, point_b) }
92
+
93
+ context "when every iteration returns a truthy value" do
94
+ subject { Ryo.all?(point_c) { _2 < 11 } }
95
+ it { is_expected.to be(true) }
96
+ end
97
+
98
+ context "when an iteration fails to return a truthy value" do
99
+ subject { Ryo.all?(point_c) { _2 < 5 } }
100
+ it { is_expected.to be(false) }
101
+ end
102
+ end
103
+
104
+ describe ".find" do
105
+ let(:point_a) { Ryo::BasicObject(x: 5) }
106
+ let(:point_b) { Ryo::BasicObject({y: 10}, point_a) }
107
+ let(:point_c) { Ryo::BasicObject({z: 15}, point_b) }
108
+
109
+ context "when an iteration yields true on point_a" do
110
+ subject { Ryo.find(point_c) { _2 == 5 } }
111
+ it { is_expected.to be(point_a) }
112
+ end
113
+
114
+ context "when an iteration yields true on point_b" do
115
+ subject { Ryo.find(point_c) { _2 == 10 } }
116
+ it { is_expected.to be(point_b) }
117
+ end
118
+
119
+ context "when an iteration yields true on point_c" do
120
+ subject { Ryo.find(point_c) { _2 == 15 } }
121
+ it { is_expected.to be(point_c) }
122
+ end
123
+
124
+ context "when an iteration never yields true" do
125
+ subject { Ryo.find(point_c) { _2 == 20 } }
126
+ it { is_expected.to eq(nil) }
127
+ end
128
+
129
+ context "when ancestors is set to zero" do
130
+ context "when the condition matches for point_a" do
131
+ subject { Ryo.find(point_c, ancestors: 0) { _2 == 5 } }
132
+ it { is_expected.to be_nil }
133
+ end
134
+
135
+ context "when the condition matches for point_b" do
136
+ subject { Ryo.find(point_c, ancestors: 0) { _2 == 10 } }
137
+ it { is_expected.to be_nil }
138
+ end
139
+
140
+ context "when the condition matches for point_c" do
141
+ subject { Ryo.find(point_c, ancestors: 0) { _2 == 15 } }
142
+ it { is_expected.to be(point_c) }
143
+ end
144
+ end
145
+
146
+ context "when ancestors is set to one" do
147
+ context "when the condition matches for point_a" do
148
+ subject { Ryo.find(point_c, ancestors: 1) { _2 == 5 } }
149
+ it { is_expected.to be_nil }
150
+ end
151
+
152
+ context "when the condition matches for point_b" do
153
+ subject { Ryo.find(point_c, ancestors: 1) { _2 == 10 } }
154
+ it { is_expected.to be(point_b) }
155
+ end
156
+
157
+ context "when the condition matches for point_c" do
158
+ subject { Ryo.find(point_c, ancestors: 1) { _2 == 15 } }
159
+ it { is_expected.to be(point_c) }
160
+ end
161
+ end
162
+
163
+ context "when ancestors is set to two" do
164
+ context "when the condition matches for point_a" do
165
+ subject { Ryo.find(point_c, ancestors: 2) { _2 == 5 } }
166
+ it { is_expected.to be(point_a) }
167
+ end
168
+
169
+ context "when the condition matches for point_b" do
170
+ subject { Ryo.find(point_c, ancestors: 2) { _2 == 10 } }
171
+ it { is_expected.to be(point_b) }
172
+ end
173
+
174
+ context "when the condition matches for point_c" do
175
+ subject { Ryo.find(point_c, ancestors: 2) { _2 == 15 } }
176
+ it { is_expected.to be(point_c) }
177
+ end
178
+ end
179
+
180
+ context "when ancestors is set to three (or higher)" do
181
+ context "when the condition matches for point_a" do
182
+ subject { Ryo.find(point_c, ancestors: 3) { _2 == 5 } }
183
+ it { is_expected.to be(point_a) }
184
+ end
185
+
186
+ context "when the condition matches for point_b" do
187
+ subject { Ryo.find(point_c, ancestors: 3) { _2 == 10 } }
188
+ it { is_expected.to be(point_b) }
189
+ end
190
+
191
+ context "when the condition matches for point_c" do
192
+ subject { Ryo.find(point_c, ancestors: 3) { _2 == 15 } }
193
+ it { is_expected.to be(point_c) }
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe Ryo::Keywords do
6
+ describe ".function" do
7
+ let(:point) { Ryo(move: Ryo.fn { |x, y| [x, y] }) }
8
+
9
+ context "when the function requires argument(s)" do
10
+ context "when the required argument is not given" do
11
+ subject { point.move.() }
12
+ it { expect { is_expected }.to raise_error(ArgumentError) }
13
+ end
14
+
15
+ context "when the required argument is given" do
16
+ subject { point.move.(30, 50) }
17
+ it { is_expected.to eq([30, 50]) }
18
+ end
19
+ end
20
+
21
+ context "when the function receives a block" do
22
+ subject { point.move.() { "block" } }
23
+ let(:point) { Ryo(move: Ryo.fn { |&b| b.() }) }
24
+ it { is_expected.to eq("block") }
25
+ end
26
+ end
27
+
28
+ describe ".delete" do
29
+ let(:point_a) { Ryo(x: 0) }
30
+ let(:point_b) { Ryo({x: 1}, point_a) }
31
+
32
+ context "with no prototype" do
33
+ context "when a property is deleted" do
34
+ subject { point_a.x }
35
+ before { Ryo.delete(point_a, "x") }
36
+ it { is_expected.to be_nil }
37
+ end
38
+ end
39
+
40
+ context "with a prototype" do
41
+ context "when a property is deleted from point_a" do
42
+ subject { point_b.x }
43
+ before { Ryo.delete(point_a, "x") }
44
+ it { is_expected.to eq(1) }
45
+ end
46
+
47
+ context "when a property is deleted from point_b" do
48
+ subject { point_b.x }
49
+ before { Ryo.delete(point_b, "x") }
50
+ it { is_expected.to eq(0) }
51
+ end
52
+
53
+ context "when a property is deleted from both point_a / point_b" do
54
+ subject { point_b.x }
55
+ before { [point_a, point_b].each { Ryo.delete(_1, "x") } }
56
+ it { is_expected.to be(nil) }
57
+ end
58
+ end
59
+ end
60
+
61
+ describe ".in?" do
62
+ let(:point_a) { Ryo(x: 0) }
63
+ let(:point_b) { Ryo({y: 1}, point_a) }
64
+ let(:point_c) { Ryo({z: 2}, point_b) }
65
+
66
+ context "when given 'x' as a property name" do
67
+ subject { Ryo.in?(point_c, "x") }
68
+ it { is_expected.to be(true) }
69
+ end
70
+
71
+ context "when given 'y' as a property name" do
72
+ subject { Ryo.in?(point_c, "y") }
73
+ it { is_expected.to be(true) }
74
+ end
75
+
76
+ context "when given 'z' as a property name" do
77
+ subject { Ryo.in?(point_c, "z") }
78
+ it { is_expected.to be(true) }
79
+ end
80
+
81
+ context "when given 'w' as a property name" do
82
+ subject { Ryo.in?(point_c, "w") }
83
+ it { is_expected.to be(false) }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe "Ryo objects" do
6
+ let(:car) { Ryo(name: "Car") }
7
+
8
+ describe "#respond_to?" do
9
+ context "when a property is defined" do
10
+ subject { car.respond_to?(:name) }
11
+ it { is_expected.to be(true) }
12
+ end
13
+
14
+ context "when a property is not defined" do
15
+ subject { car.respond_to?(:foobar) }
16
+ it { is_expected.to be(true) }
17
+ end
18
+ end
19
+
20
+ describe "#method_missing" do
21
+ context "when a property doesn't exist" do
22
+ subject { car.foobar }
23
+ it { is_expected.to eq(nil) }
24
+ end
25
+ end
26
+
27
+ describe "#eql?" do
28
+ context "when two objects are equal" do
29
+ subject { car == car_2 }
30
+ let(:car_2) { Ryo(name: "Car") }
31
+ it { is_expected.to be(true) }
32
+ end
33
+
34
+ context "when an object and a Hash are equal" do
35
+ subject { car == {"name" => "Car"} }
36
+ it { is_expected.to be(true) }
37
+ end
38
+
39
+ context "when an object and symbol-key Hash are equal" do
40
+ subject { car == {name: "Car"} }
41
+ it { is_expected.to be(true) }
42
+ end
43
+
44
+ context "when an object and nested symbol-key Hash are equal" do
45
+ subject { car == {name: "Car", wheels: {quantity: 4, weight: {lbs: "50"}}} }
46
+ let(:car) { Ryo.from(name: "Car", wheels: {quantity: 4, weight: {lbs: "50"}}) }
47
+ it { is_expected.to be(true) }
48
+ end
49
+
50
+ context "when an object is compared against nil" do
51
+ subject { car == nil }
52
+ it { is_expected.to be(false) }
53
+ end
54
+ end
55
+
56
+ describe "when a property overshadows a method" do
57
+ let(:car) do
58
+ Ryo(tap: "property")
59
+ end
60
+
61
+ context "when a block is not given" do
62
+ subject { car.tap }
63
+ it { is_expected.to eq("property") }
64
+ end
65
+
66
+ context "when a block is given" do
67
+ subject { car.tap {} }
68
+ it { is_expected.to eq(car) }
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe "Prototypes" do
6
+ context "when there is one prototype" do
7
+ let(:root) { Ryo(name: "root") }
8
+ let(:node) { Ryo({}, root) }
9
+
10
+ context "when traversing to a property on the root prototype" do
11
+ subject { node.name }
12
+ it { is_expected.to eq("root") }
13
+ end
14
+
15
+ context "when a property is deleted from the root prototype" do
16
+ subject { node.name }
17
+ before { Ryo.delete(root, "name") }
18
+ it { is_expected.to eq(nil) }
19
+ end
20
+ end
21
+
22
+ context "when there are two prototypes" do
23
+ let(:root) { Ryo(name: "root") }
24
+ let(:node_1) { Ryo({}, root) }
25
+ let(:node_2) { Ryo({}, node_1) }
26
+
27
+ context "when traversing to a property on the root prototype" do
28
+ subject { node_2.name }
29
+ it { is_expected.to eq("root") }
30
+ end
31
+
32
+ context "when traversing to a property on the middle prototype" do
33
+ subject { node_2.name }
34
+ let(:node_1) { Ryo({name: "Node 1"}, root) }
35
+ it { is_expected.to eq("Node 1") }
36
+ end
37
+
38
+ context "when a property is deleted from the middle prototype" do
39
+ subject { node_2.name }
40
+ let(:node_1) { Ryo({name: "Node 1"}, root) }
41
+ before { Ryo.delete(node_1, "name") }
42
+ it { is_expected.to eq("root") }
43
+ end
44
+ end
45
+ end