Skip to content

Commit 888a1cf

Browse files
committedDec 7, 2014
Basic docs for async helpers
1 parent 646e8e2 commit 888a1cf

File tree

4 files changed

+141
-6
lines changed

4 files changed

+141
-6
lines changed
 

‎.yardopts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--markup markdown
2+
-
3+
CHANGELOG.md

‎CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212

1313
* Add timeout support to asynchronous specs
1414

15-
## 0.2.1 2013-11-24
15+
## 0.2.1 November 24, 2013

‎opal-rspec.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ Gem::Specification.new do |s|
1616

1717
s.add_dependency 'opal', '~> 0.7.0.beta1'
1818
s.add_development_dependency 'rake'
19+
s.add_development_dependency 'yard'
1920
end
2021

‎opal/opal/rspec/async.rb

+136-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,79 @@
11
module Opal
22
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.
357
module AsyncHelpers
458
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
577
def async(desc, *args, &block)
678
options = ::RSpec::Core::Metadata.build_hash_from(args)
779
Opal::RSpec::AsyncExample.register(self, desc, options, block)
@@ -12,20 +84,65 @@ def self.included(base)
1284
base.extend ClassMethods
1385
end
1486

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+
#
15101
def async(&block)
16102
@example.continue_async(block)
17103
end
18104

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
21125
def delay(duration, &block)
22126
`setTimeout(block, duration * 1000)`
23127
self
24128
end
25129

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
27143
end
28144

145+
# Runs all async examples from {AsyncExample.examples}.
29146
class AsyncRunner
30147
def initialize(runner, reporter, finish_block)
31148
@runner = runner
@@ -69,17 +186,31 @@ def finish
69186
end
70187
end
71188

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.
72197
class AsyncExample < ::RSpec::Core::Example
198+
include AsyncHelpers
199+
200+
# Register new async example.
201+
#
202+
# @see AsyncHelpers::ClassMethods.async
73203
def self.register(*args)
74204
examples << new(*args)
75205
end
76206

207+
# All async examples in specs.
208+
#
209+
# @return [Array<AsyncExample>]
77210
def self.examples
78211
@examples ||= []
79212
end
80213

81-
include AsyncHelpers
82-
83214
def run(example_group_instance, reporter, &after_run_block)
84215
@example_group_instance = example_group_instance
85216
@reporter = reporter

0 commit comments

Comments
 (0)
Please sign in to comment.