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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +1 -1
  3. data/.gitignore +3 -1
  4. data/.release-please-manifest.json +1 -1
  5. data/.rubocop.yml +1 -1
  6. data/.yardopts +11 -0
  7. data/CHANGELOG.md +216 -0
  8. data/Gemfile +5 -1
  9. data/Gemfile.lock +35 -5
  10. data/README.md +325 -12
  11. data/Rakefile +18 -0
  12. data/benchmark/vector_comparison.rb +129 -0
  13. data/lib/vector2d/angles.rb +315 -0
  14. data/lib/vector2d/arithmetic.rb +97 -0
  15. data/lib/vector2d/comparison.rb +188 -0
  16. data/lib/vector2d/componentwise.rb +239 -0
  17. data/lib/vector2d/constructors.rb +137 -0
  18. data/lib/vector2d/conversions.rb +139 -0
  19. data/lib/vector2d/coordinates.rb +22 -0
  20. data/lib/vector2d/deprecation.rb +16 -0
  21. data/lib/vector2d/dimensions.rb +297 -0
  22. data/lib/vector2d/interpolation.rb +191 -0
  23. data/lib/vector2d/lengths.rb +284 -0
  24. data/lib/vector2d/matrix_interop.rb +93 -0
  25. data/lib/vector2d/parsing.rb +142 -0
  26. data/lib/vector2d/projection.rb +208 -0
  27. data/lib/vector2d/version.rb +2 -1
  28. data/lib/vector2d.rb +172 -60
  29. data/spec/lib/vector2d/angles_spec.rb +441 -0
  30. data/spec/lib/vector2d/{calculations_spec.rb → arithmetic_spec.rb} +48 -58
  31. data/spec/lib/vector2d/comparison_spec.rb +358 -0
  32. data/spec/lib/vector2d/componentwise_spec.rb +300 -0
  33. data/spec/lib/vector2d/constructors_spec.rb +299 -0
  34. data/spec/lib/vector2d/conversions_spec.rb +234 -0
  35. data/spec/lib/vector2d/dimensions_spec.rb +820 -0
  36. data/spec/lib/vector2d/interpolation_spec.rb +322 -0
  37. data/spec/lib/vector2d/lengths_spec.rb +457 -0
  38. data/spec/lib/vector2d/matrix_interop_spec.rb +92 -0
  39. data/spec/lib/vector2d/parsing_spec.rb +329 -0
  40. data/spec/lib/vector2d/projection_spec.rb +306 -0
  41. data/spec/lib/vector2d_documentation_spec.rb +38 -0
  42. data/spec/lib/vector2d_immutability_spec.rb +263 -0
  43. data/spec/lib/vector2d_spec.rb +90 -50
  44. data/spec/lib/vector2d_subclassing_spec.rb +210 -0
  45. data/spec/lib/vector2d_tags_spec.rb +43 -0
  46. data/spec/spec_helper.rb +2 -0
  47. data/spec/support/doc_examples/comments.rb +65 -0
  48. data/spec/support/doc_examples/markdown.rb +50 -0
  49. data/spec/support/doc_examples.rb +94 -0
  50. data/spec/support/doc_tags.rb +151 -0
  51. data/spec/support/shared_examples/class_preserving_method.rb +10 -0
  52. data/spec/support/shared_examples/deprecated_method.rb +25 -0
  53. data/spec/support/shared_examples/parsed_vector.rb +11 -0
  54. data/vector2d.gemspec +2 -1
  55. metadata +42 -13
  56. data/.travis.yml +0 -10
  57. data/lib/vector2d/calculations.rb +0 -141
  58. data/lib/vector2d/coercions.rb +0 -64
  59. data/lib/vector2d/fitting.rb +0 -60
  60. data/lib/vector2d/properties.rb +0 -46
  61. data/lib/vector2d/transformations.rb +0 -98
  62. data/spec/lib/vector2d/coercions_spec.rb +0 -59
  63. data/spec/lib/vector2d/fitting_spec.rb +0 -70
  64. data/spec/lib/vector2d/properties_spec.rb +0 -47
  65. data/spec/lib/vector2d/transformations_spec.rb +0 -139
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "bigdecimal"
5
+ require "matrix"
6
+
7
+ describe Vector2d do
8
+ subject(:vector) { described_class.new(2, 3) }
9
+
10
+ coordinate_types = {
11
+ "integer coordinates" => [2, 3],
12
+ "float coordinates" => [2.0, 3.0],
13
+ "rational coordinates" => [Rational(1, 2), Rational(3, 4)],
14
+ "big decimal coordinates" => [BigDecimal("2.5"), BigDecimal("3.5")]
15
+ }
16
+
17
+ # Runs the block in a ractor with the argument as its only parameter,
18
+ # and returns the value it produces.
19
+ def in_ractor(argument, &)
20
+ experimental = Warning[:experimental]
21
+ Warning[:experimental] = false
22
+ ractor = Ractor.new(argument, &)
23
+ ractor.respond_to?(:value) ? ractor.value : ractor.take
24
+ ensure
25
+ Warning[:experimental] = experimental
26
+ end
27
+
28
+ describe "#frozen?" do
29
+ subject { vector.frozen? }
30
+
31
+ it { is_expected.to be(true) }
32
+
33
+ coordinate_types.each do |description, (x, y)|
34
+ context "with #{description}" do
35
+ let(:vector) { described_class.new(x, y) }
36
+
37
+ it { is_expected.to be(true) }
38
+ end
39
+ end
40
+
41
+ context "with a vector built by an operation" do
42
+ let(:vector) { described_class.new(2, 3) * 2 }
43
+
44
+ it { is_expected.to be(true) }
45
+ end
46
+
47
+ context "with a vector built by .parse" do
48
+ let(:vector) { described_class.parse("2x3") }
49
+
50
+ it { is_expected.to be(true) }
51
+ end
52
+
53
+ context "with a vector built by .from_angle" do
54
+ let(:vector) { described_class.from_angle(Math::PI / 4) }
55
+
56
+ it { is_expected.to be(true) }
57
+ end
58
+
59
+ context "with a vector built by .from_degrees" do
60
+ let(:vector) { described_class.from_degrees(45) }
61
+
62
+ it { is_expected.to be(true) }
63
+ end
64
+
65
+ context "with a vector built by the helper method" do
66
+ let(:vector) { Vector2d(2, 3) }
67
+
68
+ it { is_expected.to be(true) }
69
+ end
70
+
71
+ context "with a clone" do
72
+ let(:vector) { described_class.new(2, 3).clone }
73
+
74
+ it { is_expected.to be(true) }
75
+ end
76
+
77
+ context "with a clone that asks not to be frozen" do
78
+ let(:vector) { described_class.new(2, 3).clone(freeze: false) }
79
+
80
+ it { is_expected.to be(true) }
81
+ end
82
+
83
+ context "with a dup" do
84
+ let(:vector) { described_class.new(2, 3).dup }
85
+
86
+ it { is_expected.to be(true) }
87
+ end
88
+
89
+ context "with a subclass" do
90
+ let(:vector) { Class.new(described_class).new(2, 3) }
91
+
92
+ it { is_expected.to be(true) }
93
+ end
94
+ end
95
+
96
+ describe "assigning a coordinate" do
97
+ it "raises FrozenError" do
98
+ expect { vector.instance_variable_set(:@x, 5) }
99
+ .to raise_error(FrozenError)
100
+ end
101
+
102
+ it "raises FrozenError on a dup" do
103
+ expect { vector.dup.instance_variable_set(:@x, 5) }
104
+ .to raise_error(FrozenError)
105
+ end
106
+ end
107
+
108
+ describe "#dup" do
109
+ subject(:duped) { vector.dup }
110
+
111
+ it { is_expected.to eq(vector) }
112
+ it { is_expected.to eql(vector) }
113
+
114
+ it "is shareable" do
115
+ expect(Ractor.shareable?(duped)).to be(true)
116
+ end
117
+
118
+ context "with a subclass carrying extra state" do
119
+ subject(:duped) { labeled.new(2, 3, "point").dup }
120
+
121
+ let(:labeled) do
122
+ stub_const("DupLabeled", Class.new(described_class) do
123
+ attr_reader :label
124
+
125
+ def initialize(x, y, label = "unlabeled")
126
+ @label = label
127
+ super(x, y)
128
+ end
129
+ end)
130
+ end
131
+
132
+ it { is_expected.to be_frozen }
133
+ its(:label) { is_expected.to eq("point") }
134
+ end
135
+ end
136
+
137
+ describe "Ractor.shareable?" do
138
+ subject { Ractor.shareable?(vector) }
139
+
140
+ it { is_expected.to be(true) }
141
+
142
+ coordinate_types.each do |description, (x, y)|
143
+ context "with #{description}" do
144
+ let(:vector) { described_class.new(x, y) }
145
+
146
+ it { is_expected.to be(true) }
147
+ end
148
+ end
149
+
150
+ context "with a vector built by an operation" do
151
+ let(:vector) { described_class.new(2, 3) * 2 }
152
+
153
+ it { is_expected.to be(true) }
154
+ end
155
+
156
+ context "with a coordinate that is not shareable" do
157
+ let(:coordinate) { Class.new(Numeric).new }
158
+ let(:vector) { described_class.new(2, coordinate) }
159
+
160
+ it { is_expected.to be(false) }
161
+
162
+ it "is still frozen" do
163
+ expect(vector).to be_frozen
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "sharing a vector with a ractor" do
169
+ it "arrives frozen and unchanged" do
170
+ result = in_ractor(vector) { |v| [v.frozen?, v.to_a] }
171
+
172
+ expect(result).to eq([true, [2, 3]])
173
+ end
174
+
175
+ it "calculates with a vector argument" do
176
+ result = in_ractor(vector) { |v| (v * Vector2d(2, 3)).to_a }
177
+
178
+ expect(result).to eq([4, 9])
179
+ end
180
+
181
+ it "calculates with a coerced argument" do
182
+ result = in_ractor(vector) { |v| ((v + 1) / "2x2").to_a }
183
+
184
+ expect(result).to eq([1, 2])
185
+ end
186
+
187
+ it "transforms into a new vector" do
188
+ result = in_ractor(vector) { |v| v.normalize.length }
189
+
190
+ expect(result).to be_within(0.0001).of(1.0)
191
+ end
192
+
193
+ it "returns a vector that is still frozen" do
194
+ result = in_ractor(vector, &:reverse)
195
+
196
+ expect(result).to be_frozen
197
+ end
198
+
199
+ it "returns a vector equal to the expected one" do
200
+ result = in_ractor(vector, &:reverse)
201
+
202
+ expect(result).to eq(described_class.new(-2, -3))
203
+ end
204
+
205
+ it "transforms through a matrix" do
206
+ result = in_ractor(vector) { |v| v.transform(Matrix[[0, -1], [1, 0]]).to_a }
207
+
208
+ expect(result).to eq([-3, 2])
209
+ end
210
+
211
+ it "converts to a stdlib vector" do
212
+ result = in_ractor(vector, &:to_vector)
213
+
214
+ expect(result).to eq(Vector[2, 3])
215
+ end
216
+ end
217
+
218
+ describe "a subclass adding instance variables" do
219
+ let(:before_super) do
220
+ stub_const("BeforeSuper", Class.new(described_class) do
221
+ attr_reader :label
222
+
223
+ def initialize(x, y, label = "unlabeled")
224
+ @label = label
225
+ super(x, y)
226
+ end
227
+ end)
228
+ end
229
+
230
+ let(:after_super) do
231
+ stub_const("AfterSuper", Class.new(described_class) do
232
+ def initialize(x, y, label = "unlabeled")
233
+ super(x, y)
234
+ @label = label
235
+ end
236
+ end)
237
+ end
238
+
239
+ context "when assigning before super" do
240
+ subject(:vector) { before_super.new(2, 3, "point") }
241
+
242
+ it { is_expected.to be_frozen }
243
+ its(:label) { is_expected.to eq("point") }
244
+
245
+ it "is shareable when the extra state is shareable" do
246
+ expect(Ractor.shareable?(vector)).to be(true)
247
+ end
248
+ end
249
+
250
+ context "when assigning after super" do
251
+ it "raises FrozenError" do
252
+ expect { after_super.new(2, 3) }.to raise_error(FrozenError)
253
+ end
254
+ end
255
+ end
256
+
257
+ describe ".parse with a vector argument" do
258
+ subject(:parsed) { described_class.parse(vector) }
259
+
260
+ it { is_expected.to be(vector) }
261
+ it { is_expected.to be_frozen }
262
+ end
263
+ end
@@ -5,92 +5,90 @@ require "spec_helper"
5
5
  describe Vector2d do
