1<?php 2 3namespace React\Promise\PromiseTest; 4 5use React\Promise\Deferred; 6use React\Promise\UnhandledRejectionException; 7 8trait PromiseRejectedTestTrait 9{ 10 /** 11 * @return \React\Promise\PromiseAdapter\PromiseAdapterInterface 12 */ 13 abstract public function getPromiseTestAdapter(callable $canceller = null); 14 15 /** @test */ 16 public function rejectedPromiseShouldBeImmutable() 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->reject(1); 27 $adapter->reject(2); 28 29 $adapter->promise() 30 ->then( 31 $this->expectCallableNever(), 32 $mock 33 ); 34 } 35 36 /** @test */ 37 public function rejectedPromiseShouldInvokeNewlyAddedCallback() 38 { 39 $adapter = $this->getPromiseTestAdapter(); 40 41 $adapter->reject(1); 42 43 $mock = $this->createCallableMock(); 44 $mock 45 ->expects($this->once()) 46 ->method('__invoke') 47 ->with($this->identicalTo(1)); 48 49 $adapter->promise() 50 ->then($this->expectCallableNever(), $mock); 51 } 52 53 /** @test */ 54 public function shouldForwardUndefinedRejectionValue() 55 { 56 $adapter = $this->getPromiseTestAdapter(); 57 58 $mock = $this->createCallableMock(); 59 $mock 60 ->expects($this->once()) 61 ->method('__invoke') 62 ->with(null); 63 64 $adapter->reject(1); 65 $adapter->promise() 66 ->then( 67 $this->expectCallableNever(), 68 function () { 69 // Presence of rejection handler is enough to switch back 70 // to resolve mode, even though it returns undefined. 71 // The ONLY way to propagate a rejection is to re-throw or 72 // return a rejected promise; 73 } 74 ) 75 ->then( 76 $mock, 77 $this->expectCallableNever() 78 ); 79 } 80 81 /** @test */ 82 public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate() 83 { 84 $adapter = $this->getPromiseTestAdapter(); 85 86 $mock = $this->createCallableMock(); 87 $mock 88 ->expects($this->once()) 89 ->method('__invoke') 90 ->with($this->identicalTo(2)); 91 92 $adapter->reject(1); 93 $adapter->promise() 94 ->then( 95 $this->expectCallableNever(), 96 function ($val) { 97 return $val + 1; 98 } 99 ) 100 ->then( 101 $mock, 102 $this->expectCallableNever() 103 ); 104 } 105 106 /** @test */ 107 public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution() 108 { 109 $adapter = $this->getPromiseTestAdapter(); 110 111 $mock = $this->createCallableMock(); 112 $mock 113 ->expects($this->once()) 114 ->method('__invoke') 115 ->with($this->identicalTo(2)); 116 117 $adapter->reject(1); 118 $adapter->promise() 119 ->then( 120 $this->expectCallableNever(), 121 function ($val) { 122 return \React\Promise\resolve($val + 1); 123 } 124 ) 125 ->then( 126 $mock, 127 $this->expectCallableNever() 128 ); 129 } 130 131 /** @test */ 132 public function shouldPropagateRejectionsWhenErrbackThrows() 133 { 134 $adapter = $this->getPromiseTestAdapter(); 135 136 $exception = new \Exception(); 137 138 $mock = $this->createCallableMock(); 139 $mock 140 ->expects($this->once()) 141 ->method('__invoke') 142 ->will($this->throwException($exception)); 143 144 $mock2 = $this->createCallableMock(); 145 $mock2 146 ->expects($this->once()) 147 ->method('__invoke') 148 ->with($this->identicalTo($exception)); 149 150 $adapter->reject(1); 151 $adapter->promise() 152 ->then( 153 $this->expectCallableNever(), 154 $mock 155 ) 156 ->then( 157 $this->expectCallableNever(), 158 $mock2 159 ); 160 } 161 162 /** @test */ 163 public function shouldPropagateRejectionsWhenErrbackReturnsARejection() 164 { 165 $adapter = $this->getPromiseTestAdapter(); 166 167 $mock = $this->createCallableMock(); 168 $mock 169 ->expects($this->once()) 170 ->method('__invoke') 171 ->with($this->identicalTo(2)); 172 173 $adapter->reject(1); 174 $adapter->promise() 175 ->then( 176 $this->expectCallableNever(), 177 function ($val) { 178 return \React\Promise\reject($val + 1); 179 } 180 ) 181 ->then( 182 $this->expectCallableNever(), 183 $mock 184 ); 185 } 186 187 /** @test */ 188 public function doneShouldInvokeRejectionHandlerForRejectedPromise() 189 { 190 $adapter = $this->getPromiseTestAdapter(); 191 192 $mock = $this->createCallableMock(); 193 $mock 194 ->expects($this->once()) 195 ->method('__invoke') 196 ->with($this->identicalTo(1)); 197 198 $adapter->reject(1); 199 $this->assertNull($adapter->promise()->done(null, $mock)); 200 } 201 202 /** @test */ 203 public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise() 204 { 205 $adapter = $this->getPromiseTestAdapter(); 206 207 $this->setExpectedException('\Exception', 'UnhandledRejectionException'); 208 209 $adapter->reject(1); 210 $this->assertNull($adapter->promise()->done(null, function () { 211 throw new \Exception('UnhandledRejectionException'); 212 })); 213 } 214 215 /** @test */ 216 public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise() 217 { 218 $adapter = $this->getPromiseTestAdapter(); 219 220 $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); 221 222 $adapter->reject(1); 223 $this->assertNull($adapter->promise()->done()); 224 } 225 226 /** @test */ 227 public function unhandledRejectionExceptionThrownByDoneHoldsRejectionValue() 228 { 229 $adapter = $this->getPromiseTestAdapter(); 230 231 $expected = new \stdClass(); 232 233 $adapter->reject($expected); 234 235 try { 236 $adapter->promise()->done(); 237 } catch (UnhandledRejectionException $e) { 238 $this->assertSame($expected, $e->getReason()); 239 return; 240 } 241 242 $this->fail(); 243 } 244 245 /** @test */ 246 public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise() 247 { 248 $adapter = $this->getPromiseTestAdapter(); 249 250 $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); 251 252 $adapter->reject(1); 253 $this->assertNull($adapter->promise()->done(null, function () { 254 return \React\Promise\reject(); 255 })); 256 } 257 258 /** @test */ 259 public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise() 260 { 261 $adapter = $this->getPromiseTestAdapter(); 262 263 $this->setExpectedException('\Exception', 'UnhandledRejectionException'); 264 265 $adapter->reject(1); 266 $this->assertNull($adapter->promise()->done(null, function () { 267 return \React\Promise\reject(new \Exception('UnhandledRejectionException')); 268 })); 269 } 270 271 /** @test */ 272 public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise() 273 { 274 $adapter = $this->getPromiseTestAdapter(); 275 276 $this->setExpectedException('\Exception', 'UnhandledRejectionException'); 277 278 $adapter->reject(new \Exception('UnhandledRejectionException')); 279 $this->assertNull($adapter->promise()->done()); 280 } 281 282 /** @test */ 283 public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise() 284 { 285 $this->setExpectedException('\Exception', 'UnhandledRejectionException'); 286 287 $exception = new \Exception('UnhandledRejectionException'); 288 289 $d = new Deferred(); 290 $d->resolve(); 291 292 $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) { 293 $d = new Deferred(); 294 $d->resolve(); 295 296 return \React\Promise\resolve($d->promise()->then(function () {}))->then( 297 function () use ($exception) { 298 throw $exception; 299 } 300 ); 301 }))); 302 303 $result->done(); 304 } 305 306 /** @test */ 307 public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise() 308 { 309 $adapter = $this->getPromiseTestAdapter(); 310 311 $adapter->reject(new \Exception('UnhandledRejectionException')); 312 $this->assertNull($adapter->promise()->done(null, function (\Exception $e) { 313 314 })); 315 } 316 317 /** @test */ 318 public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise() 319 { 320 $adapter = $this->getPromiseTestAdapter(); 321 322 $mock = $this->createCallableMock(); 323 $mock 324 ->expects($this->once()) 325 ->method('__invoke') 326 ->with($this->identicalTo(1)); 327 328 $adapter->reject(1); 329 $adapter->promise()->otherwise($mock); 330 } 331 332 /** @test */ 333 public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise() 334 { 335 $adapter = $this->getPromiseTestAdapter(); 336 337 $exception = new \Exception(); 338 339 $mock = $this->createCallableMock(); 340 $mock 341 ->expects($this->once()) 342 ->method('__invoke') 343 ->with($this->identicalTo($exception)); 344 345 $adapter->reject($exception); 346 $adapter->promise() 347 ->otherwise(function ($reason) use ($mock) { 348 $mock($reason); 349 }); 350 } 351 352 /** @test */ 353 public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise() 354 { 355 $adapter = $this->getPromiseTestAdapter(); 356 357 $exception = new \InvalidArgumentException(); 358 359 $mock = $this->createCallableMock(); 360 $mock 361 ->expects($this->once()) 362 ->method('__invoke') 363 ->with($this->identicalTo($exception)); 364 365 $adapter->reject($exception); 366 $adapter->promise() 367 ->otherwise(function (\InvalidArgumentException $reason) use ($mock) { 368 $mock($reason); 369 }); 370 } 371 372 /** @test */ 373 public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise() 374 { 375 $adapter = $this->getPromiseTestAdapter(); 376 377 $exception = new \Exception(); 378 379 $mock = $this->expectCallableNever(); 380 381 $adapter->reject($exception); 382 $adapter->promise() 383 ->otherwise(function (\InvalidArgumentException $reason) use ($mock) { 384 $mock($reason); 385 }); 386 } 387 388 /** @test */ 389 public function alwaysShouldNotSuppressRejectionForRejectedPromise() 390 { 391 $adapter = $this->getPromiseTestAdapter(); 392 393 $exception = new \Exception(); 394 395 $mock = $this->createCallableMock(); 396 $mock 397 ->expects($this->once()) 398 ->method('__invoke') 399 ->with($this->identicalTo($exception)); 400 401 $adapter->reject($exception); 402 $adapter->promise() 403 ->always(function () {}) 404 ->then(null, $mock); 405 } 406 407 /** @test */ 408 public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise() 409 { 410 $adapter = $this->getPromiseTestAdapter(); 411 412 $exception = new \Exception(); 413 414 $mock = $this->createCallableMock(); 415 $mock 416 ->expects($this->once()) 417 ->method('__invoke') 418 ->with($this->identicalTo($exception)); 419 420 $adapter->reject($exception); 421 $adapter->promise() 422 ->always(function () { 423 return 1; 424 }) 425 ->then(null, $mock); 426 } 427 428 /** @test */ 429 public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise() 430 { 431 $adapter = $this->getPromiseTestAdapter(); 432 433 $exception = new \Exception(); 434 435 $mock = $this->createCallableMock(); 436 $mock 437 ->expects($this->once()) 438 ->method('__invoke') 439 ->with($this->identicalTo($exception)); 440 441 $adapter->reject($exception); 442 $adapter->promise() 443 ->always(function () { 444 return \React\Promise\resolve(1); 445 }) 446 ->then(null, $mock); 447 } 448 449 /** @test */ 450 public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise() 451 { 452 $adapter = $this->getPromiseTestAdapter(); 453 454 $exception1 = new \Exception(); 455 $exception2 = new \Exception(); 456 457 $mock = $this->createCallableMock(); 458 $mock 459 ->expects($this->once()) 460 ->method('__invoke') 461 ->with($this->identicalTo($exception2)); 462 463 $adapter->reject($exception1); 464 $adapter->promise() 465 ->always(function () use ($exception2) { 466 throw $exception2; 467 }) 468 ->then(null, $mock); 469 } 470 471 /** @test */ 472 public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise() 473 { 474 $adapter = $this->getPromiseTestAdapter(); 475 476 $exception1 = new \Exception(); 477 $exception2 = new \Exception(); 478 479 $mock = $this->createCallableMock(); 480 $mock 481 ->expects($this->once()) 482 ->method('__invoke') 483 ->with($this->identicalTo($exception2)); 484 485 $adapter->reject($exception1); 486 $adapter->promise() 487 ->always(function () use ($exception2) { 488 return \React\Promise\reject($exception2); 489 }) 490 ->then(null, $mock); 491 } 492 493 /** @test */ 494 public function cancelShouldReturnNullForRejectedPromise() 495 { 496 $adapter = $this->getPromiseTestAdapter(); 497 498 $adapter->reject(); 499 500 $this->assertNull($adapter->promise()->cancel()); 501 } 502 503 /** @test */ 504 public function cancelShouldHaveNoEffectForRejectedPromise() 505 { 506 $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); 507 508 $adapter->reject(); 509 510 $adapter->promise()->cancel(); 511 } 512} 513