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,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
|
data/spec/lib/vector2d_spec.rb
CHANGED
|
@@ -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 ".
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
50
|
-
|
|
47
|
+
context "with both arguments equal" do
|
|
48
|
+
let(:comp) { described_class.new(2, 3) }
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
it { is_expected.to be(true) }
|
|
53
51
|
end
|
|
54
52
|
|
|
55
|
-
context "with
|
|
56
|
-
|
|
53
|
+
context "with x differing" do
|
|
54
|
+
let(:comp) { described_class.new(3, 3) }
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
it { is_expected.to be(false) }
|
|
59
57
|
end
|
|
60
58
|
|
|
61
|
-
context "with
|
|
62
|
-
|
|
59
|
+
context "with y differing" do
|
|
60
|
+
let(:comp) { described_class.new(2, 4) }
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
it { is_expected.to be(false) }
|
|
65
63
|
end
|
|
66
64
|
|
|
67
|
-
context "with
|
|
68
|
-
|
|
65
|
+
context "with a number argument" do
|
|
66
|
+
let(:comp) { 5 }
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
it { is_expected.to be(false) }
|
|
71
69
|
end
|
|
72
70
|
|
|
73
|
-
context "with
|
|
74
|
-
|
|
71
|
+
context "with an array argument" do
|
|
72
|
+
let(:comp) { [2, 3] }
|
|
75
73
|
|
|
76
|
-
|
|
74
|
+
it { is_expected.to be(false) }
|
|
77
75
|
end
|
|
78
76
|
|
|
79
|
-
context "with
|
|
80
|
-
|
|
77
|
+
context "with a string argument" do
|
|
78
|
+
let(:comp) { "2x3" }
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
it { is_expected.to be(false) }
|
|
83
81
|
end
|
|
84
82
|
|
|
85
|
-
context "with
|
|
86
|
-
|
|
83
|
+
context "with a nil argument" do
|
|
84
|
+
let(:comp) { nil }
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
it { is_expected.to be(false) }
|
|
89
87
|
end
|
|
90
88
|
end
|
|
91
89
|
|
|
92
|
-
describe "
|
|
93
|
-
subject { vector
|
|
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
|