6
6
  subject(:vector) { described_class.new(2, 3) }
7
7
 
8
- shared_examples "a parsed vector" do |x, y|
9
- it "receives the x property" do
10
- expect(subject.x).to eq(x)
11
- end
12
-
13
- it "receives the y property" do
14
- expect(subject.y).to eq(y)
15
- end
16
- end
17
-
18
8
  describe "helper method" do
19
9
  it "creates a vector" do
20
10
  expect(Vector2d(2, 3)).to eq(described_class.new(2, 3))
21
11
  end
22
12
  end
23
13
 
24
- describe ".parse" do
25
- context "with fixnum argument" do
26
- subject(:vector) { described_class.parse(2) }
27
-
28
- it_behaves_like "a parsed vector", [2, 2]
14
+ describe ".new" do
15
+ it "creates a vector from two coordinates" do
16
+ expect(described_class.new(2, 3).to_a).to eq([2, 3])
29
17
  end
30
18
 
31
- context "with two arguments, fixnum" do
32
- subject(:vector) { described_class.parse(1, 2) }
33
-
34
- it_behaves_like "a parsed vector", [1, 2]
19
+ context "with a complex x" do
20
+ it "raises an error" do
21
+ expect { described_class.new(Complex(1, 2), 3) }.to(
22
+ raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
23
+ )
24
+ end
35
25
  end
