r_spec-clone 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ # Clone namespace.
5
+ #
6
+ # @api private
7
+ module Clone
8
+ end
9
+ end
10
+
11
+ require_relative File.join("..", "r_spec")
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Clone
5
+ # Send log messages to the console.
6
+ #
7
+ # @api private
8
+ module Console
9
+ # @param report [::Expresenter::Pass] Passed expectation result presenter.
10
+ #
11
+ # @see https://github.com/fixrb/expresenter
12
+ #
13
+ # @return [nil] Add a colored message to `$stdout`.
14
+ def self.passed_spec(report)
15
+ puts report.colored_string
16
+ end
17
+
18
+ # @param report [::Expresenter::Fail] Failed expectation result presenter.
19
+ #
20
+ # @see https://github.com/fixrb/expresenter
21
+ #
22
+ # @raise [SystemExit] Terminate execution immediately with colored message.
23
+ def self.failed_spec(report)
24
+ abort report.colored_string
25
+ end
26
+
27
+ # The Ruby source filename and line number containing this method or nil if
28
+ # this method was not defined in Ruby (i.e. native).
29
+ #
30
+ # @param filename [String, nil] The Ruby source filename.
31
+ # @param line [Integer, nil] The Ruby source line number.
32
+ #
33
+ # @return [String] The Ruby source filename and line number associated with
34
+ # the evaluated spec.
35
+ def self.source(filename, line)
36
+ puts [filename, line].compact.join(":")
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,350 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aw"
4
+
5
+ require_relative "console"
6
+ require_relative "error"
7
+ require_relative "expectation_helper"
8
+
9
+ module RSpec
10
+ module Clone
11
+ # Abstract class for handling the domain-specific language.
12
+ class Dsl
13
+ BEFORE_METHOD = :initialize
14
+ AFTER_METHOD = :terminate
15
+
16
+ private_constant :BEFORE_METHOD, :AFTER_METHOD
17
+
18
+ # Executes the given block before each spec in the current context runs.
19
+ #
20
+ # @example
21
+ # require "r_spec/clone"
22
+ #
23
+ # RSpec.describe Integer do
24
+ # before do
25
+ # @value = 123
26
+ # end
27
+ #
28
+ # it { expect(@value).to be 123 }
29
+ #
30
+ # describe "nested" do
31
+ # before do
32
+ # @value -= 81
33
+ # end
34
+ #
35
+ # it { expect(@value).to be 42 }
36
+ # end
37
+ #
38
+ # it { expect(@value).to be 123 }
39
+ # end
40
+ #
41
+ # # Output to the console
42
+ # # Success: expected to be 123.
43
+ # # Success: expected to be 42.
44
+ # # Success: expected to be 123.
45
+ #
46
+ # @param block [Proc] The content to execute at the class initialization.
47
+ def self.before(&block)
48
+ define_method(BEFORE_METHOD) do
49
+ super()
50
+ instance_eval(&block)
51
+ end
52
+
53
+ private BEFORE_METHOD
54
+ end
55
+
56
+ # Executes the given block after each spec in the current context runs.
57
+ #
58
+ # @example
59
+ # require "r_spec/clone"
60
+ #
61
+ # RSpec.describe Integer do
62
+ # after do
63
+ # puts "That is the answer to everything."
64
+ # end
65
+ #
66
+ # it { expect(42).to be 42 }
67
+ # end
68
+ #
69
+ # # Output to the console
70
+ # # Success: expected to be 42.
71
+ # # That is the answer to everything.
72
+ #
73
+ # @param block [Proc] The content to execute at the class initialization.
74
+ def self.after(&block)
75
+ define_method(AFTER_METHOD) do
76
+ instance_exec(&block)
77
+ super()
78
+ end
79
+
80
+ private AFTER_METHOD
81
+ end
82
+
83
+ # Sets a user-defined property.
84
+ #
85
+ # @example
86
+ # require "r_spec/clone"
87
+ #
88
+ # RSpec.describe "Name stories" do
89
+ # let(:name) { "Bob" }
90
+ #
91
+ # it { expect(name).to eq "Bob" }
92
+ #
93
+ # context "with last name" do
94
+ # let(:name) { "#{super()} Smith" }
95
+ #
96
+ # it { expect(name).to eq "Bob Smith" }
97
+ # end
98
+ # end
99
+ #
100
+ # # Output to the console
101
+ # # Success: expected to eq "Bob".
102
+ # # Success: expected to eq "Bob Smith".
103
+ #
104
+ # @param name [String, Symbol] The name of the property.
105
+ # @param block [Proc] The content of the method to define.
106
+ #
107
+ # @return [Symbol] A private method that define the block content.
108
+ def self.let(name, *args, **kwargs, &block)
109
+ raise Error::ReservedMethod if [BEFORE_METHOD, AFTER_METHOD].include?(name.to_sym)
110
+
111
+ private define_method(name, *args, **kwargs, &block)
112
+ end
113
+
114
+ # Sets a user-defined property named {#subject}.
115
+ #
116
+ # @example
117
+ # require "r_spec/clone"
118
+ #
119
+ # RSpec.describe Array do
120
+ # subject { [1, 2, 3] }
121
+ #
122
+ # it "has the prescribed elements" do
123
+ # expect(subject).to eq([1, 2, 3])
124
+ # end
125
+ # end
126
+ #
127
+ # # Output to the console
128
+ # # Success: expected to eq [1, 2, 3].
129
+ #
130
+ # @param block [Proc] The subject to set.
131
+ # @return [Symbol] A {#subject} method that define the block content.
132
+ def self.subject(&block)
133
+ let(__method__, &block)
134
+ end
135
+
136
+ # Defines an example group that describes a unit to be tested.
137
+ #
138
+ # @example
139
+ # require "r_spec/clone"
140
+ #
141
+ # RSpec.describe String do
142
+ # describe "+" do
143
+ # it("concats") { expect("foo" + "bar").to eq "foobar" }
144
+ # end
145
+ # end
146
+ #
147
+ # # Output to the console
148
+ # # Success: expected to eq "foobar".
149
+ #
150
+ # @param const [Module, String] A module to include in block context.
151
+ # @param block [Proc] The block to define the specs.
152
+ def self.describe(const, &block)
153
+ desc = ::Class.new(self)
154
+ desc.let(:described_class) { const } if const.is_a?(::Module)
155
+ desc.instance_eval(&block)
156
+ end
157
+
158
+ # Defines an example group that establishes a specific context, like _empty
159
+ # array_ versus _array with elements_.
160
+ #
161
+ # Unlike {.describe}, the block is evaluated in isolation in order to scope
162
+ # possible side effects inside its context.
163
+ #
164
+ # @example
165
+ # require "r_spec/clone"
166
+ #
167
+ # RSpec.describe "web resource" do
168
+ # context "when resource is not found" do
169
+ # pending "responds with 404"
170
+ # end
171
+ #
172
+ # context "when resource is found" do
173
+ # pending "responds with 200"
174
+ # end
175
+ # end
176
+ #
177
+ # # Output to the console
178
+ # # Warning: responds with 404.
179
+ # # Warning: responds with 200.
180
+ #
181
+ # @param _description [String] A description that usually begins with
182
+ # "when", "with" or "without".
183
+ # @param block [Proc] The block to define the specs.
184
+ def self.context(_description, &block)
185
+ desc = ::Class.new(self)
186
+ ::Aw.fork! { desc.instance_eval(&block) }
187
+ end
188
+
189
+ # Defines a concrete test case.
190
+ #
191
+ # The test is performed by the block supplied to `&block`.
192
+ #
193
+ # @example The integer after 41
194
+ # require "r_spec/clone"
195
+ #
196
+ # RSpec.describe Integer do
197
+ # it { expect(41.next).to be 42 }
198
+ # end
199
+ #
200
+ # # Output to the console
201
+ # # Success: expected to be 42.
202
+ #
203
+ # @example A division by zero
204
+ # require "r_spec/clone"
205
+ #
206
+ # RSpec.describe Integer do
207
+ # subject { 41 }
208
+ #
209
+ # it { is_expected.to be_an_instance_of described_class }
210
+ #
211
+ # it "raises an error" do
212
+ # expect { subject / 0 }.to raise_exception ZeroDivisionError
213
+ # end
214
+ # end
215
+ #
216
+ # # Output to the console
217
+ # # Success: expected 41 to be an instance of Integer.
218
+ # # Success: divided by 0.
219
+ #
220
+ # It can be used inside a {.describe} or {.context} section.
221
+ #
222
+ # @param _name [String, nil] The name of the spec.
223
+ # @param block [Proc] An expectation to evaluate.
224
+ #
225
+ # @raise (see ExpectationTarget::Base#result)
226
+ # @return (see ExpectationTarget::Base#result)
227
+ def self.it(_name = nil, &block)
228
+ raise ::ArgumentError, "Missing example block" unless block
229
+
230
+ example = ::Class.new(self) { include ExpectationHelper::It }.new
231
+ example.instance_eval(&block)
232
+ rescue ::SystemExit
233
+ Console.source(*block.source_location)
234
+
235
+ exit false
236
+ ensure
237
+ example&.send(AFTER_METHOD)
238
+ end
239
+
240
+ # Use the {.its} method to define a single spec that specifies the actual
241
+ # value of an attribute of the subject using
242
+ # {ExpectationHelper::Its#is_expected}.
243
+ #
244
+ # @example The integer after 41
245
+ # require "r_spec/clone"
246
+ #
247
+ # RSpec.describe Integer do
248
+ # subject { 41 }
249
+ #
250
+ # its(:next) { is_expected.to be 42 }
251
+ # end
252
+ #
253
+ # # Output to the console
254
+ # # Success: expected to be 42.
255
+ #
256
+ # @example A division by zero
257
+ # require "r_spec/clone"
258
+ #
259
+ # RSpec.describe Integer do
260
+ # subject { 41 }
261
+ #
262
+ # its(:/, 0) { is_expected.to raise_exception ZeroDivisionError }
263
+ # end
264
+ #
265
+ # # Output to the console
266
+ # # Success: divided by 0.
267
+ #
268
+ # @example A spec without subject
269
+ # require "r_spec/clone"
270
+ #
271
+ # RSpec.describe Integer do
272
+ # its(:boom) { is_expected.to raise_exception RSpec::Error::UndefinedSubject }
273
+ # end
274
+ #
275
+ # # Output to the console
276
+ # # Success: subject not explicitly defined.
277
+ #
278
+ # @param attribute [String, Symbol] The property to call to subject.
279
+ # @param args [Array] An optional list of arguments.
280
+ # @param kwargs [Hash] An optional list of keyword arguments.
281
+ # @param block [Proc] An expectation to evaluate.
282
+ #
283
+ # @raise (see ExpectationTarget::Base#result)
284
+ # @return (see ExpectationTarget::Base#result)
285
+ def self.its(attribute, *args, **kwargs, &block)
286
+ raise ::ArgumentError, "Missing example block" unless block
287
+
288
+ example = ::Class.new(self) do
289
+ include ExpectationHelper::Its
290
+
291
+ define_method(:actual) do
292
+ subject.public_send(attribute, *args, **kwargs)
293
+ end
294
+ end.new
295
+
296
+ example.instance_eval(&block)
297
+ rescue ::SystemExit
298
+ Console.source(*block.source_location)
299
+
300
+ exit false
301
+ ensure
302
+ example&.send(AFTER_METHOD)
303
+ end
304
+
305
+ # Defines a pending test case.
306
+ #
307
+ # `&block` is never evaluated. It can be used to describe behaviour that is
308
+ # not yet implemented.
309
+ #
310
+ # @example
311
+ # require "r_spec/clone"
312
+ #
313
+ # RSpec.describe "an example" do
314
+ # pending "is implemented but waiting" do
315
+ # expect something to be finished
316
+ # end
317
+ #
318
+ # pending "is not yet implemented and waiting"
319
+ # end
320
+ #
321
+ # # Output to the console
322
+ # # Warning: is implemented but waiting.
323
+ # # Warning: is not yet implemented and waiting.
324
+ #
325
+ # @param message [String] The reason why the example is pending.
326
+ #
327
+ # @return [nil] Write a message to STDOUT.
328
+ #
329
+ # @api public
330
+ def self.pending(message)
331
+ Console.passed_spec Error::PendingExpectation.result(message)
332
+ end
333
+
334
+ private
335
+
336
+ def described_class
337
+ raise Error::UndefinedDescribedClass,
338
+ "the first argument to at least one example group must be a module"
339
+ end
340
+
341
+ def subject
342
+ raise Error::UndefinedSubject, "subject not explicitly defined"
343
+ end
344
+
345
+ define_method(AFTER_METHOD) do
346
+ # do nothing by default
347
+ end
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("error", "pending_expectation")
4
+ require_relative File.join("error", "reserved_method")
5
+ require_relative File.join("error", "undefined_described_class")
6
+ require_relative File.join("error", "undefined_subject")
7
+
8
+ module RSpec
9
+ module Clone
10
+ # Namespace for exceptions.
11
+ #
12
+ # @api private
13
+ module Error
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "expresenter"
4
+
5
+ module RSpec
6
+ module Clone
7
+ module Error
8
+ # Exception for pending expectations.
9
+ #
10
+ # @api private
11
+ class PendingExpectation < ::RuntimeError
12
+ # @param message [String] The not implemented expectation description.
13
+ #
14
+ # @return [nil] Write a pending expectation to STDOUT.
15
+ def self.result(message)
16
+ ::Expresenter.call(true).with(
17
+ actual: new(message),
18
+ error: nil,
19
+ expected: self,
20
+ got: false,
21
+ matcher: :raise_exception,
22
+ negate: true,
23
+ level: :SHOULD
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end