vector2d 2.2.4 → 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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +39 -0
  3. data/.github/workflows/build.yml +48 -10
  4. data/.gitignore +3 -1
  5. data/.release-please-manifest.json +3 -0
  6. data/.rubocop.yml +5 -2
  7. data/.tool-versions +1 -0
  8. data/.yardopts +11 -0
  9. data/CHANGELOG.md +224 -0
  10. data/CODE_OF_CONDUCT.md +92 -0
  11. data/CONTRIBUTING.md +30 -0
  12. data/Gemfile +6 -2
  13. data/Gemfile.lock +85 -51
  14. data/README.md +330 -13
  15. data/Rakefile +21 -0
  16. data/benchmark/vector_comparison.rb +129 -0
  17. data/lib/vector2d/angles.rb +315 -0
  18. data/lib/vector2d/arithmetic.rb +97 -0
  19. data/lib/vector2d/comparison.rb +188 -0
  20. data/lib/vector2d/componentwise.rb +239 -0
  21. data/lib/vector2d/constructors.rb +137 -0
  22. data/lib/vector2d/conversions.rb +139 -0
  23. data/lib/vector2d/coordinates.rb +22 -0
  24. data/lib/vector2d/deprecation.rb +16 -0
  25. data/lib/vector2d/dimensions.rb +297 -0
  26. data/lib/vector2d/interpolation.rb +191 -0
  27. data/lib/vector2d/lengths.rb +284 -0
  28. data/lib/vector2d/matrix_interop.rb +93 -0
  29. data/lib/vector2d/parsing.rb +142 -0
  30. data/lib/vector2d/projection.rb +208 -0
  31. data/lib/vector2d/version.rb +2 -1
  32. data/lib/vector2d.rb +174 -62
  33. data/release-please-config.json +10 -0
  34. data/spec/lib/vector2d/angles_spec.rb +441 -0
  35. data/spec/lib/vector2d/{calculations_spec.rb → arithmetic_spec.rb} +48 -58
  36. data/spec/lib/vector2d/comparison_spec.rb +358 -0
  37. data/spec/lib/vector2d/componentwise_spec.rb +300 -0
  38. data/spec/lib/vector2d/constructors_spec.rb +299 -0
  39. data/spec/lib/vector2d/conversions_spec.rb +234 -0
  40. data/spec/lib/vector2d/dimensions_spec.rb +820 -0
  41. data/spec/lib/vector2d/interpolation_spec.rb +322 -0
  42. data/spec/lib/vector2d/lengths_spec.rb +457 -0
  43. data/spec/lib/vector2d/matrix_interop_spec.rb +92 -0
  44. data/spec/lib/vector2d/parsing_spec.rb +329 -0
  45. data/spec/lib/vector2d/projection_spec.rb +306 -0
  46. data/spec/lib/vector2d_documentation_spec.rb +38 -0
  47. data/spec/lib/vector2d_immutability_spec.rb +263 -0
  48. data/spec/lib/vector2d_spec.rb +90 -50
  49. data/spec/lib/vector2d_subclassing_spec.rb +210 -0
  50. data/spec/lib/vector2d_tags_spec.rb +43 -0
  51. data/spec/spec_helper.rb +2 -0
  52. data/spec/support/doc_examples/comments.rb +65 -0
  53. data/spec/support/doc_examples/markdown.rb +50 -0
  54. data/spec/support/doc_examples.rb +94 -0
  55. data/spec/support/doc_tags.rb +151 -0
  56. data/spec/support/shared_examples/class_preserving_method.rb +10 -0
  57. data/spec/support/shared_examples/deprecated_method.rb +25 -0
  58. data/spec/support/shared_examples/parsed_vector.rb +11 -0
  59. data/vector2d.gemspec +9 -3
  60. metadata +55 -19
  61. data/.travis.yml +0 -10
  62. data/lib/vector2d/calculations.rb +0 -141
  63. data/lib/vector2d/coercions.rb +0 -64
  64. data/lib/vector2d/fitting.rb +0 -60
  65. data/lib/vector2d/properties.rb +0 -46
  66. data/lib/vector2d/transformations.rb +0 -85
  67. data/spec/lib/vector2d/coercions_spec.rb +0 -59
  68. data/spec/lib/vector2d/fitting_spec.rb +0 -70
  69. data/spec/lib/vector2d/properties_spec.rb +0 -47
  70. data/spec/lib/vector2d/transformations_spec.rb +0 -122
