Class.Thenable: Specs.
[mootools.git] / Specs / Class / Class.Thenable.js
blob30d635d4d7888159108295114b5276eb1c8b8c5b
1 /*
2 ---
3 name: Class.Thenable
4 requires: ~
5 provides: ~
6 ...
7 */
9 describe('Class.Thenable', function(){
11         var Thenable = Class.Thenable;
13         var Thing = new Class({
15                 Implements: Thenable
17         });
19         var aplusAdapter = {
21                 resolved: Class.Thenable.resolve,
23                 rejected: Class.Thenable.reject,
25                 deferred: function(){
26                         var thenable = new Class.Thenable();
28                         return {
29                                 promise: thenable,
30                                 resolve: thenable.resolve.bind(thenable),
31                                 reject: thenable.reject.bind(thenable)
32                         };
33                 }
35         };
37         function asyncExpectations(done, fn){
38                 var error,
39                         finished = false;
41                 function notFinished(){
42                         finished = false;
43                 }
45                 return function(){
46                         finished = true;
47                         try {
48                                 fn(notFinished);
49                         } catch (thrown){
50                                 error = thrown;
51                         }
52                         if (finished) return done(error);
53                 }
54         }
56         // Tests below are adapted versions of the tests in domenic/promises-unwrapping:
57         // https://github.com/domenic/promises-unwrapping/tree/master/reference-implementation/test
58         it('should call its fulfillment handler when fulfilled', function(done){
59                 var expectations = asyncExpectations(done, function(){
60                         expect(onFulfilled.called).to.equal(true);
61                         expect(onRejected.called).to.equal(false);
62                         expect(onFulfilled.args[0][0]).to.equal(5);
63                 });
65                 var onFulfilled = sinon.spy(expectations);
66                 var onRejected = sinon.spy(expectations);
68                 Thenable.resolve(5).then(onFulfilled, onRejected);
69         });
71         it('should call its rejection handler when rejected', function(done){
72                 var expectations = asyncExpectations(done, function(){
73                         expect(onRejected.called).to.equal(true);
74                         expect(onRejected.args[0][0]).to.equal(12);
75                 });
77                 var onRejected = sinon.spy(expectations);
79                 Thenable.reject(12)['catch'](onRejected);
80         });
82         describe('implemented', function(){
84                 it('should call its fulfillment handler when fulfilled', function(done){
85                         var expectations = asyncExpectations(done, function(){
86                                 expect(onFulfilled.called).to.equal(true);
87                                 expect(onRejected.called).to.equal(false);
88                                 expect(onFulfilled.args[0][0]).to.equal(4);
89                         });
91                         var instance = new Thing();
92                         var onFulfilled = sinon.spy(expectations);
93                         var onRejected = sinon.spy(expectations);
95                         instance.then(onFulfilled, onRejected);
96                         instance.resolve(4);
97                 });
99                 it('should call its rejection handler when rejected', function(done){
100                         var expectations = asyncExpectations(done, function(){
101                                 expect(onRejected.called).to.equal(true);
102                                 expect(onRejected.args[0][0]).to.equal(41);
103                         });
105                         var instance = new Thing();
106                         var onRejected = sinon.spy(expectations);
108                         instance.reject(41);
109                         instance['catch'](onRejected);
110                 });
112         });
114         it('should refuse to be resolved with itself', function(done){
115                 var expectations = asyncExpectations(done, function(){
116                         expect(onFulfilled.called).to.equal(false);
117                         expect(onRejected.called).to.equal(true);
118                         expect(onRejected.args[0][0] instanceof TypeError).to.equal(true);
119                 });
121                 var onFulfilled = sinon.spy(expectations);
122                 var onRejected = sinon.spy(expectations);
124                 var thenable = new Thenable();
125                 thenable.resolve(thenable).then(onFulfilled, onRejected);
126         });
128         it('should call handlers in the order they are queued, when added before resolution', function(done){
129                 var expectations = asyncExpectations(done, function(notFinished){
130                         if (onFulfilled.callCount == 2){
131                                 expect(calls[0]).to.equal(2);
132                                 expect(calls[1]).to.equal(1);
133                         } else {
134                                 notFinished();
135                         }
136                 });
138                 var onFulfilled = sinon.spy(expectations);
139                 var calls = [];
141                 var t1 = new Thenable();
142                 var t2 = new Thenable();
144                 t1.then(function(){
145                         calls.push(1);
146                 }).then(onFulfilled);
148                 t2['catch'](function(){
149                         calls.push(2);
150                 }).then(onFulfilled);
152                 t2.reject();
153                 t1.resolve();
154         });
156         it('should call handlers in the order they are queued, when added after resolution', function(done){
157                 var expectations = asyncExpectations(done, function(notFinished){
158                         if (onFulfilled.callCount == 2){
159                                 expect(calls[0]).to.equal(1);
160                                 expect(calls[1]).to.equal(2);
161                         } else {
162                                 notFinished();
163                         }
164                 });
166                 var onFulfilled = sinon.spy(expectations);
167                 var calls = [];
169                 var t1 = new Thenable();
170                 var t2 = new Thenable();
172                 t2.reject();
173                 t1.resolve();
175                 t1.then(function(){
176                         calls.push(1);
177                 }).then(onFulfilled);
179                 t2['catch'](function(){
180                         calls.push(2);
181                 }).then(onFulfilled);
182         });
184         it('should call handlers in the order they are queued, when added asynchronously after resolution', function(done){
185                 var expectations = asyncExpectations(done, function(notFinished){
186                         if (onFulfilled.callCount == 2){
187                                 expect(calls[0]).to.equal(1);
188                                 expect(calls[1]).to.equal(2);
189                         } else {
190                                 notFinished();
191                         }
192                 });
194                 var onFulfilled = sinon.spy(expectations);
195                 var calls = [];
197                 var t1 = new Thenable();
198                 var t2 = new Thenable();
200                 t2.reject();
201                 t1.resolve();
203                 setTimeout(function(){
204                         t1.then(function(){
205                                 calls.push(1);
206                         }).then(onFulfilled);
208                         t2['catch'](function(){
209                                 calls.push(2);
210                         }).then(onFulfilled);
211                 }, 0);
212         });
214         it('should resolve only once, even when resolved to a thenable that calls its handlers twice', function(done){
215                 var expectations = asyncExpectations(done, function(){
216                         expect(onFulfilled.callCount).to.equal(1);
217                         expect(onFulfilled.args[0][0]).to.equal(1);
218                 });
220                 var onFulfilled = sinon.spy(expectations);
222                 var evilThenable = Thenable.resolve();
223                 evilThenable.then = function(f){
224                         f(1);
225                         f(2);
226                 };
228                 var resolvedToEvil = new Thenable();
229                 resolvedToEvil.resolve(evilThenable);
230                 resolvedToEvil.then(onFulfilled);
231         });
233         it('should correctly store the first resolved value if resolved to a thenable which calls back with different values each time', function(done){
234                 var expectations = asyncExpectations(done, function(){
235                         expect(onFulfilled.called).to.equal(true);
236                 });
238                 var onFulfilled = sinon.spy(expectations);
240                 var thenable = {
241                         i: 0,
242                         then: function(f){
243                                 f(this.i++);
244                         }
245                 };
246                 var t = Thenable.resolve(thenable);
248                 t.then(function(value){
249                         expect(value).to.equal(0);
251                         t.then(function(value){
252                                 expect(value).to.equal(0);
254                                 t.then(function(value){
255                                         expect(value).to.equal(0);
256                                 });
257                         });
258                 });
260                 t.then(onFulfilled);
261         });
263         it('should call then methods with a clean stack when it resolves to a thenable', function(){
264                 var then = sinon.spy();
266                 var thenable = {
267                         then: then
268                 };
270                 Thenable.resolve(thenable);
272                 expect(then.called).to.equal(false);
273         });
275         it('should call then methods with a clean stack when it resolves to an (evil) Thenable', function(){
276                 var then = sinon.spy();
278                 var evilThenable = Thenable.resolve();
279                 evilThenable.then = then;
281                 Thenable.resolve(evilThenable);
282                 expect(then.called).to.equal(false);
283         });
285         it('should reset correctly', function(){
286                 var thenable = new Thenable.resolve(3)
287                 expect(thenable.getThenableState()).to.equal('fulfilled');
289                 thenable.resetThenable();
290                 expect(thenable.getThenableState()).to.equal('pending');
291         });
293         it('should reject before resetting, if still pending', function(done){
294                 var expectations = asyncExpectations(done, function(){
295                         expect(onRejected.called).to.equal(true);
296                         expect(onRejected.args[0][0]).to.equal(7);
297                         expect(thenable.getThenableState()).to.equal('pending');
298                 });
300                 var onRejected = sinon.spy(expectations);
302                 var thenable = new Thenable();
303                 expect(thenable.getThenableState()).to.equal('pending');
305                 thenable.then(null, onRejected);
307                 thenable.resetThenable(7);
308         });