1<?php
2
3namespace React\Promise\PromiseTest;
4
5use React\Promise;
6use React\Promise\Deferred;
7
8trait RejectTestTrait
9{
10    /**
11     * @return \React\Promise\PromiseAdapter\PromiseAdapterInterface
12     */
13    abstract public function getPromiseTestAdapter(callable $canceller = null);
14
15    /** @test */
16    public function rejectShouldRejectWithAnImmediateValue()
17    {
18        $adapter = $this->getPromiseTestAdapter();
19
20        $mock = $this->createCallableMock();
21        $mock
22            ->expects($this->once())
23            ->method('__invoke')
24            ->with($this->identicalTo(1));
25
26        $adapter->promise()
27            ->then($this->expectCallableNever(), $mock);
28
29        $adapter->reject(1);
30    }
31
32    /** @test */
33    public function rejectShouldRejectWithFulfilledPromise()
34    {
35        $adapter = $this->getPromiseTestAdapter();
36
37        $mock = $this->createCallableMock();
38        $mock
39            ->expects($this->once())
40            ->method('__invoke')
41            ->with($this->identicalTo(1));
42
43        $adapter->promise()
44            ->then($this->expectCallableNever(), $mock);
45
46        $adapter->reject(Promise\resolve(1));
47    }
48
49    /** @test */
50    public function rejectShouldRejectWithRejectedPromise()
51    {
52        $adapter = $this->getPromiseTestAdapter();
53
54        $mock = $this->createCallableMock();
55        $mock
56            ->expects($this->once())
57            ->method('__invoke')
58            ->with($this->identicalTo(1));
59
60        $adapter->promise()
61            ->then($this->expectCallableNever(), $mock);
62
63        $adapter->reject(Promise\reject(1));
64    }
65
66    /** @test */
67    public function rejectShouldForwardReasonWhenCallbackIsNull()
68    {
69        $adapter = $this->getPromiseTestAdapter();
70
71        $mock = $this->createCallableMock();
72        $mock
73            ->expects($this->once())
74            ->method('__invoke')
75            ->with($this->identicalTo(1));
76
77        $adapter->promise()
78            ->then(
79                $this->expectCallableNever()
80            )
81            ->then(
82                $this->expectCallableNever(),
83                $mock
84            );
85
86        $adapter->reject(1);
87    }
88
89    /** @test */
90    public function rejectShouldMakePromiseImmutable()
91    {
92        $adapter = $this->getPromiseTestAdapter();
93
94        $mock = $this->createCallableMock();
95        $mock
96            ->expects($this->once())
97            ->method('__invoke')
98            ->with($this->identicalTo(1));
99
100        $adapter->promise()
101            ->then(null, function ($value) use ($adapter) {
102                $adapter->reject(3);
103
104                return Promise\reject($value);
105            })
106            ->then(
107                $this->expectCallableNever(),
108                $mock
109            );
110
111        $adapter->reject(1);
112        $adapter->reject(2);
113    }
114
115    /** @test */
116    public function notifyShouldInvokeOtherwiseHandler()
117    {
118        $adapter = $this->getPromiseTestAdapter();
119
120        $mock = $this->createCallableMock();
121        $mock
122            ->expects($this->once())
123            ->method('__invoke')
124            ->with($this->identicalTo(1));
125
126        $adapter->promise()
127            ->otherwise($mock);
128
129        $adapter->reject(1);
130    }
131
132    /** @test */
133    public function doneShouldInvokeRejectionHandler()
134    {
135        $adapter = $this->getPromiseTestAdapter();
136
137        $mock = $this->createCallableMock();
138        $mock
139            ->expects($this->once())
140            ->method('__invoke')
141            ->with($this->identicalTo(1));
142
143        $this->assertNull($adapter->promise()->done(null, $mock));
144        $adapter->reject(1);
145    }
146
147    /** @test */
148    public function doneShouldThrowExceptionThrownByRejectionHandler()
149    {
150        $adapter = $this->getPromiseTestAdapter();
151
152        $this->setExpectedException('\Exception', 'UnhandledRejectionException');
153
154        $this->assertNull($adapter->promise()->done(null, function () {
155            throw new \Exception('UnhandledRejectionException');
156        }));
157        $adapter->reject(1);
158    }
159
160    /** @test */
161    public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException()
162    {
163        $adapter = $this->getPromiseTestAdapter();
164
165        $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
166
167        $this->assertNull($adapter->promise()->done());
168        $adapter->reject(1);
169    }
170
171    /** @test */
172    public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects()
173    {
174        $adapter = $this->getPromiseTestAdapter();
175
176        $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
177
178        $this->assertNull($adapter->promise()->done(null, function () {
179            return \React\Promise\reject();
180        }));
181        $adapter->reject(1);
182    }
183
184    /** @test */
185    public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException()
186    {
187        $adapter = $this->getPromiseTestAdapter();
188
189        $this->setExpectedException('\Exception', 'UnhandledRejectionException');
190
191        $this->assertNull($adapter->promise()->done(null, function () {
192            return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
193        }));
194        $adapter->reject(1);
195    }
196
197    /** @test */
198    public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater()
199    {
200        $adapter = $this->getPromiseTestAdapter();
201
202        $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
203
204        $d = new Deferred();
205        $promise = $d->promise();
206
207        $this->assertNull($adapter->promise()->done(null, function () use ($promise) {
208            return $promise;
209        }));
210        $adapter->reject(1);
211        $d->reject(1);
212    }
213
214    /** @test */
215    public function doneShouldThrowExceptionProvidedAsRejectionValue()
216    {
217        $adapter = $this->getPromiseTestAdapter();
218
219        $this->setExpectedException('\Exception', 'UnhandledRejectionException');
220
221        $this->assertNull($adapter->promise()->done());
222        $adapter->reject(new \Exception('UnhandledRejectionException'));
223    }
224
225    /** @test */
226    public function doneShouldThrowWithDeepNestingPromiseChains()
227    {
228        $this->setExpectedException('\Exception', 'UnhandledRejectionException');
229
230        $exception = new \Exception('UnhandledRejectionException');
231
232        $d = new Deferred();
233
234        $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
235            $d = new Deferred();
236            $d->resolve();
237
238            return \React\Promise\resolve($d->promise()->then(function () {}))->then(
239                function () use ($exception) {
240                    throw $exception;
241                }
242            );
243        })));
244
245        $result->done();
246
247        $d->resolve();
248    }
249
250    /** @test */
251    public function doneShouldRecoverWhenRejectionHandlerCatchesException()
252    {
253        $adapter = $this->getPromiseTestAdapter();
254
255        $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
256
257        }));
258        $adapter->reject(new \Exception('UnhandledRejectionException'));
259    }
260
261    /** @test */
262    public function alwaysShouldNotSuppressRejection()
263    {
264        $adapter = $this->getPromiseTestAdapter();
265
266        $exception = new \Exception();
267
268        $mock = $this->createCallableMock();
269        $mock
270            ->expects($this->once())
271            ->method('__invoke')
272            ->with($this->identicalTo($exception));
273
274        $adapter->promise()
275            ->always(function () {})
276            ->then(null, $mock);
277
278        $adapter->reject($exception);
279    }
280
281    /** @test */
282    public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise()
283    {
284        $adapter = $this->getPromiseTestAdapter();
285
286        $exception = new \Exception();
287
288        $mock = $this->createCallableMock();
289        $mock
290            ->expects($this->once())
291            ->method('__invoke')
292            ->with($this->identicalTo($exception));
293
294        $adapter->promise()
295            ->always(function () {
296                return 1;
297            })
298            ->then(null, $mock);
299
300        $adapter->reject($exception);
301    }
302
303    /** @test */
304    public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise()
305    {
306        $adapter = $this->getPromiseTestAdapter();
307
308        $exception = new \Exception();
309
310        $mock = $this->createCallableMock();
311        $mock
312            ->expects($this->once())
313            ->method('__invoke')
314            ->with($this->identicalTo($exception));
315
316        $adapter->promise()
317            ->always(function () {
318                return \React\Promise\resolve(1);
319            })
320            ->then(null, $mock);
321
322        $adapter->reject($exception);
323    }
324
325    /** @test */
326    public function alwaysShouldRejectWhenHandlerThrowsForRejection()
327    {
328        $adapter = $this->getPromiseTestAdapter();
329
330        $exception = new \Exception();
331
332        $mock = $this->createCallableMock();
333        $mock
334            ->expects($this->once())
335            ->method('__invoke')
336            ->with($this->identicalTo($exception));
337
338        $adapter->promise()
339            ->always(function () use ($exception) {
340                throw $exception;
341            })
342            ->then(null, $mock);
343
344        $adapter->reject($exception);
345    }
346
347    /** @test */
348    public function alwaysShouldRejectWhenHandlerRejectsForRejection()
349    {
350        $adapter = $this->getPromiseTestAdapter();
351
352        $exception = new \Exception();
353
354        $mock = $this->createCallableMock();
355        $mock
356            ->expects($this->once())
357            ->method('__invoke')
358            ->with($this->identicalTo($exception));
359
360        $adapter->promise()
361            ->always(function () use ($exception) {
362                return \React\Promise\reject($exception);
363            })
364            ->then(null, $mock);
365
366        $adapter->reject($exception);
367    }
368}
369