a-ruby-promise 0.0.1

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.
@@ -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