vector2d 2.3.0 → 3.0.0
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 +4 -4
- data/.github/workflows/build.yml +1 -1
- data/.gitignore +3 -1
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +1 -1
- data/.yardopts +11 -0
- data/CHANGELOG.md +216 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +35 -5
- data/README.md +325 -12
- data/Rakefile +18 -0
- data/benchmark/vector_comparison.rb +129 -0
- data/lib/vector2d/angles.rb +315 -0
- data/lib/vector2d/arithmetic.rb +97 -0
- data/lib/vector2d/comparison.rb +188 -0
- data/lib/vector2d/componentwise.rb +239 -0
- data/lib/vector2d/constructors.rb +137 -0
- data/lib/vector2d/conversions.rb +139 -0
- data/lib/vector2d/coordinates.rb +22 -0
- data/lib/vector2d/deprecation.rb +16 -0
- data/lib/vector2d/dimensions.rb +297 -0
- data/lib/vector2d/interpolation.rb +191 -0
- data/lib/vector2d/lengths.rb +284 -0
- data/lib/vector2d/matrix_interop.rb +93 -0
- data/lib/vector2d/parsing.rb +142 -0
- data/lib/vector2d/projection.rb +208 -0
- data/lib/vector2d/version.rb +2 -1
- data/lib/vector2d.rb +172 -60
- data/spec/lib/vector2d/angles_spec.rb +441 -0
- data/spec/lib/vector2d/{calculations_spec.rb → arithmetic_spec.rb} +48 -58
- data/spec/lib/vector2d/comparison_spec.rb +358 -0
- data/spec/lib/vector2d/componentwise_spec.rb +300 -0
- data/spec/lib/vector2d/constructors_spec.rb +299 -0
- data/spec/lib/vector2d/conversions_spec.rb +234 -0
- data/spec/lib/vector2d/dimensions_spec.rb +820 -0
- data/spec/lib/vector2d/interpolation_spec.rb +322 -0
- data/spec/lib/vector2d/lengths_spec.rb +457 -0
- data/spec/lib/vector2d/matrix_interop_spec.rb +92 -0
- data/spec/lib/vector2d/parsing_spec.rb +329 -0
- data/spec/lib/vector2d/projection_spec.rb +306 -0
- data/spec/lib/vector2d_documentation_spec.rb +38 -0
- data/spec/lib/vector2d_immutability_spec.rb +263 -0
- data/spec/lib/vector2d_spec.rb +90 -50
- data/spec/lib/vector2d_subclassing_spec.rb +210 -0
- data/spec/lib/vector2d_tags_spec.rb +43 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/doc_examples/comments.rb +65 -0
- data/spec/support/doc_examples/markdown.rb +50 -0
- data/spec/support/doc_examples.rb +94 -0
- data/spec/support/doc_tags.rb +151 -0
- data/spec/support/shared_examples/class_preserving_method.rb +10 -0
- data/spec/support/shared_examples/deprecated_method.rb +25 -0
- data/spec/support/shared_examples/parsed_vector.rb +11 -0
- data/vector2d.gemspec +2 -1
- metadata +42 -13
- data/.travis.yml +0 -10
- data/lib/vector2d/calculations.rb +0 -141
- data/lib/vector2d/coercions.rb +0 -64
- data/lib/vector2d/fitting.rb +0 -60
- data/lib/vector2d/properties.rb +0 -46
- data/lib/vector2d/transformations.rb +0 -98
- data/spec/lib/vector2d/coercions_spec.rb +0 -59
- data/spec/lib/vector2d/fitting_spec.rb +0 -70
- data/spec/lib/vector2d/properties_spec.rb +0 -47
- data/spec/lib/vector2d/transformations_spec.rb +0 -139
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "bigdecimal"
|
|
5
|
+
|
|
6
|
+
describe Vector2d::Constructors do
|
|
7
|
+
describe ".from_angle" do
|
|
8
|
+
context "with an angle of zero" do
|
|
9
|
+
subject(:vector) { Vector2d.from_angle(0) }
|
|
10
|
+
|
|
11
|
+
it_behaves_like "a parsed vector", [1.0, 0.0]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "with a quarter turn" do
|
|
15
|
+
subject(:vector) { Vector2d.from_angle(Math::PI / 2) }
|
|
16
|
+
|
|
17
|
+
its(:x) { is_expected.to be_within(0.0001).of(0.0) }
|
|
18
|
+
its(:y) { is_expected.to be_within(0.0001).of(1.0) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context "with a negative angle" do
|
|
22
|
+
subject(:vector) { Vector2d.from_angle(-Math::PI / 2) }
|
|
23
|
+
|
|
24
|
+
its(:x) { is_expected.to be_within(0.0001).of(0.0) }
|
|
25
|
+
its(:y) { is_expected.to be_within(0.0001).of(-1.0) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "without a length" do
|
|
29
|
+
subject(:vector) { Vector2d.from_angle(0.9827) }
|
|
30
|
+
|
|
31
|
+
its(:length) { is_expected.to be_within(0.0001).of(1.0) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "with a length" do
|
|
35
|
+
subject(:vector) { Vector2d.from_angle(0.9827, 3.6055) }
|
|
36
|
+
|
|
37
|
+
its(:length) { is_expected.to be_within(0.0001).of(3.6055) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "with an integer length" do
|
|
41
|
+
subject(:vector) { Vector2d.from_angle(0, 2) }
|
|
42
|
+
|
|
43
|
+
its(:x) { is_expected.to be_a(Float) }
|
|
44
|
+
its(:y) { is_expected.to be_a(Float) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
[2, Math::PI / 2, Rational(1, 2), BigDecimal("1.5707963267948966")].each do |angle|
|
|
48
|
+
context "with a #{angle.class} angle" do
|
|
49
|
+
subject(:vector) { Vector2d.from_angle(angle) }
|
|
50
|
+
|
|
51
|
+
it "returns float coordinates" do
|
|
52
|
+
expect(vector.to_a).to all(be_an_instance_of(Float))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "returns the angle it was given" do
|
|
56
|
+
expect(vector.angle).to be_within(0.0001).of(angle.to_f)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
[2, 2.0, Rational(2, 1), BigDecimal("2")].each do |length|
|
|
62
|
+
context "with a #{length.class} length" do
|
|
63
|
+
subject(:vector) { Vector2d.from_angle(0.9827, length) }
|
|
64
|
+
|
|
65
|
+
it "returns float coordinates" do
|
|
66
|
+
expect(vector.to_a).to all(be_an_instance_of(Float))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "applies the length" do
|
|
70
|
+
expect(vector.length).to be_within(0.0001).of(2.0)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "is the inverse of #to_polar" do
|
|
76
|
+
length, angle = Vector2d.new(2, 3).to_polar
|
|
77
|
+
|
|
78
|
+
expect(Vector2d.from_angle(angle, length))
|
|
79
|
+
.to eq(Vector2d.new(2.0, 3.0))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "returns the angle it was given" do
|
|
83
|
+
expect(Vector2d.from_angle(0.9827).angle)
|
|
84
|
+
.to be_within(0.0001).of(0.9827)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "with a non-numeric angle" do
|
|
88
|
+
it "raises an error" do
|
|
89
|
+
expect { Vector2d.from_angle("1") }.to(
|
|
90
|
+
raise_error(ArgumentError, 'not a valid coordinate: "1"')
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context "with a non-numeric length" do
|
|
96
|
+
it "raises an error" do
|
|
97
|
+
expect { Vector2d.from_angle(0, nil) }.to(
|
|
98
|
+
raise_error(ArgumentError, "not a valid coordinate: nil")
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context "with a complex angle" do
|
|
104
|
+
it "raises an error" do
|
|
105
|
+
expect { Vector2d.from_angle(Complex(1, 2)) }.to(
|
|
106
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
context "with a complex length" do
|
|
112
|
+
it "raises an error" do
|
|
113
|
+
expect { Vector2d.from_angle(0, Complex(1, 2)) }.to(
|
|
114
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe ".random" do
|
|
121
|
+
it "returns a unit vector" do
|
|
122
|
+
expect(Vector2d.random.length).to be_within(1e-12).of(1.0)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "returns a vector of the given length" do
|
|
126
|
+
expect(Vector2d.random(2.5).length).to be_within(1e-12).of(2.5)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "returns floats" do
|
|
130
|
+
expect(Vector2d.random.to_a).to all(be_a(Float))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "returns a frozen vector" do
|
|
134
|
+
expect(Vector2d.random).to be_frozen
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "stays within the unit circle over many draws" do
|
|
138
|
+
lengths = 200.times.map { Vector2d.random.length }
|
|
139
|
+
|
|
140
|
+
expect(lengths).to all(be_within(1e-12).of(1.0))
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context "with a seeded Random" do
|
|
144
|
+
it "repeats the same vector for the same seed" do
|
|
145
|
+
first = Vector2d.random(random: Random.new(42))
|
|
146
|
+
|
|
147
|
+
expect(Vector2d.random(random: Random.new(42))).to eq(first)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "returns a different vector for a different seed" do
|
|
151
|
+
expect(Vector2d.random(random: Random.new(42)))
|
|
152
|
+
.not_to eq(Vector2d.random(random: Random.new(43)))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "advances the sequence between draws" do
|
|
156
|
+
random = Random.new(42)
|
|
157
|
+
|
|
158
|
+
expect(Vector2d.random(random: random))
|
|
159
|
+
.not_to eq(Vector2d.random(random: random))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "applies the length" do
|
|
163
|
+
expect(Vector2d.random(3.0, random: Random.new(42)).length)
|
|
164
|
+
.to be_within(1e-12).of(3.0)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context "with a stubbed Random" do
|
|
169
|
+
let(:random) { instance_double(Random, rand: 0.25) }
|
|
170
|
+
|
|
171
|
+
it "maps the draw onto a full turn" do
|
|
172
|
+
expect(Vector2d.random(random: random))
|
|
173
|
+
.to eq(Vector2d.from_angle(Math::PI / 2))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it "covers the whole circle" do
|
|
177
|
+
allow(random).to receive(:rand).and_return(0.75)
|
|
178
|
+
|
|
179
|
+
expect(Vector2d.random(random: random))
|
|
180
|
+
.to eq(Vector2d.from_angle(1.5 * Math::PI))
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "reaches every quadrant" do
|
|
185
|
+
random = Random.new(1234)
|
|
186
|
+
quadrants = 200.times.map do
|
|
187
|
+
Vector2d.random(random: random).to_a.map(&:negative?)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
expect(quadrants.uniq.size).to eq(4)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
[2, 2.0, Rational(2, 1), BigDecimal("2")].each do |length|
|
|
194
|
+
context "with a #{length.class} length" do
|
|
195
|
+
subject(:vector) { Vector2d.random(length, random: Random.new(42)) }
|
|
196
|
+
|
|
197
|
+
it "returns float coordinates" do
|
|
198
|
+
expect(vector.to_a).to all(be_an_instance_of(Float))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "applies the length" do
|
|
202
|
+
expect(vector.length).to be_within(1e-12).of(2.0)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
context "with a non-numeric length" do
|
|
208
|
+
it "raises an error" do
|
|
209
|
+
expect { Vector2d.random(nil) }.to(
|
|
210
|
+
raise_error(ArgumentError, "not a valid coordinate: nil")
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
context "with a complex length" do
|
|
216
|
+
it "raises an error" do
|
|
217
|
+
expect { Vector2d.random(Complex(1, 2)) }.to(
|
|
218
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
describe "the direction constants" do
|
|
225
|
+
{
|
|
226
|
+
zero: [0, 0],
|
|
227
|
+
one: [1, 1],
|
|
228
|
+
up: [0, 1],
|
|
229
|
+
down: [0, -1],
|
|
230
|
+
left: [-1, 0],
|
|
231
|
+
right: [1, 0]
|
|
232
|
+
}.each do |name, coordinates|
|
|
233
|
+
describe ".#{name}" do
|
|
234
|
+
subject(:vector) { Vector2d.public_send(name) }
|
|
235
|
+
|
|
236
|
+
it "has the expected coordinates" do
|
|
237
|
+
expect(vector.to_a).to eq(coordinates)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "has integer coordinates" do
|
|
241
|
+
expect(vector.to_a).to all(be_an(Integer))
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it { is_expected.to be_frozen }
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it "points up along the positive y axis" do
|
|
249
|
+
expect(Vector2d.up.angle).to be_within(1e-12).of(Math::PI / 2)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it "points down along the negative y axis" do
|
|
253
|
+
expect(Vector2d.down.angle).to be_within(1e-12).of(-Math::PI / 2)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
it "points right along the zero angle" do
|
|
257
|
+
expect(Vector2d.right.angle).to eq(0.0)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it "points left along the straight angle" do
|
|
261
|
+
expect(Vector2d.left.angle).to be_within(1e-12).of(Math::PI)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it "reverses up into down" do
|
|
265
|
+
expect(Vector2d.up.reverse).to eq(Vector2d.down)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it "reverses left into right" do
|
|
269
|
+
expect(Vector2d.left.reverse).to eq(Vector2d.right)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "rotates right into up a quarter turn counterclockwise" do
|
|
273
|
+
expect(Vector2d.right.rotate(Math::PI / 2).round)
|
|
274
|
+
.to eq(Vector2d.up)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it "takes the perpendicular of right to up" do
|
|
278
|
+
expect(Vector2d.right.perpendicular).to eq(Vector2d.up)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it "keeps integer coordinates through integer arithmetic" do
|
|
282
|
+
expect((Vector2d.up * 2).to_a).to all(be_an(Integer))
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it "widens to floats when a float is involved" do
|
|
286
|
+
expect((Vector2d.up * 0.5).to_a).to all(be_a(Float))
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "returns an equal vector on every call" do
|
|
290
|
+
first = Vector2d.zero
|
|
291
|
+
|
|
292
|
+
expect(Vector2d.zero).to eql(first)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it "is the zero vector" do
|
|
296
|
+
expect(Vector2d.zero).to be_zero
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Vector2d::Conversions do
|
|
6
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
|
7
|
+
|
|
8
|
+
describe "#coerce" do
|
|
9
|
+
it "returns the other operand as a vector, followed by itself" do
|
|
10
|
+
expect(vector.coerce(4)).to eq([Vector2d.new(4, 4), vector])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "makes a vector the right hand operand of a scalar" do
|
|
14
|
+
expect(2 * vector).to eq(Vector2d.new(4, 6))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "retains the operand order of the original expression" do
|
|
18
|
+
expect(10 - vector).to eq(Vector2d.new(8, 7))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context "when the other operand isn't parseable" do
|
|
22
|
+
it "raises a TypeError" do
|
|
23
|
+
expect { vector.coerce(Object.new) }.to(
|
|
24
|
+
raise_error(TypeError, "Object can't be coerced into Vector2d")
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "when the other operand is complex" do
|
|
30
|
+
it "raises a TypeError" do
|
|
31
|
+
expect { vector.coerce(Complex(1, 2)) }.to(
|
|
32
|
+
raise_error(TypeError, "Complex can't be coerced into Vector2d")
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "when a complex number is the left hand operand" do
|
|
38
|
+
it "raises a TypeError" do
|
|
39
|
+
expect { Complex(1, 2) * vector }.to(
|
|
40
|
+
raise_error(TypeError, "Complex can't be coerced into Vector2d")
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#deconstruct" do
|
|
47
|
+
it "returns an array" do
|
|
48
|
+
expect(vector.deconstruct).to eq([2, 3])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "#deconstruct_keys" do
|
|
53
|
+
it "returns a hash" do
|
|
54
|
+
expect(vector.deconstruct_keys(nil)).to eq(x: 2, y: 3)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "when only one key is requested" do
|
|
58
|
+
it "returns both components" do
|
|
59
|
+
expect(vector.deconstruct_keys([:x])).to eq(x: 2, y: 3)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "pattern matching" do
|
|
65
|
+
def classify(vector)
|
|
66
|
+
case vector
|
|
67
|
+
in [0, 0] then :origin
|
|
68
|
+
in [Integer => a, Integer => b] then a + b
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def axis_of(vector)
|
|
73
|
+
case vector
|
|
74
|
+
in { x: 0, y: 0 } then :origin
|
|
75
|
+
in { x: 0 } then :on_y_axis
|
|
76
|
+
in { y: 0 } then :on_x_axis
|
|
77
|
+
else :off_axis
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def orientation_of(vector)
|
|
82
|
+
case vector
|
|
83
|
+
in [x, y] if x < y then :portrait
|
|
84
|
+
in [x, y] if x > y then :landscape
|
|
85
|
+
else :square
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def contains?(vector, value)
|
|
90
|
+
case vector
|
|
91
|
+
in [*, ^value, *] then true
|
|
92
|
+
else false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "destructures into an array" do
|
|
97
|
+
vector => [x, y]
|
|
98
|
+
|
|
99
|
+
expect([x, y]).to eq([2, 3])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "destructures into keys" do
|
|
103
|
+
vector => { x:, y: }
|
|
104
|
+
|
|
105
|
+
expect([x, y]).to eq([2, 3])
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "binds the components of an array pattern" do
|
|
109
|
+
expect(classify(vector)).to eq(5)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "matches a hash pattern" do
|
|
113
|
+
expect(axis_of(vector)).to eq(:off_axis)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "matches a guarded pattern" do
|
|
117
|
+
expect(orientation_of(vector)).to eq(:portrait)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "matches a find pattern" do
|
|
121
|
+
expect(contains?(vector, 3)).to be(true)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "doesn't match a find pattern on a missing component" do
|
|
125
|
+
expect(contains?(vector, 4)).to be(false)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "destructures a vector nested in an array" do
|
|
129
|
+
[Vector2d.new(0, 0), vector] => [[0, 0], [x, y]]
|
|
130
|
+
|
|
131
|
+
expect([x, y]).to eq([2, 3])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "destructures a vector nested in a hash" do
|
|
135
|
+
{ size: vector } => { size: { x:, y: } }
|
|
136
|
+
|
|
137
|
+
expect([x, y]).to eq([2, 3])
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context "when the vector is the origin" do
|
|
141
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
142
|
+
|
|
143
|
+
it "matches the leading array pattern" do
|
|
144
|
+
expect(classify(vector)).to eq(:origin)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "matches the leading hash pattern" do
|
|
148
|
+
expect(axis_of(vector)).to eq(:origin)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context "when the vector is on an axis" do
|
|
153
|
+
subject(:vector) { Vector2d.new(0, 3) }
|
|
154
|
+
|
|
155
|
+
it "matches on the partial hash pattern" do
|
|
156
|
+
expect(axis_of(vector)).to eq(:on_y_axis)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
context "when the components are floats" do
|
|
161
|
+
subject(:vector) { Vector2d.new(2.0, 3.0) }
|
|
162
|
+
|
|
163
|
+
it "raises NoMatchingPatternError" do
|
|
164
|
+
expect { classify(vector) }.to raise_error(NoMatchingPatternError)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "still matches an untyped pattern" do
|
|
168
|
+
expect(orientation_of(vector)).to eq(:portrait)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe "#inspect" do
|
|
174
|
+
it "renders a string representation" do
|
|
175
|
+
expect(vector.inspect).to eq("Vector2d(2,3)")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
context "when the vector is a subclass" do
|
|
179
|
+
subject(:vector) { SubVector.new(2, 3) }
|
|
180
|
+
|
|
181
|
+
before { stub_const("SubVector", Class.new(Vector2d)) }
|
|
182
|
+
|
|
183
|
+
it "renders the name of the subclass" do
|
|
184
|
+
expect(vector.inspect).to eq("SubVector(2,3)")
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
describe "#to_a" do
|
|
190
|
+
it "returns an array" do
|
|
191
|
+
expect(vector.to_a).to eq([2, 3])
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
describe "#to_hash" do
|
|
196
|
+
it "returns a hash" do
|
|
197
|
+
expect(vector.to_hash).to eq(x: 2, y: 3)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
describe "#to_i_vector" do
|
|
202
|
+
subject { vector.to_i_vector }
|
|
203
|
+
|
|
204
|
+
let(:vector) { Vector2d.new(2.0, 3.0) }
|
|
205
|
+
|
|
206
|
+
its(:x) { is_expected.to be_a(Integer) }
|
|
207
|
+
its(:y) { is_expected.to be_a(Integer) }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
describe "#to_f_vector" do
|
|
211
|
+
subject { vector.to_f_vector }
|
|
212
|
+
|
|
213
|
+
its(:x) { is_expected.to be_a(Float) }
|
|
214
|
+
its(:y) { is_expected.to be_a(Float) }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
describe "#to_s" do
|
|
218
|
+
context "when fixnum" do
|
|
219
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
|
220
|
+
|
|
221
|
+
it "renders a string" do
|
|
222
|
+
expect(vector.to_s).to eq("2x3")
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
context "when float" do
|
|
227
|
+
subject(:vector) { Vector2d.new(2.0, 3.0) }
|
|
228
|
+
|
|
229
|
+
it "renders a string" do
|
|
230
|
+
expect(vector.to_s).to eq("2.0x3.0")
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|