36
26
 
37
- context "with two arguments, float" do
38
- subject(:vector) { described_class.parse(1.0, 2.0) }
39
-
40
- it_behaves_like "a parsed vector", [1.0, 2.0]
27
+ context "with a complex y" do
28
+ it "raises an error" do
29
+ expect { described_class.new(2, Complex(1, 2)) }.to(
30
+ raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
31
+ )
32
+ end
41
33
  end
42
34
 
43
- context "with string argument, first omitted" do
44
- subject(:vector) { described_class.parse("x200") }
45
-
46
- it_behaves_like "a parsed vector", [0, 200]
35
+ context "with a non-numeric coordinate" do
36
+ it "raises an error" do
37
+ expect { described_class.new(2, nil) }.to(
38
+ raise_error(ArgumentError, "not a valid coordinate: nil")
39
+ )
40
+ end
47
41
  end
42
+ end
43
+
44
+ describe "#==" do
45
+ subject { vector == comp }
48
46
 
49
- context "with string argument, second omitted" do
50
- subject(:vector) { described_class.parse("200x") }
47
+ context "with both arguments equal" do
48
+ let(:comp) { described_class.new(2, 3) }
51
49
 
52
- it_behaves_like "a parsed vector", [200, 0]
50
+ it { is_expected.to be(true) }
53
51
  end
