pure_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,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'pure_promise'
7
+ spec.version = '0.0.1'
8
+ spec.authors = ['Cameron Martin']
9
+ spec.email = ['cameronmartin123@gmail.com']
10
+ spec.summary = %q{Promises/A+ with a twist}
11
+ spec.description = %q{Promises/A+ with a twist. Resolves some of the inconsistencies and annoyances I've experienced with other promises libraries}
12
+ spec.homepage = ''
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.6'
21
+ spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'rspec', '~> 3.0'
23
+ end
@@ -0,0 +1,26 @@
1
+ describe PurePromise::Callback do
2
+
3
+ let(:return_promise) { PurePromise.new }
4
+
5
+ describe '#call' do
6
+
7
+ it 'delegates to calling proc' do
8
+ promise = PurePromise.fulfill(:value)
9
+ callback = double('callback')
10
+
11
+ expect(callback).to receive(:call).with(:value).and_return(promise)
12
+
13
+ PurePromise::Callback.new(callback, return_promise).call(:value)
14
+ end
15
+
16
+ it 'rejects promise if callback errors' do
17
+ error = RuntimeError.new
18
+ callback = proc { raise error }
19
+
20
+ PurePromise::Callback.new(callback, return_promise).call(:value)
21
+
22
+ expect_rejection(return_promise, with: error)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,118 @@
1
+ describe PurePromise::Coercer do
2
+
3
+ subject { PurePromise::Coercer.new(thenable, PurePromise) }
4
+
5
+ context 'with non-thenable' do
6
+ let(:thenable) { Object.new }
7
+
8
+ describe '.is_thenable?' do
9
+ it 'is false' do
10
+ expect(PurePromise::Coercer.is_thenable?(thenable)).to eq(false)
11
+ end
12
+ end
13
+
14
+ describe '#initialize' do
15
+ it 'raises TypeError' do
16
+ expect { subject }.to raise_error(TypeError, 'Can only coerce a thenable')
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ context 'with PurePromise' do
23
+ describe '#coerce' do
24
+ let(:thenable) { PurePromise.new }
25
+
26
+ it 'returns promise' do
27
+ expect(subject.coerce).to equal(thenable)
28
+ end
29
+
30
+ end
31
+ end
32
+
33
+ context 'with conformant thenable' do
34
+
35
+ let(:thenable) { Thenable::Conformant.new }
36
+
37
+ describe '.is_thenable?' do
38
+ it 'is true' do
39
+ expect(PurePromise::Coercer.is_thenable?(thenable)).to eq(true)
40
+ end
41
+ end
42
+
43
+ describe '#coerce' do
44
+
45
+ it 'returns a promise' do
46
+ expect(subject.coerce).to be_a(PurePromise)
47
+ end
48
+
49
+ it 'fulfills when callback passed to then is called' do
50
+ promise = subject.coerce
51
+
52
+ expect_fulfillment(promise, with: :value) do
53
+ thenable.fulfill(:value)
54
+ end
55
+ end
56
+
57
+ it 'rejects when callback passed to then is called' do
58
+ promise = subject.coerce
59
+
60
+ expect_rejection(promise, with: :value) do
61
+ thenable.reject(:value)
62
+ end
63
+ end
64
+
65
+ it 'ignores subsequent calls of reject callback' do
66
+ promise = subject.coerce
67
+
68
+ thenable.reject(:value)
69
+ thenable.reject(:other_value)
70
+
71
+ expect_rejection(promise, with: :value)
72
+ end
73
+
74
+ it 'ignores subsequent calls of fulfill callback' do
75
+ promise = subject.coerce
76
+
77
+ thenable.fulfill(:value)
78
+ thenable.fulfill(:other_value)
79
+
80
+ expect_fulfillment(promise, with: :value)
81
+ end
82
+
83
+ end
84
+ end
85
+
86
+ context 'with early erroring thenable' do
87
+ let(:thenable) { Thenable::EarlyErroring.new(error) }
88
+
89
+ let(:error) { RuntimeError.new('Some error') }
90
+
91
+ describe '#coerce' do
92
+ it 'rejects promise with error' do
93
+ promise = subject.coerce
94
+
95
+ expect_rejection(promise, with: error)
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ context 'with late erroring thenable' do
102
+ let(:thenable) { Thenable::LateErroring.new(:value, error) }
103
+
104
+ let(:error) { RuntimeError.new }
105
+
106
+ describe '#coerce' do
107
+ it 'rejects promise with error' do
108
+ promise = subject.coerce
109
+
110
+ expect_fulfillment(promise, with: :value)
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+
117
+
118
+ end
@@ -0,0 +1,445 @@
1
+ describe PurePromise do
2
+
3
+ subject { PurePromise.new }
4
+
5
+ let(:fulfill_callback) { double('fulfill_callback').as_null_object }
6
+ let(:reject_callback) { double('reject_callback').as_null_object }
7
+
8
+ describe '#initialize' do
9
+ it 'yields fullfill and reject methods if block given' do
10
+ expect do |b|
11
+ PurePromise.new(&b)
12
+ end.to yield_with_args(
13
+ a_bound_method_of(PurePromise.instance_method(:fulfill)),
14
+ a_bound_method_of(PurePromise.instance_method(:reject))
15
+ )
16
+ end
17
+
18
+ it 'initializes as pending' do
19
+ expect_pending(PurePromise.new)
20
+ end
21
+ end
22
+
23
+ describe '.error' do
24
+
25
+ it 'rejects to a RuntimeError with no arguments' do
26
+ promise = PurePromise.error
27
+
28
+ expect_rejection(promise, with: an_error(RuntimeError).with_backtrace(caller))
29
+ end
30
+
31
+ it 'rejects to a RuntimeError with message with single string argument' do
32
+ promise = PurePromise.error('error message')
33
+
34
+ expect_rejection(promise, with: an_error(RuntimeError, 'error message').with_backtrace(caller))
35
+ end
36
+
37
+ it 'rejects to a specific error when given an Exception object' do
38
+ exception = TypeError.new('error message')
39
+ promise = PurePromise.error(exception)
40
+
41
+ expect_rejection(promise, with: an_error(TypeError, 'error message').with_backtrace(caller))
42
+ end
43
+
44
+ it 'rejects to a specific error when given a exception class and string' do
45
+ promise = PurePromise.error(TypeError, 'error message')
46
+
47
+ expect_rejection(promise, with: an_error(TypeError, 'error message').with_backtrace(caller))
48
+ end
49
+
50
+ it 'sets custom backtrace if given third argument' do
51
+ backtrace = ['something']
52
+ promise = PurePromise.error(TypeError, 'error message', backtrace)
53
+
54
+ expect_rejection(promise, with: an_error(TypeError, 'error message').with_backtrace(backtrace))
55
+ end
56
+
57
+ end
58
+
59
+ # TODO: Test delegation of .fulfill and .reject
60
+
61
+ describe '#fulfill' do
62
+ it 'calls fulfill callback when promise transitions to fulfilled' do
63
+ expect_fulfillment(subject, with: :value) do
64
+ subject.fulfill(:value)
65
+ end
66
+ end
67
+
68
+ it 'is fulfilled' do
69
+ subject.fulfill
70
+ expect_fulfillment(subject)
71
+ end
72
+
73
+ it 'should raise error if fulfill twice' do
74
+ subject.fulfill
75
+ expect { subject.fulfill }.to raise_error(PurePromise::MutationError, 'You can only mutate pending promises')
76
+ end
77
+
78
+ it 'should raise error if fulfilled after being rejected' do
79
+ subject.reject
80
+ expect { subject.fulfill }.to raise_error(PurePromise::MutationError, 'You can only mutate pending promises')
81
+ end
82
+
83
+ it 'returns self' do
84
+ expect(subject.fulfill).to eq(subject)
85
+ end
86
+ end
87
+
88
+ describe '#reject' do
89
+ it 'calls reject callback when promise transitions to rejected' do
90
+ expect_rejection(subject, with: :value) do
91
+ subject.reject(:value)
92
+ end
93
+ end
94
+
95
+ it 'is rejected' do
96
+ subject.reject
97
+ expect_rejection(subject)
98
+ end
99
+
100
+ it 'should raise error if rejected twice' do
101
+ subject.reject
102
+ expect { subject.reject }.to raise_error(PurePromise::MutationError, 'You can only mutate pending promises')
103
+ end
104
+
105
+ it 'should raise error if rejected after being fulfilled' do
106
+ subject.fulfill
107
+ expect { subject.reject }.to raise_error(PurePromise::MutationError, 'You can only mutate pending promises')
108
+ end
109
+
110
+ it 'returns self' do
111
+ expect(subject.reject).to eq(subject)
112
+ end
113
+ end
114
+
115
+ describe '#resolve' do
116
+
117
+ it 'raises TypeError if argument is same as self' do
118
+ expect { subject.resolve(subject) }.to raise_error(TypeError, 'Promise cannot be resolved to itself')
119
+ end
120
+
121
+ it 'raises TypeError if argument is not a promise' do
122
+ expect { subject.resolve(Object.new) }.to raise_error(TypeError, 'Argument is not a promise')
123
+ end
124
+
125
+ context 'when argument is a promise' do
126
+ let(:argument) { PurePromise.new }
127
+
128
+ it 'returns self when argument is pending' do
129
+ return_value = subject.resolve(argument)
130
+
131
+ expect(return_value).to eq(subject)
132
+ end
133
+
134
+ it 'fulfills subject if argument is fulfilled' do
135
+ argument.fulfill(:value)
136
+ subject.resolve(argument)
137
+
138
+ expect_fulfillment(subject, with: :value)
139
+ end
140
+
141
+ it 'rejects subject if argument is rejected' do
142
+ argument.reject(:value)
143
+ subject.resolve(argument)
144
+
145
+ expect_rejection(subject, with: :value)
146
+ end
147
+
148
+ it 'fulfills subject when argument is fulfilled' do
149
+ subject.resolve(argument)
150
+ argument.fulfill(:value)
151
+
152
+ expect_fulfillment(subject, with: :value)
153
+ end
154
+
155
+ it 'rejects subject when argument is rejected' do
156
+ subject.resolve(argument)
157
+ argument.reject(:value)
158
+
159
+ expect_rejection(subject, with: :value)
160
+ end
161
+ end
162
+
163
+ context 'when argument is a thenable' do
164
+ it 'fulfills when callback passed to then is called' do
165
+ thenable = Thenable::Conformant.new
166
+
167
+ subject.resolve(thenable)
168
+
169
+ expect_fulfillment(subject, with: :value) do
170
+ thenable.fulfill(:value)
171
+ end
172
+ end
173
+
174
+ it 'rejects when callback passed to then is called' do
175
+ thenable = Thenable::Conformant.new
176
+
177
+ subject.resolve(thenable)
178
+
179
+ expect_rejection(subject, with: :value) do
180
+ thenable.reject(:value)
181
+ end
182
+ end
183
+ end
184
+
185
+ end
186
+
187
+ describe '#resolve_into' do
188
+ let(:argument) { PurePromise.new }
189
+
190
+ it 'returns self' do
191
+ return_value = subject.resolve_into(argument)
192
+
193
+ expect(return_value).to equal(subject)
194
+ end
195
+
196
+ it 'raises TypeError if argument is not a PurePromise' do
197
+ expect { subject.resolve_into(Object.new) }.to raise_error(TypeError, 'Argument must be of same type as self')
198
+ end
199
+
200
+ it 'fulfills promise if self is fulfilled' do
201
+ subject.fulfill(:value)
202
+
203
+ expect(argument).to receive(:fulfill).with(:value)
204
+ subject.resolve_into(argument)
205
+ end
206
+
207
+ it 'rejects promise if self is rejected' do
208
+ subject.reject(:value)
209
+
210
+ expect(argument).to receive(:reject).with(:value)
211
+ subject.resolve_into(argument)
212
+ end
213
+
214
+ it 'fulfills promise when self is fulfilled' do
215
+ subject.resolve_into(argument)
216
+
217
+ expect_fulfillment(argument, with: :value) do
218
+ subject.fulfill(:value)
219
+ end
220
+ end
221
+
222
+ it 'rejects promise when self is rejected' do
223
+ subject.resolve_into(argument)
224
+
225
+ expect_rejection(argument, with: :value) do
226
+ subject.reject(:value)
227
+ end
228
+ end
229
+ end
230
+
231
+ describe '#then' do
232
+
233
+ it 'is a promise' do
234
+ return_promise = subject.then(fulfill_callback, reject_callback)
235
+ expect(return_promise).to be_an_instance_of(subject.class)
236
+ end
237
+
238
+ it 'executes callbacks in order' do
239
+ callbacks = 2.times.map do
240
+ double('fulfill_callback').as_null_object
241
+ end.each do |callback|
242
+ subject.then(callback)
243
+ end
244
+
245
+ callbacks.each do |callback|
246
+ expect(callback).to receive(:call).and_return(PurePromise.fulfill).ordered
247
+ end
248
+
249
+ subject.fulfill
250
+ end
251
+
252
+ # TODO: Find a better way of testing this
253
+ it 'calls defer if fulfilled' do
254
+ subject.fulfill
255
+ expect(subject).to receive(:defer)
256
+ subject.then
257
+ end
258
+
259
+ it 'calls defer if rejected' do
260
+ subject.reject
261
+ expect(subject).to receive(:defer)
262
+ subject.then
263
+ end
264
+
265
+ it 'calls defer when fulfilled' do
266
+ subject.then
267
+ expect(subject).to receive(:defer)
268
+ subject.fulfill
269
+ end
270
+
271
+ it 'calls defer when rejected' do
272
+ subject.then
273
+ expect(subject).to receive(:defer)
274
+ subject.reject
275
+ end
276
+
277
+ # REVIEW: Consider moving context 'if/when subject is fulfilled/rejected' into this level
278
+
279
+ context 'with no callbacks' do
280
+
281
+ before(:each) { @return_promise = subject.then }
282
+
283
+ it 'returns a promise that fulfills when subject fulfills' do
284
+ subject.fulfill(:value)
285
+
286
+ expect_fulfillment(@return_promise, with: :value)
287
+ end
288
+
289
+ it 'returns a promise that rejects when subject rejects' do
290
+ subject.reject(:value)
291
+
292
+ expect_rejection(@return_promise, with: :value)
293
+ end
294
+
295
+ end
296
+
297
+ context 'with fulfill callback' do
298
+
299
+ it 'allows registering fulfill callback by passing a block' do
300
+ return_promise = subject.then do
301
+ PurePromise.fulfill(:value)
302
+ end
303
+
304
+ subject.fulfill
305
+
306
+ expect_fulfillment(return_promise, with: :value)
307
+ end
308
+
309
+ context 'when callback is registered while pending' do
310
+
311
+ it 'fullfills to the value that the return promise of the callback fullfills to' do
312
+ return_promise = subject.then(proc {
313
+ PurePromise.fulfill(:value)
314
+ })
315
+
316
+ subject.fulfill
317
+
318
+ expect_fulfillment(return_promise, with: :value)
319
+
320
+ end
321
+
322
+ it 'rejects to the value that the return promise of the callback rejects to' do
323
+ return_promise = subject.then(proc {
324
+ PurePromise.reject(:value)
325
+ })
326
+ subject.fulfill
327
+
328
+ expect_rejection(return_promise, with: :value)
329
+ end
330
+
331
+ end
332
+
333
+ context 'when callback is registered after fulfillment' do
334
+ before(:each) { subject.fulfill }
335
+
336
+ it 'fullfills to the value that the return promise of the callback fullfills to' do
337
+ return_promise = subject.then(proc {
338
+ PurePromise.fulfill(:value)
339
+ })
340
+
341
+ expect_fulfillment(return_promise, with: :value)
342
+ end
343
+
344
+ it 'rejects to the value that the return promise of the callback rejects to' do
345
+ return_promise = subject.then(proc {
346
+ PurePromise.reject(:value)
347
+ })
348
+
349
+ expect_rejection(return_promise, with: :value)
350
+ end
351
+ end
352
+
353
+ end
354
+
355
+ context 'with reject callback' do
356
+
357
+ context 'when callback is registered while pending' do
358
+
359
+ it 'fullfills to the value that the return promise of the callback fullfills to' do
360
+ return_promise = subject.then(proc{}, proc {
361
+ PurePromise.fulfill(:value)
362
+ })
363
+
364
+ subject.reject
365
+
366
+ expect_fulfillment(return_promise, with: :value)
367
+
368
+ end
369
+
370
+ it 'rejects to the value that the return promise of the callback rejects to' do
371
+ return_promise = subject.then(proc{}, proc {
372
+ PurePromise.reject(:value)
373
+ })
374
+ subject.reject
375
+
376
+ expect_rejection(return_promise, with: :value)
377
+ end
378
+
379
+ end
380
+
381
+ context 'when callback is registered after rejection' do
382
+ before(:each) { subject.reject }
383
+
384
+ it 'fullfills to the value that the return promise of the callback fullfills to' do
385
+ return_promise = subject.then(proc{}, proc {
386
+ PurePromise.fulfill(:value)
387
+ })
388
+
389
+ expect_fulfillment(return_promise, with: :value)
390
+ end
391
+
392
+ it 'rejects to the value that the return promise of the callback rejects to' do
393
+ return_promise = subject.then(proc{}, proc {
394
+ PurePromise.reject(:value)
395
+ })
396
+
397
+ expect_rejection(return_promise, with: :value)
398
+ end
399
+ end
400
+ end
401
+
402
+ end
403
+
404
+ describe '#catch' do
405
+
406
+ it 'is called on rejected promise' do
407
+ subject.reject(:value)
408
+
409
+ callback = proc { PurePromise.fulfill }
410
+
411
+ expect(callback).to receive(:call).with(:value).and_call_original
412
+
413
+ subject.catch(&callback)
414
+ end
415
+
416
+ it 'returns a promise that fulfills with original' do
417
+ promise = subject.catch { PurePromise.fulfill }
418
+
419
+ expect_fulfillment(promise, with: :value) do
420
+ subject.fulfill(:value)
421
+ end
422
+ end
423
+
424
+ context 'with no callbacks' do
425
+
426
+ before(:each) { @return_promise = subject.catch }
427
+
428
+ it 'returns a promise that fulfills when subject fulfills' do
429
+ subject.fulfill(:value)
430
+
431
+ expect_fulfillment(@return_promise, with: :value)
432
+ end
433
+
434
+ it 'returns a promise that rejects when subject rejects' do
435
+ subject.reject(:value)
436
+
437
+ expect_rejection(@return_promise, with: :value)
438
+ end
439
+
440
+ end
441
+
442
+
443
+ end
444
+
445
+ end