|
17 | 17 | use RuntimeException;
|
18 | 18 | use stdClass;
|
19 | 19 | use Throwable;
|
| 20 | +use TypeError; |
20 | 21 | use ValueError;
|
21 | 22 |
|
22 | 23 | class PromiseTest extends TestCase {
|
@@ -902,4 +903,67 @@ protected function mockCallableThrowsException(
|
902 | 903 | ->will(self::throwException($exception));
|
903 | 904 | return $mock;
|
904 | 905 | }
|
| 906 | + |
| 907 | + /* |
| 908 | + * The functionality tested here is an important distinction in the PHP |
| 909 | + * implemnetation, because of the type safety PHP can enforce compared |
| 910 | + * to the JavaScript implementation. If there's a catch function, but |
| 911 | + * the type of exception does not match the actual rejection, the |
| 912 | + * rejection should be thrown to the main thread instead. |
| 913 | + */ |
| 914 | + public function testCatchRejectionHandlerIsNotCalledByTypeHintedOnRejectedCallback() { |
| 915 | + $exception = new RangeException(); |
| 916 | + $promiseContainer = $this->getTestPromiseContainer(); |
| 917 | + $sut = $promiseContainer->getPromise(); |
| 918 | + |
| 919 | + $shouldNeverBeCalled = self::mockCallable(0); |
| 920 | + self::expectException(RangeException::class); |
| 921 | + |
| 922 | + $sut->catch(function(PromiseException $reason) use($shouldNeverBeCalled) { |
| 923 | + call_user_func($shouldNeverBeCalled, $reason); |
| 924 | + }); |
| 925 | + |
| 926 | + $promiseContainer->reject($exception); |
| 927 | + } |
| 928 | + |
| 929 | + public function testMatchingTypedCatchRejectionHandlerCanHandleInternalTypeErrors() { |
| 930 | + $exception = new RangeException("No, Michael, no!"); |
| 931 | + $promiseContainer = $this->getTestPromiseContainer(); |
| 932 | + $sut = $promiseContainer->getPromise(); |
| 933 | + |
| 934 | + $onRejected1 = self::mockCallable(0); |
| 935 | + $onRejected2 = self::mockCallable(0); |
| 936 | + |
| 937 | + // There is a type error in the matching catch callback. This |
| 938 | + // should bubble out of the chain rather than being seen as |
| 939 | + // missing the RangeException type hint. |
| 940 | + self::expectException(TypeError::class); |
| 941 | + self::expectExceptionMessage("DateTime::__construct(): Argument #1 (\$datetime) must be of type string, Closure given"); |
| 942 | + |
| 943 | + $sut->catch(function(PromiseException $reason1) use($onRejected1) { |
| 944 | + call_user_func($onRejected1, $reason1); |
| 945 | + }) |
| 946 | + ->catch(function(RangeException $reason2) use($onRejected2) { |
| 947 | + new DateTime(fn() => "That was so not right!"); |
| 948 | + call_user_func($onRejected2, $reason2); |
| 949 | + }); |
| 950 | + $promiseContainer->reject($exception); |
| 951 | + } |
| 952 | + |
| 953 | + public function testCatchRejectionHandlerIsCalledByAnotherMatchingTypeHintedOnRejectedCallback() { |
| 954 | + $exception = new RangeException(); |
| 955 | + $promiseContainer = $this->getTestPromiseContainer(); |
| 956 | + $sut = $promiseContainer->getPromise(); |
| 957 | + |
| 958 | + $onRejected1 = self::mockCallable(0); |
| 959 | + $onRejected2 = self::mockCallable(1); |
| 960 | + |
| 961 | + $sut->catch(function(PromiseException $reason) use($onRejected1) { |
| 962 | + call_user_func($onRejected1, $reason); |
| 963 | + })->catch(function(RangeException $reason) use($onRejected2) { |
| 964 | + call_user_func($onRejected2, $reason); |
| 965 | + }); |
| 966 | + |
| 967 | + $promiseContainer->reject($exception); |
| 968 | + } |
905 | 969 | }
|
0 commit comments