54
52
 
55
- context "with string argument, fixnum" do
56
- subject(:vector) { described_class.parse("1x2") }
53
+ context "with x differing" do
54
+ let(:comp) { described_class.new(3, 3) }
57
55
 
58
- it_behaves_like "a parsed vector", [1, 2]
56
+ it { is_expected.to be(false) }
59
57
  end
60
58
 
61
- context "with string argument, float" do
62
- subject(:vector) { described_class.parse("1.0x2.0") }
59
+ context "with y differing" do
60
+ let(:comp) { described_class.new(2, 4) }
63
61
 
64
- it_behaves_like "a parsed vector", [1.0, 2.0]
62
+ it { is_expected.to be(false) }
65
63
  end
66
64
 
67
- context "with array argument" do
68
- subject(:vector) { described_class.parse([1, 2]) }
65
+ context "with a number argument" do
66
+ let(:comp) { 5 }
69
67
 
70
- it_behaves_like "a parsed vector", [1, 2]
68
+ it { is_expected.to be(false) }
71
69
  end
72
70
 
73
- context "with hash argument, symbol keys" do
74
- subject(:vector) { described_class.parse(x: 1, y: 2) }
71
+ context "with an array argument" do
72
+ let(:comp) { [2, 3] }
75
73
 
76
- it_behaves_like "a parsed vector", [1, 2]
74
+ it { is_expected.to be(false) }
77
75
  end
