1
1
module Opal
2
2
module RSpec
3
+ # {AsyncHelpers} is automatically included in all example groups to add
4
+ # support for running specs async. Usually, rspec runners expect all
5
+ # examples to run synchronously, but this is not ideal in the case for
6
+ # Opal where a lot of underlying libraries expect the ability to run code
7
+ # in an asynchronous manner.
8
+ #
9
+ # This module defines an {AsyncHelpers::ClassMethods.async} method which
10
+ # can be used instead of `it` inside an example group, which marks the
11
+ # example as being async. This makes the runner wait for the example to
12
+ # complete.
13
+ #
14
+ # describe "Some examples" do
15
+ # it "normal example" do
16
+ # # normal test code
17
+ # end
18
+ #
19
+ # async "async example" do
20
+ # # this will wait until completion before moving on
21
+ # end
22
+ # end
23
+ #
24
+ # Marking an example as being async is only half the task. Examples will
25
+ # also have an instance {AsyncHelpers#async} method defined which is then
26
+ # used to complete the example. Any code run inside this block will run
27
+ # inside the context of the example.
28
+ #
29
+ # describe "HTTP requests" do
30
+ # async "might take a while" do
31
+ # HTTP.get("/url/to/get") do |res|
32
+ # async { expect(res).to be_ok }
33
+ # end
34
+ # end
35
+ # end
36
+ #
37
+ # As soon as `async` is run inside the block, the example completes. This
38
+ # means that only 1 `async` call is allowed. However, you can use `async`
39
+ # multiple times aslong as it is only called once:
40
+ #
41
+ # describe "HTTP requests" do
42
+ # async "should work" do
43
+ # HTTP.get("/users/1").then |res|
44
+ # async { expect(res).to be_ok }
45
+ # end.fail do
46
+ # async { raise "this should not be called" }
47
+ # end
48
+ # end
49
+ # end
50
+ #
51
+ # Here, a promise will either be accepted or rejected, so an `async` block
52
+ # can be used in each case as only 1 will be called.
53
+ #
54
+ # Another helper, {AsyncHelpers#delay} can also be used to run a block of
55
+ # code after a given time in seconds. This is useful to wait for animations
56
+ # or time restricted operations to occur.
3
57
module AsyncHelpers
4
58
module ClassMethods
59
+ # Define an async example method. This should be used instead of `it`
60
+ # to inform the spec runner that the example will need to wait for an
61
+ # {AsyncHelpers#async} method to complete the test. Any additional
62
+ # configuration options can be passed to this call, and they just get
63
+ # delegated to the underlying `#it` call.
64
+ #
65
+ # @example
66
+ # describe "Some tests" do
67
+ # async "should be async" do
68
+ # # ... async code
69
+ # end
70
+ #
71
+ # it "should work with normal tests" do
72
+ # expect(1).to eq(1)
73
+ # end
74
+ # end
75
+ #
76
+ # @param desc [String] description
5
77
def async ( desc , *args , &block )
6
78
options = ::RSpec ::Core ::Metadata . build_hash_from ( args )
7
79
Opal ::RSpec ::AsyncExample . register ( self , desc , options , block )
@@ -12,20 +84,65 @@ def self.included(base)
12
84
base . extend ClassMethods
13
85
end
14
86
87
+ # Must be used with {ClassMethods#async} to finish the async action. If
88
+ # this is not called inside the body then the spec runner will time out
89
+ # or the error might give a false positive as it is not caught inside
90
+ # the current example.
91
+ #
92
+ # @example Complete expectation after HTTP request
93
+ # describe "HTTP calls" do
94
+ # async "complete eventually" do
95
+ # HTTP.get("/some_url") do |response|
96
+ # async { expect(response).to be_ok }
97
+ # end
98
+ # end
99
+ # end
100
+ #
15
101
def async ( &block )
16
102
@example . continue_async ( block )
17
103
end
18
104
19
- alias run_async async
20
-
105
+ # Runs the given block after a given duration. You are still required to
106
+ # use a {#async} block inside the delayed callback. This helper can be
107
+ # used to simulate IO delays, or just to wait for animations/other
108
+ # behaviour to finish.
109
+ #
110
+ # The `duaration` should be given in seconds, i.e. `1` means 1 second, or
111
+ # 0.3 means 300ms. The given block is just run after the time delay.
112
+ #
113
+ # @example
114
+ # describe "Some interaction" do
115
+ # async "takes a while to complete" do
116
+ # task = start_long_task!
117
+ #
118
+ # delay(1) do
119
+ # async { expect(task).to be_completed }
120
+ # end
121
+ # end
122
+ # end
123
+ #
124
+ # @param duration [Integer, Float] time in seconds to wait
21
125
def delay ( duration , &block )
22
126
`setTimeout(block, duration * 1000)`
23
127
self
24
128
end
25
129
26
- alias set_timeout delay
130
+ # Use {#async} instead.
131
+ #
132
+ # @deprecated
133
+ def run_async ( &block )
134
+ async ( &block )
135
+ end
136
+
137
+ # Use {#delay} instead.
138
+ #
139
+ # @deprecated
140
+ def set_timeout ( *args , &block )
141
+ delay ( *args , &block )
142
+ end
27
143
end
28
144
145
+ # Runs all async examples from {AsyncExample.examples}.
29
146
class AsyncRunner
30
147
def initialize ( runner , reporter , finish_block )
31
148
@runner = runner
@@ -69,17 +186,31 @@ def finish
69
186
end
70
187
end
71
188
189
+ # An {AsyncExample} is a subclass of regular example instances which adds
190
+ # support for running an example, and waiting for a non-sync outcome. All
191
+ # async examples in a set of spec files will get registered through
192
+ # {AsyncExample.register}, and added to the {AsyncExample.examples} array
193
+ # ready for the runner to run.
194
+ #
195
+ # You will not need to create new instances of this class directly, and
196
+ # should instead use {AsyncHelpers} to create async examples.
72
197
class AsyncExample < ::RSpec ::Core ::Example
198
+ include AsyncHelpers
199
+
200
+ # Register new async example.
201
+ #
202
+ # @see AsyncHelpers::ClassMethods.async
73
203
def self . register ( *args )
74
204
examples << new ( *args )
75
205
end
76
206
207
+ # All async examples in specs.
208
+ #
209
+ # @return [Array<AsyncExample>]
77
210
def self . examples
78
211
@examples ||= [ ]
79
212
end
80
213
81
- include AsyncHelpers
82
-
83
214
def run ( example_group_instance , reporter , &after_run_block )
84
215
@example_group_instance = example_group_instance
85
216
@reporter = reporter
0 commit comments