@@ -0,0 +1,820 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Vector2d::Dimensions do
6
+ subject(:vector) { Vector2d.new(2, 3) }
7
+
8
+ let(:original) { Vector2d.new(300, 300) }
9
+
10
+ describe "#area" do
11
+ it "multiplies the coordinates" do
12
+ expect(vector.area).to eq(6)
13
+ end
14
+
15
+ it "is never negative" do
16
+ expect(Vector2d.new(-2, 3).area).to eq(6)
17
+ end
18
+
19
+ it "is zero without width or height" do
20
+ expect(Vector2d.new(2, 0).area).to eq(0)
21
+ end
22
+ end
23
+
24
+ describe "#aspect_ratio" do
25
+ it "returns the aspect_ratio" do
26
+ expect(vector.aspect_ratio).to be_within(0.0001).of(0.6667)
27
+ end
28
+
29
+ context "when y is zero" do
30
+ let(:vector) { Vector2d.new(2, 0) }
31
+
32
+ it "raises an ArgumentError" do
33
+ expect { vector.aspect_ratio }
34
+ .to raise_error(ArgumentError,
35
+ "Vector2d(2,0) has no aspect ratio, y is zero")
36
+ end
37
+ end
38
+
39
+ context "with the zero vector" do
40
+ let(:vector) { Vector2d.new(0, 0) }
41
+
42
+ it "raises an ArgumentError" do
43
+ expect { vector.aspect_ratio }
44
+ .to raise_error(ArgumentError,
45
+ "the zero vector has no aspect ratio")
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#landscape?" do
51
+ subject { vector.landscape? }
52
+
53
+ context "when the vector is wider than it is tall" do
54
+ let(:vector) { Vector2d.new(3, 2) }
55
+
56
+ it { is_expected.to be(true) }
57
+ end
58
+
59
+ context "when the vector is taller than it is wide" do
60
+ let(:vector) { Vector2d.new(2, 3) }
61
+
62
+ it { is_expected.to be(false) }
63
+ end
64
+
65
+ context "when the vector is square" do
66
+ let(:vector) { Vector2d.new(2, 2) }
67
+
68
+ it { is_expected.to be(false) }
69
+ end
70
+
71
+ context "when the coordinates are negative" do
72
+ let(:vector) { Vector2d.new(-3, -2) }
73
+
74
+ it { is_expected.to be(true) }
75
+ end
76
+ end
77
+
78
+ describe "#portrait?" do
79
+ subject { vector.portrait? }
80
+
81
+ context "when the vector is taller than it is wide" do
82
+ let(:vector) { Vector2d.new(2, 3) }
83
+
84
+ it { is_expected.to be(true) }
85
+ end
86
+
87
+ context "when the vector is wider than it is tall" do
88
+ let(:vector) { Vector2d.new(3, 2) }
89
+
90
+ it { is_expected.to be(false) }
91
+ end
92
+
93
+ context "when the vector is square" do
94
+ let(:vector) { Vector2d.new(2, 2) }
95
+
96
+ it { is_expected.to be(false) }
97
+ end
98
+
99
+ context "when the coordinates are negative" do
100
+ let(:vector) { Vector2d.new(-2, -3) }
101
+
102
+ it { is_expected.to be(true) }
103
+ end
104
+ end
105
+
106
+ describe "#square?" do
107
+ subject { vector.square? }
108
+
109
+ context "when the coordinates are equal" do
110
+ let(:vector) { Vector2d.new(2, 2) }
111
+
112
+ it { is_expected.to be(true) }
113
+ end
114
+
115
+ context "when the coordinates differ" do
116
+ let(:vector) { Vector2d.new(2, 3) }
117
+
118
+ it { is_expected.to be(false) }
119
+ end
120
+
121
+ context "when the coordinates differ only in sign" do
122
+ let(:vector) { Vector2d.new(-2, 2) }
123
+
124
+ it { is_expected.to be(true) }
125
+ end
126
+
127
+ context "with the zero vector" do
128
+ let(:vector) { Vector2d.new(0, 0) }
129
+
130
+ it { is_expected.to be(true) }
131
+ end
132
+ end
133
+
134
+ describe "the shape predicates" do
135
+ it "are exclusive" do
136
+ [Vector2d.new(3, 2), Vector2d.new(2, 3), Vector2d.new(2, 2),
137
+ Vector2d.new(0, 0)].each do |v|
138
+ expect([v.landscape?, v.portrait?, v.square?].count(true)).to eq(1)
139
+ end
140
+ end
141
+ end
142
+
143
+ describe "#fit" do
144
+ subject(:vector) { original.fit(comp) }
145
+
146
+ context "when scaling by height" do
147
+ let(:comp) { Vector2d.new(200, 150) }
148
+
149
+ its(:x) { is_expected.to eq(150) }
150
+ its(:y) { is_expected.to eq(150) }
151
+ end
152
+
153
+ context "when scaling by width" do
154
+ let(:comp) { Vector2d.new(150, 200) }
155
+
156
+ its(:x) { is_expected.to eq(150) }
157
+ its(:y) { is_expected.to eq(150) }
158
+ end
159
+
160
+ context "when the x coordinate is negative" do
161
+ let(:original) { Vector2d.new(-20, 10) }
162
+ let(:comp) { Vector2d.new(5, 5) }
163
+
164
+ its(:x) { is_expected.to eq(-5) }
165
+ its(:y) { is_expected.to eq(2.5) }
166
+ end
167
+
168
+ context "when the y coordinate is negative" do
169
+ let(:original) { Vector2d.new(20, -10) }
170
+ let(:comp) { Vector2d.new(5, 5) }
171
+
172
+ its(:x) { is_expected.to eq(5) }
173
+ its(:y) { is_expected.to eq(-2.5) }
174
+ end
175
+
176
+ context "when both coordinates are negative" do
177
+ let(:original) { Vector2d.new(-20, -10) }
178
+ let(:comp) { Vector2d.new(5, 5) }
179
+
180
+ its(:x) { is_expected.to eq(-5) }
181
+ its(:y) { is_expected.to eq(-2.5) }
182
+ end
183
+
184
+ context "when both coordinates are negative and the y axis binds" do
185
+ let(:original) { Vector2d.new(-10, -20) }
186
+ let(:comp) { Vector2d.new(5, 5) }
187
+
188
+ its(:x) { is_expected.to eq(-2.5) }
189
+ its(:y) { is_expected.to eq(-5) }
190
+ end
191
+
192
+ context "when the constraint is negative" do
193
+ let(:original) { Vector2d.new(20, 10) }
194
+ let(:comp) { Vector2d.new(-5, -5) }
195
+
196
+ its(:x) { is_expected.to eq(5) }
197
+ its(:y) { is_expected.to eq(2.5) }
198
+ end
199
+
200
+ context "with the zero vector" do
201
+ let(:original) { Vector2d.new(0, 0) }
202
+ let(:comp) { Vector2d.new(5, 5) }
203
+
204
+ it "returns the zero vector" do
205
+ expect(vector).to eq(Vector2d.new(0, 0))
206
+ end
207
+ end
208
+
209
+ context "when the argument is invalid" do
210
+ let(:comp) { "garbage" }
211
+
212
+ it "raises an error" do
213
+ expect { vector }.to raise_error(ArgumentError)
214
+ end
215
+ end
216
+
217
+ context "when the vector is zero and the argument is invalid" do
218
+ let(:original) { Vector2d.new(0, 0) }
219
+ let(:comp) { "garbage" }
220
+
221
+ it "raises an error" do
222
+ expect { vector }.to raise_error(ArgumentError)
223
+ end
224
+ end
225
+
226
+ context "when the x axis is zero" do
227
+ let(:original) { Vector2d.new(0, 5) }
228
+ let(:comp) { Vector2d.new(10, 2) }
229
+
230
+ it "disregards the zero axis" do
231
+ expect(vector).to eq(Vector2d.new(0.0, 2.0))
232
+ end
233
+ end
234
+
235
+ context "when the y axis is zero" do
236
+ let(:original) { Vector2d.new(5, 0) }
237
+ let(:comp) { Vector2d.new(2, 10) }
238
+
239
+ it "disregards the zero axis" do
240
+ expect(vector).to eq(Vector2d.new(2.0, 0.0))
241
+ end
242
+ end
243
+
244
+ context "when both constraint axes are zero" do
245
+ let(:original) { Vector2d.new(20, 10) }
246
+ let(:comp) { Vector2d.new(0, 0) }
247
+
248
+ it "returns the vector unchanged" do
249
+ expect(vector).to eq(Vector2d.new(20, 10))
250
+ end
251
+ end
252
+
253
+ context "when upscaling is enabled" do
254
+ subject(:vector) { original.fit(comp, upscale: true) }
255
+
256
+ let(:comp) { Vector2d.new(600, 600) }
257
+
258
+ its(:x) { is_expected.to eq(600) }
259
+ its(:y) { is_expected.to eq(600) }
260
+ end
261
+
262
+ context "when upscaling is disabled and the vector is larger" do
263
+ subject(:vector) { original.fit(comp, upscale: false) }
264
+
265
+ let(:comp) { Vector2d.new(200, 150) }
266
+
267
+ its(:x) { is_expected.to eq(150) }
268
+ its(:y) { is_expected.to eq(150) }
269
+ end
270
+
271
+ context "when upscaling is disabled and the vector already fits" do
272
+ subject(:vector) { original.fit(comp, upscale: false) }
273
+
274
+ let(:comp) { Vector2d.new(600, 600) }
275
+
276
+ it { is_expected.to equal(original) }
277
+ end
278
+
279
+ context "when upscaling is disabled and the vector fits exactly" do
280
+ subject(:vector) { original.fit(comp, upscale: false) }
281
+
282
+ let(:comp) { Vector2d.new(300, 300) }
283
+
284
+ it { is_expected.to equal(original) }
285
+ end
286
+
287
+ context "when upscaling is disabled and only one axis fits" do
288
+ subject(:vector) { original.fit(comp, upscale: false) }
289
+
290
+ let(:comp) { Vector2d.new(600, 150) }
291
+
292
+ its(:x) { is_expected.to eq(150) }
293
+ its(:y) { is_expected.to eq(150) }
294
+ end
295
+
296
+ context "when upscaling is disabled and the vector is negative" do
297
+ subject(:vector) { original.fit(comp, upscale: false) }
298
+
299
+ let(:original) { Vector2d.new(-20, -10) }
300
+ let(:comp) { Vector2d.new(5, 5) }
301
+
302
+ its(:x) { is_expected.to eq(-5) }
303
+ its(:y) { is_expected.to eq(-2.5) }
304
+ end
305
+
306
+ context "when upscaling is disabled and the vector is unconstrained" do
307
+ subject(:vector) { original.fit(comp, upscale: false) }
308
+
309
+ let(:original) { Vector2d.new(20, 10) }
310
+ let(:comp) { Vector2d.new(0, 0) }
311
+
312
+ it { is_expected.to equal(original) }
313
+ end
314
+ end
315
+
316
+ describe "#fits?" do
317
+ subject { original.fits?(comp) }
318
+
319
+ let(:original) { Vector2d.new(20, 10) }
320
+
321
+ context "when the vector is smaller than the constraint" do
322
+ let(:comp) { Vector2d.new(40, 40) }
323
+
324
+ it { is_expected.to be(true) }
325
+ end
326
+
327
+ context "when the vector overflows the x axis" do
328
+ let(:comp) { Vector2d.new(10, 40) }
329
+
330
+ it { is_expected.to be(false) }
331
+ end
332
+
333
+ context "when the vector overflows the y axis" do
334
+ let(:comp) { Vector2d.new(40, 5) }
335
+
336
+ it { is_expected.to be(false) }
337
+ end
338
+
339
+ context "when the vector matches the constraint" do
340
+ let(:comp) { Vector2d.new(20, 10) }
341
+
342
+ it { is_expected.to be(true) }
343
+ end
344
+
345
+ context "when the coordinates are negative" do
346
+ let(:original) { Vector2d.new(-20, -10) }
347
+ let(:comp) { Vector2d.new(40, 40) }
348
+
349
+ it { is_expected.to be(true) }
350
+ end
351
+
352
+ context "when an axis of the constraint is zero" do
353
+ let(:comp) { Vector2d.new(0, 40) }
354
+
355
+ it { is_expected.to be(true) }
356
+ end
357
+
358
+ context "when the unconstrained axis is the overflowing one" do
359
+ let(:comp) { Vector2d.new(40, 0) }
360
+ let(:original) { Vector2d.new(20, 80) }
361
+
362
+ it { is_expected.to be(true) }
363
+ end
364
+
365
+ context "with the zero vector" do
366
+ let(:original) { Vector2d.new(0, 0) }
367
+ let(:comp) { Vector2d.new(40, 40) }
368
+
369
+ it { is_expected.to be(true) }
370
+ end
371
+
372
+ it "coerces the constraint" do
373
+ expect(original.fits?("40x40")).to be(true)
374
+ end
375
+
376
+ it "agrees with #fit" do
377
+ [Vector2d.new(40, 40), Vector2d.new(10, 40),
378
+ Vector2d.new(20, 10)].each do |c|
379
+ expect(original.fits?(c))
380
+ .to be(original.fit(c, upscale: false) == original)
381
+ end
382
+ end
383
+ end
384
+
385
+ describe "#cover" do
386
+ subject(:vector) { original.cover(comp) }
387
+
388
+ context "when width is largest" do
389
+ let(:comp) { Vector2d.new(200, 150) }
390
+
391
+ its(:x) { is_expected.to eq(200) }
392
+ its(:y) { is_expected.to eq(200) }
393
+ end
394
+
395
+ context "when height is largest" do
396
+ let(:comp) { Vector2d.new(150, 200) }
397
+
398
+ its(:x) { is_expected.to eq(200) }
399
+ its(:y) { is_expected.to eq(200) }
400
+ end
401
+
402
+ context "when a coordinate is negative" do
403
+ let(:original) { Vector2d.new(-300, 300) }
404
+ let(:comp) { Vector2d.new(150, 150) }
405
+
406
+ its(:x) { is_expected.to eq(-150) }
407
+ its(:y) { is_expected.to eq(150) }
408
+ end
409
+
410
+ context "when a coordinate is negative and width is largest" do
411
+ let(:original) { Vector2d.new(-300, 300) }
412
+ let(:comp) { Vector2d.new(200, 150) }
413
+
414
+ its(:x) { is_expected.to eq(-200) }
415
+ its(:y) { is_expected.to eq(200) }
416
+ end
417
+
418
+ context "when a coordinate is negative and height is largest" do
419
+ let(:original) { Vector2d.new(300, -300) }
420
+ let(:comp) { Vector2d.new(150, 200) }
421
+
422
+ its(:x) { is_expected.to eq(200) }
423
+ its(:y) { is_expected.to eq(-200) }
424
+ end
425
+
426
+ context "when both coordinates are negative" do
427
+ let(:original) { Vector2d.new(-20, -10) }
428
+ let(:comp) { Vector2d.new(5, 5) }
429
+
430
+ its(:x) { is_expected.to eq(-10) }
431
+ its(:y) { is_expected.to eq(-5) }
432
+ end
433
+
434
+ context "when the constraint is negative" do
435
+ let(:original) { Vector2d.new(20, 10) }
436
+ let(:comp) { Vector2d.new(-5, -5) }
437
+
438
+ its(:x) { is_expected.to eq(10) }
439
+ its(:y) { is_expected.to eq(5) }
440
+ end
441
+
442
+ context "with the zero vector" do
443
+ let(:original) { Vector2d.new(0, 0) }
444
+ let(:comp) { Vector2d.new(5, 5) }
445
+
446
+ it "returns the zero vector" do
447
+ expect(vector).to eq(Vector2d.new(0, 0))
448
+ end
449
+ end
450
+
451
+ context "when the argument is invalid" do
452
+ let(:comp) { "garbage" }
453
+
454
+ it "raises an error" do
455
+ expect { vector }.to raise_error(ArgumentError)
456
+ end
457
+ end
458
+
459
+ context "when the vector is zero and the argument is invalid" do
460
+ let(:original) { Vector2d.new(0, 0) }
461
+ let(:comp) { "garbage" }
462
+
463
+ it "raises an error" do
464
+ expect { vector }.to raise_error(ArgumentError)
465
+ end
466
+ end
467
+
468
+ context "when the x axis is zero" do
469
+ let(:original) { Vector2d.new(0, 5) }
470
+ let(:comp) { Vector2d.new(10, 2) }
471
+
472
+ it "disregards the zero axis" do
473
+ expect(vector).to eq(Vector2d.new(0.0, 2.0))
474
+ end
475
+
476
+ it "matches #fit" do
477
+ expect(vector).to eq(original.fit(comp))
478
+ end
479
+ end
480
+
481
+ context "when the y axis is zero" do
482
+ let(:original) { Vector2d.new(5, 0) }
483
+ let(:comp) { Vector2d.new(2, 10) }
484
+
485
+ it "disregards the zero axis" do
486
+ expect(vector).to eq(Vector2d.new(2.0, 0.0))
487
+ end
488
+
489
+ it "matches #fit" do
490
+ expect(vector).to eq(original.fit(comp))
491
+ end
492
+ end
493
+
494
+ context "when both constraint axes are zero" do
495
+ let(:original) { Vector2d.new(20, 10) }
496
+ let(:comp) { Vector2d.new(0, 0) }
497
+
498
+ it "returns the vector unchanged" do
499
+ expect(vector).to eq(Vector2d.new(20, 10))
500
+ end
501
+
502
+ it "matches #fit" do
503
+ expect(vector).to eq(original.fit(comp))
504
+ end
505
+ end
506
+
507
+ context "when upscaling is enabled" do
508
+ subject(:vector) { original.cover(comp, upscale: true) }
509
+
510
+ let(:comp) { Vector2d.new(600, 450) }
511
+
512
+ its(:x) { is_expected.to eq(600) }
513
+ its(:y) { is_expected.to eq(600) }
514
+ end
515
+
516
+ context "when upscaling is disabled and the vector is larger" do
517
+ subject(:vector) { original.cover(comp, upscale: false) }
518
+
519
+ let(:comp) { Vector2d.new(200, 150) }
520
+
521
+ its(:x) { is_expected.to eq(200) }
522
+ its(:y) { is_expected.to eq(200) }
523
+ end
524
+
525
+ context "when upscaling is disabled and the vector already covers" do
526
+ subject(:vector) { original.cover(comp, upscale: false) }
527
+
528
+ let(:comp) { Vector2d.new(600, 450) }
529
+
530
+ it { is_expected.to equal(original) }
531
+ end
532
+
533
+ context "when upscaling is disabled and the vector covers exactly" do
534
+ subject(:vector) { original.cover(comp, upscale: false) }
535
+
536
+ let(:comp) { Vector2d.new(300, 200) }
537
+
538
+ it { is_expected.to equal(original) }
539
+ end
540
+
541
+ context "when upscaling is disabled and a single axis constrains" do
542
+ subject(:vector) { original.cover(comp, upscale: false) }
543
+
544
+ let(:original) { Vector2d.new(0, 300) }
545
+ let(:comp) { Vector2d.new(10, 600) }
546
+
547
+ it { is_expected.to equal(original) }
548
+ end
549
+
550
+ context "when upscaling is disabled and a single axis scales it down" do
551
+ subject(:vector) { original.cover(comp, upscale: false) }
552
+
553
+ let(:original) { Vector2d.new(0, 300) }
554
+ let(:comp) { Vector2d.new(10, 150) }
555
+
556
+ its(:x) { is_expected.to eq(0) }
557
+ its(:y) { is_expected.to eq(150) }
558
+ end
559
+ end
560
+
561
+ describe "#covers?" do
562
+ subject { original.covers?(comp) }
563
+
564
+ let(:original) { Vector2d.new(20, 10) }
565
+
566
+ context "when the vector is larger than the constraint" do
567
+ let(:comp) { Vector2d.new(5, 5) }
568
+
569
+ it { is_expected.to be(true) }
570
+ end
571
+
572
+ context "when the vector falls short on the y axis" do
573
+ let(:original) { Vector2d.new(20, 1) }
574
+ let(:comp) { Vector2d.new(5, 5) }
575
+
576
+ it { is_expected.to be(false) }
577
+ end
578
+
579
+ context "when the vector falls short on the x axis" do
580
+ let(:original) { Vector2d.new(1, 20) }
581
+ let(:comp) { Vector2d.new(5, 5) }
582
+
583
+ it { is_expected.to be(false) }
584
+ end
585
+
586
+ context "when the vector matches the constraint" do
587
+ let(:comp) { Vector2d.new(20, 10) }
588
+
589
+ it { is_expected.to be(true) }
590
+ end
591
+
592
+ context "when the coordinates are negative" do
593
+ let(:original) { Vector2d.new(-20, -10) }
594
+ let(:comp) { Vector2d.new(5, 5) }
595
+
596
+ it { is_expected.to be(true) }
597
+ end
598
+
599
+ context "when an axis of the vector is zero" do
600
+ let(:original) { Vector2d.new(0, 10) }
601
+ let(:comp) { Vector2d.new(5, 5) }
602
+
603
+ it { is_expected.to be(true) }
604
+ end
605
+
606
+ context "with the zero vector" do
607
+ let(:original) { Vector2d.new(0, 0) }
608
+ let(:comp) { Vector2d.new(5, 5) }
609
+
610
+ it { is_expected.to be(true) }
611
+ end
612
+
613
+ it "coerces the constraint" do
614
+ expect(original.covers?("5x5")).to be(true)
615
+ end
616
+
617
+ it "is true exactly when #cover doesn't scale the vector up" do
618
+ [Vector2d.new(5, 5), Vector2d.new(40, 40),
619
+ Vector2d.new(20, 10)].each do |c|
620
+ expect(original.covers?(c))
621
+ .to be(original.cover(c, upscale: false) == original.cover(c))
622
+ end
623
+ end
624
+ end
625
+
626
+ describe "#fit_either" do
627
+ subject(:vector) { original.fit_either(comp) }
628
+
629
+ it_behaves_like "a deprecated method", "fit_either", "#cover" do
630
+ let(:comp) { Vector2d.new(200, 150) }
631
+ end
632
+
633
+ context "when width is largest" do
634
+ let(:comp) { Vector2d.new(200, 150) }
635
+
636
+ it "matches #cover" do
637
+ expect(vector).to eq(original.cover(comp))
638
+ end
639
+ end
640
+ end
641
+
642
+ describe "#contain" do
643
+ subject(:vector) { original.contain(comp) }
644
+
645
+ it_behaves_like "a deprecated method", "contain",
646
+ "other.fit(self, upscale: false)" do
647
+ let(:comp) { Vector2d.new(400, 300) }
648
+ end
649
+
650
+ context "when vector is smaller" do
651
+ let(:comp) { Vector2d.new(150, 100) }
652
+
653
+ its(:x) { is_expected.to eq(150) }
654
+ its(:y) { is_expected.to eq(100) }
655
+ end
656
+
657
+ context "when vector is wider" do
658
+ let(:comp) { Vector2d.new(400, 300) }
659
+
660
+ its(:x) { is_expected.to eq(300) }
661
+ its(:y) { is_expected.to eq(225) }
662
+ end
663
+
664
+ context "when vector is higher" do
665
+ let(:comp) { Vector2d.new(300, 400) }
666
+
667
+ its(:x) { is_expected.to eq(225) }
668
+ its(:y) { is_expected.to eq(300) }
669
+ end
670
+
671
+ context "when the argument is an array" do
672
+ let(:comp) { [400, 300] }
673
+
674
+ its(:x) { is_expected.to eq(300) }
675
+ its(:y) { is_expected.to eq(225) }
676
+ end
677
+
678
+ context "when the argument is a string" do
679
+ let(:comp) { "400x300" }
680
+
681
+ its(:x) { is_expected.to eq(300) }
682
+ its(:y) { is_expected.to eq(225) }
683
+ end
684
+
685
+ context "when the argument is a hash" do
686
+ let(:comp) { { x: 400, y: 300 } }
687
+
688
+ its(:x) { is_expected.to eq(300) }
689
+ its(:y) { is_expected.to eq(225) }
690
+ end
691
+
692
+ context "when the coerced argument already fits" do
693
+ let(:comp) { [150, 100] }
694
+
695
+ it { is_expected.to be_a(Vector2d) }
696
+ its(:x) { is_expected.to eq(150) }
697
+ its(:y) { is_expected.to eq(100) }
698
+ end
699
+
700
+ context "when the vector is negative and wider" do
701
+ let(:comp) { Vector2d.new(-400, 300) }
702
+
703
+ its(:x) { is_expected.to eq(-300) }
704
+ its(:y) { is_expected.to eq(225) }
705
+ end
706
+
707
+ context "when the vector is negative and higher" do
708
+ let(:comp) { Vector2d.new(300, -400) }
709
+
710
+ its(:x) { is_expected.to eq(225) }
711
+ its(:y) { is_expected.to eq(-300) }
712
+ end
713
+
714
+ context "when both coordinates are negative" do
715
+ let(:comp) { Vector2d.new(-400, -300) }
716
+
717
+ its(:x) { is_expected.to eq(-300) }
718
+ its(:y) { is_expected.to eq(-225) }
719
+ end
720
+
721
+ context "when the negative vector already fits" do
722
+ let(:comp) { Vector2d.new(-150, -100) }
723
+
724
+ its(:x) { is_expected.to eq(-150) }
725
+ its(:y) { is_expected.to eq(-100) }
726
+ end
727
+
728
+ context "when the vector is unconstrained" do
729
+ let(:original) { Vector2d.new(0, 0) }
730
+ let(:comp) { Vector2d.new(40, 20) }
731
+
732
+ it "returns the argument unchanged" do
733
+ expect(vector).to eq(Vector2d.new(40, 20))
734
+ end
735
+ end
736
+
737
+ context "when one axis is unconstrained and the other fits" do
738
+ let(:original) { Vector2d.new(0, 10) }
739
+ let(:comp) { Vector2d.new(5, 5) }
740
+
741
+ it "does not scale the argument up" do
742
+ expect(vector).to eq(Vector2d.new(5, 5))
743
+ end
744
+ end
745
+
746
+ context "when one axis is unconstrained and the other does not fit" do
747
+ let(:original) { Vector2d.new(0, 10) }
748
+ let(:comp) { Vector2d.new(5, 20) }
749
+
750
+ it "scales the argument down" do
751
+ expect(vector).to eq(Vector2d.new(2.5, 10))
752
+ end
753
+ end
754
+
755
+ context "when both vectors are zero" do
756
+ let(:original) { Vector2d.new(0, 0) }
757
+ let(:comp) { Vector2d.new(0, 0) }
758
+
759
+ it "returns the argument unchanged" do
760
+ expect(vector).to eq(Vector2d.new(0, 0))
761
+ end
762
+ end
763
+
764
+ context "when the argument is invalid" do
765
+ let(:comp) { "garbage" }
766
+
767
+ it "raises an error" do
768
+ expect { vector }.to raise_error(ArgumentError)
769
+ end
770
+ end
771
+ end
772
+
773
+ describe "#constrain_both" do
774
+ subject(:vector) { original.constrain_both(comp) }
775
+
776
+ it_behaves_like "a deprecated method", "constrain_both", "#fit" do
777
+ let(:comp) { Vector2d.new(200, 150) }
778
+ end
779
+
780
+ context "when the vector is larger than the constraint" do
781
+ let(:comp) { Vector2d.new(200, 150) }
782
+
783
+ it "matches #fit" do
784
+ expect(vector).to eq(original.fit(comp))
785
+ end
786
+ end
787
+
788
+ context "when the vector is smaller than the constraint" do
789
+ let(:comp) { Vector2d.new(600, 600) }
790
+
791
+ it "scales up, as #fit does" do
792
+ expect(vector).to eq(Vector2d.new(600, 600))
793
+ end
794
+ end
795
+ end
796
+
797
+ describe "#constrain_one" do
798
+ subject(:vector) { original.constrain_one(comp) }
799
+
800
+ it_behaves_like "a deprecated method", "constrain_one", "#cover" do
801
+ let(:comp) { Vector2d.new(200, 150) }
802
+ end
803
+
804
+ context "when width is largest" do
805
+ let(:comp) { Vector2d.new(200, 150) }
806
+
807
+ it "matches #cover" do
808
+ expect(vector).to eq(original.cover(comp))
809
+ end
810
+ end
811
+
812
+ context "when height is largest" do
813
+ let(:comp) { Vector2d.new(150, 200) }
814
+
815
+ it "matches #cover" do
816
+ expect(vector).to eq(original.cover(comp))
817
+ end
818
+ end
819
+ end
820
+ end