78
76
 
79
- context "with hash argument, string keys" do
80
- subject(:vector) { described_class.parse("x" => 1, "y" => 2) }
77
+ context "with a string argument" do
78
+ let(:comp) { "2x3" }
81
79
 
82
- it_behaves_like "a parsed vector", [1, 2]
80
+ it { is_expected.to be(false) }
83
81
  end
84
82
 
85
- context "with vector argument" do
86
- subject(:vector) { described_class.parse(described_class.new(1, 2)) }
83
+ context "with a nil argument" do
84
+ let(:comp) { nil }
87
85
 
88
- it_behaves_like "a parsed vector", [1, 2]
86
+ it { is_expected.to be(false) }
89
87
  end
90
88
  end
91
89
 
92
- describe "#==" do
93
- subject { vector == comp }
90
+ describe "#eql?" do
91
+ subject { vector.eql?(comp) }
94
92
 
95
93
  context "with both arguments equal" do
96
94
  let(:comp) { described_class.new(2, 3) }
@@ -109,5 +107,47 @@ describe Vector2d do
109
107
 
110
108
  it { is_expected.to be(false) }
111
109
  end
110
+
111
+ context "with coordinates of a different type" do
112
+ let(:comp) { described_class.new(2.0, 3.0) }
113
+
114
+ it { is_expected.to be(false) }
115
+ end
116
+
117
+ context "with a subclass" do
118
+ let(:comp) { Class.new(described_class).new(2, 3) }
119
+
120
+ it { is_expected.to be(false) }
121
+ end
122
+
123
+ context "with a non-vector" do
124
+ let(:comp) { [2, 3] }
125
+
126
+ it { is_expected.to be(false) }
127
+ end
128
+ end
129
+
130
+ describe "#hash" do
131
+ subject { vector.hash }
132
+
133
+ context "with an equal vector" do
134
+ it { is_expected.to eq(described_class.new(2, 3).hash) }
135
+ end
136
+
137
+ context "with a differing vector" do
138
+ it { is_expected.not_to eq(described_class.new(3, 2).hash) }
139
+ end
140
+
141
+ context "with coordinates of a different type" do
142
+ it { is_expected.not_to eq(described_class.new(2.0, 3.0).hash) }
143
+ end
144
+
145
+ it "makes vectors usable as hash keys" do
146
+ expect({ vector => :value }[described_class.new(2, 3)]).to be(:value)
147
+ end
148
+
149
+ it "deduplicates equal vectors in arrays" do
150
+ expect([vector] | [described_class.new(2, 3)]).to eq([vector])
151
+ end
112
152
  end
113
153
  end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "matrix"
