ryo.rb 0.4.4
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.
- checksums.yaml +7 -0
- data/.github/workflows/specs.yml +23 -0
- data/.gitignore +6 -0
- data/.gitlab-ci.yml +9 -0
- data/.rubocop.yml +56 -0
- data/.yardoc-template/default/fulldoc/html/css/0x1eef.css +15 -0
- data/.yardoc-template/default/layout/html/setup.rb +5 -0
- data/.yardoc-template/default/module/setup.rb +7 -0
- data/.yardopts +4 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +373 -0
- data/Rakefile +3 -0
- data/lib/ryo/basic_object.rb +58 -0
- data/lib/ryo/builder.rb +106 -0
- data/lib/ryo/enumerable.rb +214 -0
- data/lib/ryo/function.rb +68 -0
- data/lib/ryo/keywords.rb +67 -0
- data/lib/ryo/lazy.rb +4 -0
- data/lib/ryo/object.rb +58 -0
- data/lib/ryo/reflect.rb +379 -0
- data/lib/ryo/version.rb +5 -0
- data/lib/ryo.rb +197 -0
- data/ryo.rb.gemspec +21 -0
- data/share/ryo.rb/examples/1.0_prototypes_point_object.rb +12 -0
- data/share/ryo.rb/examples/1.1_prototypes_ryo_fn.rb +14 -0
- data/share/ryo.rb/examples/2.0_iteration_each.rb +13 -0
- data/share/ryo.rb/examples/2.1_iteration_map.rb +16 -0
- data/share/ryo.rb/examples/2.2_iteration_ancestors.rb +13 -0
- data/share/ryo.rb/examples/3.0_recursion_ryo_from.rb +13 -0
- data/share/ryo.rb/examples/3.1_recursion_ryo_from_with_array.rb +19 -0
- data/share/ryo.rb/examples/3.2_recursion_ryo_from_with_openstruct.rb +14 -0
- data/share/ryo.rb/examples/4.0_basicobject_ryo_basicobject.rb +12 -0
- data/share/ryo.rb/examples/4.1_basicobject_ryo_basicobject_from.rb +13 -0
- data/share/ryo.rb/examples/5_collisions_resolution_strategy.rb +8 -0
- data/share/ryo.rb/examples/6_beyond_hash_objects.rb +20 -0
- data/share/ryo.rb/examples/7_ryo_lazy.rb +14 -0
- data/share/ryo.rb/examples/setup.rb +3 -0
- data/spec/readme_spec.rb +79 -0
- data/spec/ryo_basic_object_spec.rb +60 -0
- data/spec/ryo_enumerable_spec.rb +197 -0
- data/spec/ryo_keywords_spec.rb +86 -0
- data/spec/ryo_object_spec.rb +71 -0
- data/spec/ryo_prototypes_spec.rb +45 -0
- data/spec/ryo_reflect_spec.rb +175 -0
- data/spec/ryo_spec.rb +130 -0
- data/spec/setup.rb +5 -0
- metadata +173 -0
@@ -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
|
data/spec/readme_spec.rb
ADDED
@@ -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
|