a-ruby-promise 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+ require_relative "../spec_helper"
3
+ require_relative "../promises_aplus"
4
+
5
+ describe "2.2.5 `onFulfilled` and `onRejected` must be called as functions (i.e. with no `this` value)." do
6
+ it "is specific to JavaScript" do skip end
7
+ end
@@ -0,0 +1,238 @@
1
+ # encoding: UTF-8
2
+ require_relative "../spec_helper"
3
+ require_relative "../promises_aplus"
4
+
5
+ dummy = { dummy: "dummy" } # we fulfill or reject with this when we don't intend to test against it
6
+ other = { other: "other" } # a value we don't want to be strict equal to
7
+ sentinel = { sentinel: "sentinel" } # a sentinel fulfillment value to test for with strict equality
8
+ sentinel2 = { sentinel2: "sentinel2" }
9
+ sentinel3 = { sentinel3: "sentinel3" }
10
+
11
+ CallbackAggregator = Struct.new(:times, :ultimate_callback) do
12
+ def call
13
+ @so_far = (@so_far || 0) + 1
14
+ ultimate_callback.call if @so_far == times
15
+ end
16
+ end
17
+
18
+ describe "2.2.6: `then` may be called multiple times on the same promise." do
19
+ include PromisesAplus
20
+
21
+ describe "2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` callbacks must execute in the order of their originating calls to `then`." do
22
+ describe "multiple boring fulfillment handlers" do
23
+ PromisesAplus.test_fulfilled(self, sentinel) do |promise, done|
24
+ handler1 = Minitest::Mock.new.expect(:call, nil, [sentinel])
25
+ handler2 = Minitest::Mock.new.expect(:call, nil, [sentinel])
26
+ handler3 = Minitest::Mock.new.expect(:call, nil, [sentinel])
27
+ spy = Minitest::Mock.new
28
+
29
+ promise.then(handler1, spy)
30
+ promise.then(handler2, spy)
31
+ promise.then(handler3, spy)
32
+
33
+ promise.then ->(value) {
34
+ value.must_equal sentinel
35
+ [handler1, handler2, handler3, spy].each(&:verify)
36
+ done.call
37
+ }
38
+ end
39
+ end
40
+
41
+ describe "multiple fulfillment handlers, one of which throws" do
42
+ PromisesAplus.test_fulfilled(self, sentinel) do |promise, done|
43
+ handler1 = Minitest::Mock.new.expect(:call, nil, [sentinel])
44
+ handler2 = Minitest::Mock.new.expect(:call, nil, [sentinel])
45
+ handler3 = Minitest::Mock.new.expect(:call, nil, [sentinel])
46
+ spy = Minitest::Mock.new
47
+
48
+ promise.then(handler1, spy)
49
+ promise.then(->(value) { handler2.call(value); throw(other) }, spy)
50
+ promise.then(handler3, spy)
51
+
52
+ promise.then ->(value) {
53
+ value.must_equal sentinel
54
+ [handler1, handler2, handler3, spy].each(&:verify)
55
+ done.call
56
+ }
57
+ end
58
+ end
59
+
60
+ describe "results in multiple branching chains with their own fulfillment values" do
61
+ PromisesAplus.test_fulfilled(self, sentinel) do |promise, done|
62
+ semi_done = CallbackAggregator.new(3, done)
63
+
64
+ promise.then(->(value) {
65
+ sentinel
66
+ }).then(->(value) {
67
+ value.must_equal sentinel
68
+ semi_done.call
69
+ })
70
+
71
+ promise.then(->(value) {
72
+ raise Exception.new(sentinel2)
73
+ }).then(nil, ->(reason) {
74
+ reason.to_s.must_equal sentinel2.to_s
75
+ semi_done.call
76
+ })
77
+
78
+ promise.then(->(value) {
79
+ sentinel3
80
+ }).then(->(value) {
81
+ value.must_equal sentinel3
82
+ semi_done.call
83
+ })
84
+ end
85
+ end
86
+
87
+ describe "`onFulfilled` handlers are called in the original order" do
88
+ PromisesAplus.test_fulfilled(self, dummy) do |promise, done|
89
+ handler1 = Minitest::Mock.new.expect(:call, 1, [dummy])
90
+ handler2 = Minitest::Mock.new.expect(:call, 2, [dummy])
91
+ handler3 = Minitest::Mock.new.expect(:call, 3, [dummy])
92
+ sequence = Minitest::Mock.new.expect(:call, nil, [1]).expect(:call, nil, [2]).expect(:call, nil, [3])
93
+
94
+ promise.then(handler1).then(sequence)
95
+ promise.then(handler2).then(sequence)
96
+ promise.then(handler3).then(sequence)
97
+
98
+ promise.then ->(value) {
99
+ value.must_equal dummy
100
+ [handler1, handler2, handler3, sequence].each(&:verify)
101
+ done.call
102
+ }
103
+ end
104
+
105
+ describe "even when one handler is added inside another handler" do
106
+ PromisesAplus.test_fulfilled(self, dummy) do |promise, done|
107
+ handler1 = Minitest::Mock.new.expect(:call, 1, [dummy])
108
+ handler2 = Minitest::Mock.new.expect(:call, 2, [dummy])
109
+ handler3 = Minitest::Mock.new.expect(:call, 3, [dummy])
110
+ sequence = Minitest::Mock.new.expect(:call, nil, [1]).expect(:call, nil, [2]).expect(:call, nil, [3])
111
+
112
+ # TODO: The JavaScript spec adds handler3 inside this handler,
113
+ # but since we don't have the delayed execution of handlers, we can't do that!
114
+ promise.then ->(value) {
115
+ sequence.call(handler1.call(value))
116
+ promise.then(handler2).then(sequence)
117
+ }
118
+ promise.then(handler3).then(sequence)
119
+
120
+ promise.then ->(value) {
121
+ value.must_equal dummy
122
+ [handler1, handler2, handler3, sequence].each(&:verify)
123
+ done.call
124
+ }
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ describe "2.2.6.2: If/when `promise` is rejected, all respective `onRejected` callbacks must execute in the order of their originating calls to `then`." do
131
+ describe "multiple boring rejection handlers" do
132
+ PromisesAplus.test_rejected(self, sentinel) do |promise, done|
133
+ handler1 = Minitest::Mock.new.expect(:call, nil, [sentinel])
134
+ handler2 = Minitest::Mock.new.expect(:call, nil, [sentinel])
135
+ handler3 = Minitest::Mock.new.expect(:call, nil, [sentinel])
136
+ spy = Minitest::Mock.new
137
+
138
+ promise.then(spy, handler1)
139
+ promise.then(spy, handler2)
140
+ promise.then(spy, handler3)
141
+
142
+ promise.then nil, ->(reason) {
143
+ reason.must_equal sentinel
144
+ [handler1, handler2, handler3, spy].each(&:verify)
145
+ done.call
146
+ }
147
+ end
148
+ end
149
+
150
+ describe "multiple rejection handlers, one of which throws" do
151
+ PromisesAplus.test_rejected(self, sentinel) do |promise, done|
152
+ handler1 = Minitest::Mock.new.expect(:call, nil, [sentinel])
153
+ handler2 = Minitest::Mock.new.expect(:call, nil, [sentinel])
154
+ handler3 = Minitest::Mock.new.expect(:call, nil, [sentinel])
155
+ spy = Minitest::Mock.new
156
+
157
+ promise.then(spy, handler1)
158
+ promise.then(spy, ->(reason) { handler2.call(reason); throw(other) })
159
+ promise.then(spy, handler3)
160
+
161
+ promise.then nil, ->(reason) {
162
+ reason.must_equal sentinel
163
+ [handler1, handler2, handler3, spy].each(&:verify)
164
+ done.call
165
+ }
166
+ end
167
+ end
168
+
169
+ describe "results in multiple branching chains with their own fulfillment values" do
170
+ PromisesAplus.test_rejected(self, sentinel) do |promise, done|
171
+ semi_done = CallbackAggregator.new(3, done)
172
+
173
+ promise.then(nil, ->(reason) {
174
+ sentinel
175
+ }).then(->(value) {
176
+ value.must_equal sentinel
177
+ semi_done.call
178
+ })
179
+
180
+ promise.then(nil, ->(reason) {
181
+ raise Exception.new(sentinel2)
182
+ }).then(nil, ->(reason) {
183
+ reason.to_s.must_equal sentinel2.to_s
184
+ semi_done.call
185
+ })
186
+
187
+ promise.then(nil, ->(reason) {
188
+ sentinel3
189
+ }).then(->(value) {
190
+ value.must_equal sentinel3
191
+ semi_done.call
192
+ })
193
+ end
194
+ end
195
+
196
+ describe "`onRejected` handlers are called in the original order" do
197
+ PromisesAplus.test_rejected(self, dummy) do |promise, done|
198
+ handler1 = Minitest::Mock.new.expect(:call, 1, [dummy])
199
+ handler2 = Minitest::Mock.new.expect(:call, 2, [dummy])
200
+ handler3 = Minitest::Mock.new.expect(:call, 3, [dummy])
201
+ sequence = Minitest::Mock.new.expect(:call, nil, [1]).expect(:call, nil, [2]).expect(:call, nil, [3])
202
+
203
+ promise.then(nil, handler1).then(sequence)
204
+ promise.then(nil, handler2).then(sequence)
205
+ promise.then(nil, handler3).then(sequence)
206
+
207
+ promise.then nil, ->(reason) {
208
+ reason.must_equal dummy
209
+ [handler1, handler2, handler3, sequence].each(&:verify)
210
+ done.call
211
+ }
212
+ end
213
+
214
+ describe "even when one handler is added inside another handler" do
215
+ PromisesAplus.test_rejected(self, dummy) do |promise, done|
216
+ handler1 = Minitest::Mock.new.expect(:call, 1, [dummy])
217
+ handler2 = Minitest::Mock.new.expect(:call, 2, [dummy])
218
+ handler3 = Minitest::Mock.new.expect(:call, 3, [dummy])
219
+ sequence = Minitest::Mock.new.expect(:call, nil, [1]).expect(:call, nil, [2]).expect(:call, nil, [3])
220
+
221
+ # TODO: The JavaScript spec adds handler3 inside this handler,
222
+ # but since we don't have the delayed execution of handlers, we can't do that!
223
+ promise.then nil, ->(reason) {
224
+ sequence.call(handler1.call(reason))
225
+ promise.then(nil, handler2).then(sequence)
226
+ }
227
+ promise.then(nil, handler3).then(sequence)
228
+
229
+ promise.then nil, ->(reason) {
230
+ reason.must_equal dummy
231
+ [handler1, handler2, handler3, sequence].each(&:verify)
232
+ done.call
233
+ }
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,99 @@
1
+ # encoding: UTF-8
2
+ require_relative "../spec_helper"
3
+ require_relative "../promises_aplus"
4
+
5
+ dummy = { dummy: "dummy" } # we fulfill or reject with this when we don't intend to test against it
6
+ sentinel = { sentinel: "sentinel" } # a sentinel fulfillment value to test for with strict equality
7
+ other = { other: "other" } # a value we don't want to be strict equal to
8
+
9
+ reasons = {
10
+ "`nil`" => nil,
11
+ "`false`" => false,
12
+ "`0`" => 0,
13
+ "an error" => StandardError.new,
14
+ "an error without a stack" => StandardError.new,
15
+ "a date" => Time.now,
16
+ "an object" => Object.new,
17
+ "an always-pending thenable" => Struct.new(:then).new(->(v) {}),
18
+ "a fulfilled promise" => resolved(dummy),
19
+ "a rejected promise" => rejected(dummy)
20
+ }
21
+
22
+ non_functions = {
23
+ "`nil`" => nil,
24
+ "`false`" => false,
25
+ "`5`" => 5,
26
+ "an object" => Object.new,
27
+ "an array containing a callable" => [->(value) { other }]
28
+ }
29
+
30
+ describe "2.2.7: `then` must return a promise: `promise2 = promise1.then(onFulfilled, onRejected)`" do
31
+ include PromisesAplus
32
+
33
+ it "is a promise" do
34
+ promise1 = deferred.promise
35
+ promise2 = promise1.then
36
+
37
+ promise2.must_be_kind_of Promise
38
+ done!
39
+ end
40
+
41
+ describe "2.2.7.1: If either `onFulfilled` or `onRejected` returns a value `x`, run the Promise Resolution Procedure `[[Resolve]](promise2, x)`" do
42
+ it "see separate 3.3 tests" do
43
+ done! and skip
44
+ end
45
+ end
46
+
47
+ describe "2.2.7.2: If either `onFulfilled` or `onRejected` throws an exception `e`, `promise2` must be rejected with `e` as the reason." do
48
+ reasons.each do |description, expected_reason|
49
+ describe "The reason is #{description}" do
50
+ PromisesAplus.test_fulfilled(self, dummy) do |promise1, done|
51
+ promise2 = promise1.then ->(value) {
52
+ raise Exception, expected_reason.to_s
53
+ }
54
+ promise2.then nil, ->(reason) {
55
+ reason.to_s.must_equal expected_reason.to_s
56
+ done.call
57
+ }
58
+ end
59
+ PromisesAplus.test_rejected(self, dummy) do |promise1, done|
60
+ promise2 = promise1.then nil, ->(reason) {
61
+ raise Exception, expected_reason.to_s
62
+ }
63
+ promise2.then nil, ->(reason) {
64
+ reason.to_s.must_equal expected_reason.to_s
65
+ done.call
66
+ }
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "2.2.7.3: If `onFulfilled` is not a function and `promise1` is fulfilled, `promise2` must be fulfilled with the same value." do
73
+ non_functions.each do |description, non_function|
74
+ describe "`onFulfilled` is #{description}" do
75
+ PromisesAplus.test_fulfilled(self, sentinel) do |promise1, done|
76
+ promise2 = promise1.then(non_function)
77
+ promise2.then ->(value) {
78
+ value.must_equal sentinel
79
+ done.call
80
+ }
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "2.2.7.4: If `onRejected` is not a function and `promise1` is rejected, `promise2` must be rejected with the same reason." do
87
+ non_functions.each do |description, non_function|
88
+ describe "`onRejected` is #{description}" do
89
+ PromisesAplus.test_rejected(self, sentinel) do |promise1, done|
90
+ promise2 = promise1.then(nil, non_function)
91
+ promise2.then nil, ->(reason) {
92
+ reason.must_equal sentinel
93
+ done.call
94
+ }
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,44 @@
1
+ require "minitest/autorun"
2
+ require "minitest/mock"
3
+ require "minitest/pride"
4
+ require "a-ruby-promise"
5
+ require_relative "deferred"
6
+
7
+ Thread.abort_on_exception = true
8
+
9
+ def resolved(value)
10
+ Promise.new { fulfill(value) }
11
+ end
12
+
13
+ def rejected(reason)
14
+ Promise.new { reject(reason) }
15
+ end
16
+
17
+ def deferred
18
+ Deferred.new
19
+ end
20
+
21
+ def short_sleep
22
+ sleep 0.05
23
+ end
24
+
25
+ def assert_unresolved(*promises)
26
+ promises.each { |promise| promise.must_be :pending? }
27
+ end
28
+
29
+ def assert_resolved(*promises)
30
+ promises.each { |promise| promise.wont_be :pending? }
31
+ end
32
+
33
+ def eventually(&block)
34
+ time_limit = Time.now + 2
35
+ loop do
36
+ begin
37
+ yield
38
+ rescue Minitest::Assertion => error
39
+ end
40
+ return if error.nil?
41
+ raise error if Time.now >= time_limit
42
+ sleep 0.05
43
+ end
44
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: a-ruby-promise
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Haagen Michaelsen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A Ruby Promise implementation that attempts to comply with the Promises/A+
84
+ specification and test suite.
85
+ email:
86
+ - tobias.michaelsen@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - lib/a-ruby-promise.rb
92
+ - lib/promise.rb
93
+ - lib/promise/version.rb
94
+ - spec/aplus_spec.rb
95
+ - spec/deferred.rb
96
+ - spec/promise_spec.rb
97
+ - spec/promises_aplus.rb
98
+ - spec/promises_aplus/2_1_2_spec.rb
99
+ - spec/promises_aplus/2_1_3_spec.rb
100
+ - spec/promises_aplus/2_2_1_spec.rb
101
+ - spec/promises_aplus/2_2_2_spec.rb
102
+ - spec/promises_aplus/2_2_3_spec.rb
103
+ - spec/promises_aplus/2_2_4_spec.rb
104
+ - spec/promises_aplus/2_2_5_spec.rb
105
+ - spec/promises_aplus/2_2_6_spec.rb
106
+ - spec/promises_aplus/2_2_7_spec.rb
107
+ - spec/spec_helper.rb
108
+ homepage: https://github.com/tobiashm
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.2.2
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Promises in Ruby
132
+ test_files:
133
+ - spec/aplus_spec.rb
134
+ - spec/deferred.rb
135
+ - spec/promise_spec.rb
136
+ - spec/promises_aplus.rb
137
+ - spec/promises_aplus/2_1_2_spec.rb
138
+ - spec/promises_aplus/2_1_3_spec.rb
139
+ - spec/promises_aplus/2_2_1_spec.rb
140
+ - spec/promises_aplus/2_2_2_spec.rb
141
+ - spec/promises_aplus/2_2_3_spec.rb
142
+ - spec/promises_aplus/2_2_4_spec.rb
143
+ - spec/promises_aplus/2_2_5_spec.rb
144
+ - spec/promises_aplus/2_2_6_spec.rb
145
+ - spec/promises_aplus/2_2_7_spec.rb
146
+ - spec/spec_helper.rb