5
+
6
+ describe Vector2d do
7
+ subject(:vector) { subclass.new(2.5, 3.5) }
8
+
9
+ let(:subclass) { stub_const("SubVector", Class.new(described_class)) }
10
+
11
+ it_behaves_like "a class preserving method", :*, 2
12
+ it_behaves_like "a class preserving method", :/, 2
13
+ it_behaves_like "a class preserving method", :+, 2
14
+ it_behaves_like "a class preserving method", :-, 2
15
+ it_behaves_like "a class preserving method", :-@
16
+ it_behaves_like "a class preserving method", :+@
17
+ it_behaves_like "a class preserving method", :direction_to, 2
18
+ it_behaves_like "a class preserving method", :move_toward, 2, 0.5
19
+ it_behaves_like "a class preserving method", :move_toward, 2, 10
20
+ it_behaves_like "a class preserving method", :project, 2
21
+ it_behaves_like "a class preserving method", :reflect, 2
22
+ it_behaves_like "a class preserving method", :reject, 2
23
+ it_behaves_like "a class preserving method", :refract, 2, 0.5
24
+
25
+ it_behaves_like "a class preserving method", :transform,
26
+ Matrix[[0, -1], [1, 0]]
27
+
28
+ it_behaves_like "a class preserving method", :to_i_vector
29
+ it_behaves_like "a class preserving method", :to_f_vector
30
+
31
+ it_behaves_like "a class preserving method", :fit, 10
32
+ it_behaves_like "a class preserving method", :cover, 10
33
+
34
+ it_behaves_like "a class preserving method", :abs
35
+ it_behaves_like "a class preserving method", :ceil
36
+ it_behaves_like "a class preserving method", :clamp, 0, 10
37
+ it_behaves_like "a class preserving method", :clamp, 0..10
38
+ it_behaves_like "a class preserving method", :clamp_length, 1.0, 10.0
39
+ it_behaves_like "a class preserving method", :clamp_length, 1.0..10.0
40
+ it_behaves_like "a class preserving method", :floor
41
+ it_behaves_like "a class preserving method", :lerp, 2, 0.5
42
+ it_behaves_like "a class preserving method", :slerp, 2, 0.5
43
+ it_behaves_like "a class preserving method", :limit_length, 1.0
44
+ it_behaves_like "a class preserving method", :max, 2
45
+ it_behaves_like "a class preserving method", :midpoint, 2
46
+ it_behaves_like "a class preserving method", :min, 2
47
+ it_behaves_like "a class preserving method", :normalize
48
+ it_behaves_like "a class preserving method", :perpendicular
49
+ it_behaves_like "a class preserving method", :perpendicular_cw
50
+ it_behaves_like "a class preserving method", :resize, 2.0
51
+ it_behaves_like "a class preserving method", :reverse
52
+ it_behaves_like "a class preserving method", :rotate, Math::PI
53
+ it_behaves_like "a class preserving method", :rotate_degrees, 180
54
+ it_behaves_like "a class preserving method", :rotate_around, 0, Math::PI
55
+ it_behaves_like "a class preserving method", :round
56
+ it_behaves_like "a class preserving method", :sign
57
+ it_behaves_like "a class preserving method", :snap, 1
58
+ it_behaves_like "a class preserving method", :with_x, 5
59
+ it_behaves_like "a class preserving method", :with_y, 5
60
+
61
+ describe ".parse" do
62
+ it "rebuilds a vector of another class" do
63
+ expect(subclass.parse(described_class.new(2, 3)))
64
+ .to be_an_instance_of(subclass)
65
+ end
66
+
67
+ it "returns a vector of the receiver's class as it is" do
68
+ expect(subclass.parse(vector)).to be(vector)
69
+ end
70
+
71
+ it "returns a subclass as it is when parsed by the parent" do
72
+ expect(described_class.parse(vector)).to be(vector)
73
+ end
74
+ end
75
+
76
+ describe "#coerce" do
77
+ it "builds the other operand as the receiver's class" do
78
+ expect(vector.coerce(2).first).to be_an_instance_of(subclass)
79
+ end
80
+
81
+ it "keeps the class when the vector is the right hand operand" do
82
+ expect(2 * vector).to be_an_instance_of(subclass)
83
+ end
84
+ end
85
+
86
+ describe ".from_angle" do
87
+ it "returns an instance of the receiver's class" do
88
+ expect(subclass.from_angle(Math::PI / 2))
89
+ .to be_an_instance_of(subclass)
90
+ end
91
+ end
92
+
93
+ describe ".from_degrees" do
94
+ it "returns an instance of the receiver's class" do
95
+ expect(subclass.from_degrees(90))
96
+ .to be_an_instance_of(subclass)
97
+ end
98
+ end
99
+
100
+ describe ".random" do
101
+ it "returns an instance of the receiver's class" do
102
+ expect(subclass.random).to be_an_instance_of(subclass)
103
+ end
104
+ end
105
+
106
+ describe "the direction constants" do
107
+ %i[zero one up down left right].each do |name|
108
+ describe ".#{name}" do
109
+ it "returns an instance of the receiver's class" do
110
+ expect(subclass.public_send(name)).to be_an_instance_of(subclass)
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "#contain" do
117
+ it "returns an instance of the argument's class" do
118
+ expect(described_class.new(2, 3).contain(subclass.new(4, 5)))
119
+ .to be_an_instance_of(subclass)
120
+ end
121
+ end
122
+
123
+ context "when the subclass takes extra constructor arguments" do
124
+ subject(:vector) { labeled.new(2.5, 3.5, "point") }
125
+
126
+ let(:labeled) do
127
+ stub_const("LabeledVector", Class.new(described_class) do
128
+ attr_reader :label
129
+
130
+ def self.build(x, y)
131
+ new(x, y, "unlabeled")
132
+ end
133
+
134
+ def initialize(x, y, label)
135
+ @label = label
136
+ super(x, y)
137
+ end
138
+
139
+ def build(x, y)
140
+ self.class.new(x, y, label)
141
+ end
142
+ end)
143
+ end
144
+
145
+ it { is_expected.to be_frozen }
146
+
147
+ describe "#abs" do
148
+ subject { vector.abs }
149
+
150
+ it { is_expected.to be_an_instance_of(labeled) }
151
+ it { is_expected.to be_frozen }
152
+ its(:label) { is_expected.to eq("point") }
153
+ end
154
+
155
+ describe "#*" do
156
+ subject { vector * 2 }
157
+
158
+ its(:label) { is_expected.to eq("point") }
159
+ end
160
+
161
+ describe "#transform" do
162
+ subject { vector.transform(Matrix[[0, -1], [1, 0]]) }
163
+
164
+ its(:label) { is_expected.to eq("point") }
165
+ end
166
+
167
+ describe "#coerce" do
168
+ subject { 2 * vector }
169
+
170
+ its(:label) { is_expected.to eq("point") }
171
+ end
172
+
173
+ describe ".parse" do
174
+ subject { labeled.parse("2x3") }
175
+
176
+ it { is_expected.to be_an_instance_of(labeled) }
177
+ its(:label) { is_expected.to eq("unlabeled") }
178
+ end
179
+
180
+ describe ".parse with a Vector" do
181
+ subject { labeled.parse(Vector[2, 3]) }
182
+
183
+ its(:label) { is_expected.to eq("unlabeled") }
184
+ end
185
+
186
+ describe ".from_angle" do
187
+ subject { labeled.from_angle(0) }
188
+
189
+ its(:label) { is_expected.to eq("unlabeled") }
190
+ end
191
+
192
+ describe ".random" do
193
+ subject { labeled.random }
194
+
195
+ its(:label) { is_expected.to eq("unlabeled") }
196
+ end
197
+
198
+ describe ".up" do
199
+ subject { labeled.up }
200
+
201
+ its(:label) { is_expected.to eq("unlabeled") }
202
+ end
203
+
204
+ describe "#refract" do
205
+ subject { vector.refract(Vector2d(0, 1), 0.5) }
206
+
207
+ its(:label) { is_expected.to eq("point") }
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Vector2d do
6
+ DocTags.methods.each do |method|
7
+ describe method.path do
8
+ it "documents every parameter" do
9
+ expect(DocTags.undocumented_parameters(method)).to be_empty
10
+ end
11
+
12
+ it "documents what it returns" do
13
+ expect(method.tags(:return)).not_to be_empty
14
+ end
15
+
16
+ it "avoids type names that are too vague", :aggregate_failures do
17
+ DocTags.tags(method).each do |tag|
18
+ expect(tag.types & DocTags::FORBIDDEN).to be_empty, tag.to_s
19
+ end
20
+ end
21
+
22
+ it "names every coordinate type", :aggregate_failures do
23
+ DocTags.coordinate_tags(method).each do |tag|
24
+ expect(tag.missing(DocTags::COORDINATE)).to be_empty, tag.to_s
25
+ end
26
+ end
27
+
28
+ it "names the parse union in full", :aggregate_failures do
29
+ DocTags.parse_union_tags(method).each do |tag|
30
+ expect(tag.missing(DocTags::COERCIBLE)).to be_empty, tag.to_s
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ it "tags every method that warns as deprecated" do
37
+ expect(DocTags.untagged_deprecations).to be_empty
38
+ end
39
+
40
+ it "closes every doc comment with its tag block" do
41
+ expect(DocTags.prose_after_tags).to be_empty
42
+ end
43
+ end