diff --git a/.travis.yml b/.travis.yml index 6b9b05b0..b0b5c89e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: php php: - - 5.3.3 - - 5.3 - 5.4 - 5.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a1dec3b..35c0aa2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ CHANGELOG ========= +* 2.0.0 (xxxx-xx-xx) + + New major release. The goal was to streamline the API and to make it more + compliant with other promise libraries and especially with the new upcoming + [ES6 promises specification](https://github.com/domenic/promises-unwrapping/). + + * Add standalone Promise class. + * Add new React\Promise\race() function. + * BC break: Bump minimum PHP version to PHP 5.4. + * BC break: Remove ResolverInterface and PromiseInterface from Deferred. + * BC break: Change signature of PromiseInterface. + * BC break: Remove When and Util classes and move static methods to functions. + * BC break: FulfilledPromise and RejectedPromise now throw an exception when + initialized with a promise instead of a value/reason. + * BC break: React\Promise\Deferred::resolve() and React\Promise\Deferred::reject() + no longer return a promise. + * 1.0.4 (2013-04-03) * Trigger PHP errors when invalid callback is passed. diff --git a/README.md b/README.md index 44a30666..820e6a83 100644 --- a/README.md +++ b/README.md @@ -13,24 +13,31 @@ Table of Contents 2. [Concepts](#concepts) * [Deferred](#deferred) * [Promise](#promise) - * [Resolver](#resolver) 3. [API](#api) * [Deferred](#deferred-1) + * [Deferred::promise()](#deferredpromise) + * [Deferred::resolve()](#deferredresolve) + * [Deferred::reject()](#deferredreject) + * [Deferred::progress()](#deferredprogress) + * [PromiseInterface](#promiseinterface) + * [PromiseInterface::then()](#promiseinterfacethen) * [Promise](#promise-1) - * [Resolver](#resolver-1) - * [When](#when) - * [When::all()](#whenall) - * [When::any()](#whenany) - * [When::some()](#whensome) - * [When::map()](#whenmap) - * [When::reduce()](#whenreduce) - * [When::resolve()](#whenresolve) - * [When::reject()](#whenreject) - * [When::lazy()](#whenlazy) - * [Promisor](#promisor) + * [FulfilledPromise](#fulfilledpromise) + * [RejectedPromise](#rejectedpromise) + * [LazyPromise](#lazypromise) + * [Functions](#functions) + * [resolve()](#resolve) + * [reject()](#reject) + * [all()](#all) + * [race()](#race) + * [any()](#any) + * [some()](#some) + * [map()](#map) + * [reduce()](#reduce) + * [PromisorInterface](#promisorinterface) 4. [Examples](#examples) * [How to use Deferred](#how-to-use-deferred) - * [How Promise forwarding works](#how-promise-forwarding-works) + * [How promise forwarding works](#how-promise-forwarding-works) * [Resolution forwarding](#resolution-forwarding) * [Rejection forwarding](#rejection-forwarding) * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding) @@ -44,10 +51,10 @@ Introduction React/Promise is a library implementing [CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. -It also provides several other useful Promise-related concepts, such as joining -multiple Promises and mapping and reducing collections of Promises. +It also provides several other useful promise-related concepts, such as joining +multiple promises and mapping and reducing collections of promises. -If you've never heard about Promises before, +If you've never heard about promises before, [read this first](https://gist.github.com/3889970). Concepts @@ -61,254 +68,322 @@ that executes asynchronously and completes at some point in the future. ### Promise -While a Deferred represents the computation itself, a **Promise** represents -the result of that computation. Thus, each Deferred has a Promise that acts as +While a deferred represents the computation itself, a **Promise** represents +the result of that computation. Thus, each deferred has a promise that acts as a placeholder for its actual result. -### Resolver - -A **Resolver** can resolve, reject or trigger progress notifications on behalf -of a Deferred without knowing any details about consumers. - -Sometimes it can be useful to hand out a resolver and allow another -(possibly untrusted) party to provide the resolution value for a Promise. - API --- ### Deferred A deferred represents an operation whose resolution is pending. It has separate -Promise and Resolver parts that can be safely given out to separate groups of -consumers and producers to allow safe, one-way communication. +promise and resolver parts. ``` php $deferred = new React\Promise\Deferred(); -$promise = $deferred->promise(); -$resolver = $deferred->resolver(); +$promise = $deferred->promise(); + +$deferred->resolve(mixed $value = null); +$deferred->reject(mixed $reason = null); +$deferred->progress(mixed $update = null); ``` -Although a Deferred has the full Promise + Resolver API, this should be used for -convenience only by the creator of the deferred. Only the Promise and Resolver -should be given to consumers and producers. +The `promise` method returns the promise of the deferred. + +The `resolve` and `reject` methods control the state of the deferred. + +The `progress` method is for progress notification. + +#### Deferred::promise() ``` php -$deferred = new React\Promise\Deferred(); +$promise = $deferred->promise(); +``` -$deferred->then(callable $fulfilledHandler = null, callable $errorHandler = null, callable $progressHandler = null); -$deferred->resolve(mixed $promiseOrValue = null); -$deferred->reject(mixed $reason = null); -$deferred->progress(mixed $update = null); +Returns the promise of the deferred, which you can hand out to others while +keeping the authority to modify its state to yourself. + +#### Deferred::resolve() + +``` php +$deferred->resolve(mixed $value = null); ``` -### Promise +Resolves the promise returned by `promise()`. All consumers are notified by +having `$onFulfilled` (which they registered via `$promise->then()`) called with +`$value`. -The Promise represents the eventual outcome, which is either fulfillment -(success) and an associated value, or rejection (failure) and an associated -reason. The Promise provides mechanisms for arranging to call a function on its -value or reason, and produces a new Promise for the result. +If `$value` itself is a promise, the promise will transition to the state of +this promise once it is resolved. + +#### Deferred::reject() + +``` php +$resolver->reject(mixed $reason = null); +``` + +Rejects the promise returned by `promise()`, signalling that the deferred's +computation failed. +All consumers are notified by having `$onRejected` (which they registered via +`$promise->then()`) called with `$reason`. + +If `$reason` itself is a promise, the promise will be rejected with the outcome +of this promise regardless whether it fulfills or rejects. -A Promise has a single method `then()` which registers new fulfilled, error and -progress handlers with this Promise (all parameters are optional): +#### Deferred::progress() ``` php -$newPromise = $promise->then(callable $fulfilledHandler = null, callable $errorHandler = null, callable $progressHandler = null); +$resolver->progress(mixed $update = null); ``` - * `$fulfilledHandler` will be invoked once the Promise is fulfilled and passed +Triggers progress notifications, to indicate to consumers that the computation +is making progress toward its result. + +All consumers are notified by having `$onProgress` (which they registered via +`$promise->then()`) called with `$update`. + +### PromiseInterface + +The promise interface provides the common interface for all promise +implementations. + +A promise represents an eventual outcome, which is either fulfillment (success) +and an associated value, or rejection (failure) and an associated reason. + +Once in the fulfilled or rejected state, a promise becomes immutable. +Neither its state nor its result (or error) can be modified. + +#### PromiseInterface::then() + +``` php +$newPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null); +``` + +The `then()` method registers new fulfilled, rejection and progress handlers +with this promise (all parameters are optional): + + * `$onFulfilled` will be invoked once the promise is fulfilled and passed the result as the first argument. - * `$errorHandler` will be invoked once the Promise is rejected and passed the + * `$onRejected` will be invoked once the promise is rejected and passed the reason as the first argument. - * `$progressHandler` will be invoked whenever the producer of the Promise + * `$onProgress` will be invoked whenever the producer of the promise triggers progress notifications and passed a single argument (whatever it wants) to indicate progress. -Returns a new Promise that will fulfill with the return value of either -`$fulfilledHandler` or `$errorHandler`, whichever is called, or will reject with +It returns a new promise that will fulfill with the return value of either +`$onFulfilled` or `$onRejected`, whichever is called, or will reject with the thrown exception if either throws. -Once in the fulfilled or rejected state, a Promise becomes immutable. -Neither its state nor its result (or error) can be modified. - -A Promise makes the following guarantees about handlers registered in +A promise makes the following guarantees about handlers registered in the same call to `then()`: - 1. Only one of `$fulfilledHandler` or `$errorHandler` will be called, + 1. Only one of `$onFulfilled` or `$onRejected` will be called, never both. - 2. `$fulfilledHandler` and `$errorHandler` will never be called more + 2. `$onFulfilled` and `$onRejected` will never be called more than once. - 3. `$progressHandler` may be called multiple times. + 3. `$onProgress` may be called multiple times. -#### See also +#### Implementations + +* [Promise](#promise-1) +* [FulfilledPromise](#fulfilledpromise) +* [RejectedPromise](#rejectedpromise) +* [LazyPromise](#lazypromise) -* [When::resolve()](#whenresolve) - Creating a resolved Promise -* [When::reject()](#whenreject) - Creating a rejected Promise +#### See also -### Resolver +* [resolve()](#resolve) - Creating a resolved promise +* [reject()](#reject) - Creating a rejected promise -The Resolver represents the responsibility of fulfilling, rejecting and -notifying the associated Promise. +### Promise -A Resolver has 3 methods: `resolve()`, `reject()` and `progress()`: +Creates a promise whose state is controlled by the functions passed to +`$resolver`. ``` php -$resolver->resolve(mixed $result = null); +$resolver = function (callable $resolve, callable $reject, callable $progress) { + // Do some work, possibly asynchronously, and then + // resolve or reject. You can notify of progress events + // along the way if you want/need. + + $resolve($awesomeResult); + // or $resolve($anotherPromise); + // or $reject($nastyError); + // or $progress($progressNotification); +}; + +$promise = new React\Promise\Promise($resolver); ``` -Resolves a Deferred. All consumers are notified by having their -`$fulfilledHandler` (which they registered via `$promise->then()`) called with -`$result`. +The promise constructor receives a resolver function which will be called +with 3 arguments: -If `$result` itself is a promise, the Deferred will transition to the state of -this promise once it is resolved. + * `$resolve($value)` - Primary function that seals the fate of the + returned promise. Accepts either a non-promise value, or another promise. + When called with a non-promise value, fulfills promise with that value. + When called with another promise, e.g. `$resolve($otherPromise)`, promise's + fate will be equivalent to that of `$otherPromise`. + * `$reject($reason)` - Function that rejects the promise. + * `$progress($update)` - Function that issues progress events for the promise. -``` php -$resolver->reject(mixed $reason = null); +If the resolver throws an exception, the promise will be rejected with that +thrown exception as the rejection reason. + + +### FulfilledPromise + +Creates a already fulfilled promise. + +```php +$promise = React\Promise\FulfilledPromise($value); ``` -Rejects a Deferred, signalling that the Deferred's computation failed. -All consumers are notified by having their `$errorHandler` (which they -registered via `$promise->then()`) called with `$reason`. +Note, that `$value` **cannot** be a promise. It's recommended to use +[resolve()](#resolve) for creating resolved promises. -If `$reason` itself is a promise, the Deferred will be rejected with the outcome -of this promise regardless whether it fulfills or rejects. +### RejectedPromise -``` php -$resolver->progress(mixed $update = null); +Creates a already rejected promise. + +```php +$promise = React\Promise\RejectedPromise($reason); ``` -Triggers progress notifications, to indicate to consumers that the computation -is making progress toward its result. +Note, that `$reason` **cannot** be a promise. It's recommended to use +[reject()](#reject) for creating rejected promises. -All consumers are notified by having their `$progressHandler` (which they -registered via `$promise->then()`) called with `$update`. +### LazyPromise -### When +Creates a promise which will be lazily initialized by `$factory` once a consumer +calls the `then()` method. -The `React\Promise\When` class provides useful methods for creating, joining, -mapping and reducing collections of Promises. +```php +$factory = function () { + $deferred = new React\Promise\Deferred(); -#### When::all() + // Do some heavy stuff here and resolve the deferred once completed -``` php -$promise = React\Promise\When::all(array|React\Promise\PromiseInterface $promisesOrValues, callable $fulfilledHandler = null, callable $errorHandler = null, callable $progressHandler = null); + return $deferred->promise(); +}; + +$promise = React\Promise\LazyPromise($factory); + +// $factory will only be executed once we call then() +$promise->then(function ($value) { +}); ``` -Returns a Promise that will resolve only once all the items in -`$promisesOrValues` have resolved. The resolution value of the returned Promise -will be an array containing the resolution values of each of the items in -`$promisesOrValues`. +### Functions + +Useful functions for creating, joining, mapping and reducing collections of +promises. -#### When::any() +#### resolve() ``` php -$promise = React\Promise\When::any(array|React\Promise\PromiseInterface $promisesOrValues, callable $fulfilledHandler = null, callable $errorHandler = null, callable $progressHandler = null); +$promise = React\Promise\resolve(mixed $promiseOrValue); ``` -Returns a Promise that will resolve when any one of the items in -`$promisesOrValues` resolves. The resolution value of the returned Promise -will be the resolution value of the triggering item. +Creates a resolved promise for the supplied `$promiseOrValue`. -The returned Promise will only reject if *all* items in `$promisesOrValues` are -rejected. The rejection value will be an array of all rejection reasons. +If `$promiseOrValue` is a value, it will be the resolution value of the +returned promise. + +If `$promiseOrValue` is a promise, it will simply be returned. -#### When::some() +#### reject() ``` php -$promise = React\Promise\When::some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany, callable $fulfilledHandler = null, callable $errorHandler = null, callable $progressHandler = null); +$promise = React\Promise\reject(mixed $promiseOrValue); ``` -Returns a Promise that will resolve when `$howMany` of the supplied items in -`$promisesOrValues` resolve. The resolution value of the returned Promise -will be an array of length `$howMany` containing the resolution values of the -triggering items. +Creates a rejected promise for the supplied `$promiseOrValue`. -The returned Promise will reject if it becomes impossible for `$howMany` items -to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items -reject). The rejection value will be an array of -`(count($promisesOrValues) - $howMany) + 1` rejection reasons. +If `$promiseOrValue` is a value, it will be the rejection value of the +returned promise. -#### When::map() +If `$promiseOrValue` is a promise, its completion value will be the rejected +value of the returned promise. + +This can be useful in situations where you need to reject a promise without +throwing an exception. For example, it allows you to propagate a rejection with +the value of another promise. + +#### all() ``` php -$promise = React\Promise\When::map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc); +$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues); ``` -Traditional map function, similar to `array_map()`, but allows input to contain -Promises and/or values, and `$mapFunc` may return either a value or a Promise. - -The map function receives each item as argument, where item is a fully resolved -value of a Promise or value in `$promisesOrValues`. +Returns a promise that will resolve only once all the items in +`$promisesOrValues` have resolved. The resolution value of the returned promise +will be an array containing the resolution values of each of the items in +`$promisesOrValues`. -#### When::reduce() +#### race() ``` php -$promise = React\Promise\When::reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null); +$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues); ``` -Traditional reduce function, similar to `array_reduce()`, but input may contain -Promises and/or values, and `$reduceFunc` may return either a value or a -Promise, *and* `$initialValue` may be a Promise or a value for the starting -value. +Initiates a competitive race that allows one winner. Returns a promise which is +resolved in the same way the first settled promise resolves. -#### When::resolve() +#### any() ``` php -$promise = React\Promise\When::resolve(mixed $promiseOrValue); +$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues); ``` -Creates a resolved Promise for the supplied `$promiseOrValue`. - -If `$promiseOrValue` is a value, it will be the resolution value of the -returned Promise. +Returns a promise that will resolve when any one of the items in +`$promisesOrValues` resolves. The resolution value of the returned promise +will be the resolution value of the triggering item. -If `$promiseOrValue` is a Promise, it will simply be returned. +The returned promise will only reject if *all* items in `$promisesOrValues` are +rejected. The rejection value will be an array of all rejection reasons. -#### When::reject() +#### some() ``` php -$promise = React\Promise\When::reject(mixed $promiseOrValue); +$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany); ``` -Creates a rejected Promise for the supplied `$promiseOrValue`. - -If `$promiseOrValue` is a value, it will be the rejection value of the -returned Promise. - -If `$promiseOrValue` is a Promise, its completion value will be the rejected -value of the returned Promise. +Returns a promise that will resolve when `$howMany` of the supplied items in +`$promisesOrValues` resolve. The resolution value of the returned promise +will be an array of length `$howMany` containing the resolution values of the +triggering items. -This can be useful in situations where you need to reject a Promise without -throwing an exception. For example, it allows you to propagate a rejection with -the value of another Promise. +The returned promise will reject if it becomes impossible for `$howMany` items +to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items +reject). The rejection value will be an array of +`(count($promisesOrValues) - $howMany) + 1` rejection reasons. -#### When::lazy() +#### map() ``` php -$promise = React\Promise\When::lazy(callable $factory); +$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc); ``` -Creates a Promise which will be lazily initialized by `$factory` once a consumer -calls the `then()` method. - -```php -$factory = function () { - $deferred = new React\Promise\Deferred(); - - // Do some heavy stuff here and resolve the Deferred once completed +Traditional map function, similar to `array_map()`, but allows input to contain +promises and/or values, and `$mapFunc` may return either a value or a promise. - return $deferred->promise(); -}; +The map function receives each item as argument, where item is a fully resolved +value of a promise or value in `$promisesOrValues`. -$promise = React\Promise\When::lazy($factory); +#### reduce() -// $factory will only be executed once we call then() -$promise->then(function ($value) { -}); +``` php +$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null); ``` -### Promisor +Traditional reduce function, similar to `array_reduce()`, but input may contain +promises and/or values, and `$reduceFunc` may return either a value or a +promise, *and* `$initialValue` may be a promise or a value for the starting +value. + +### PromisorInterface The `React\Promise\PromisorInterface` provides a common interface for objects that provide a promise. `React\Promise\Deferred` implements it, but since it @@ -324,18 +399,18 @@ function getAwesomeResultPromise() { $deferred = new React\Promise\Deferred(); - // Pass only the Resolver, to provide the resolution value for the Promise + // Pass only the Resolver, to provide the resolution value for the promise computeAwesomeResultAsynchronously($deferred->resolver()); - // Return only the Promise, so that the caller cannot - // resolve, reject, or otherwise muck with the original Deferred. + // Return only the promise, so that the caller cannot + // resolve, reject, or otherwise muck with the original deferred. return $deferred->promise(); } getAwesomeResultPromise() ->then( - function ($result) { - // Deferred resolved, do something with $result + function ($value) { + // Deferred resolved, do something with $value }, function ($reason) { // Deferred rejected, do something with $reason @@ -346,21 +421,21 @@ getAwesomeResultPromise() ); ``` -### How Promise forwarding works +### How promise forwarding works A few simple examples to show how the mechanics of Promises/A forwarding works. -These examples are contrived, of course, and in real usage, Promise chains will +These examples are contrived, of course, and in real usage, promise chains will typically be spread across several function calls, or even several levels of your application architecture. #### Resolution forwarding -Resolved Promises forward resolution values to the next Promise. -The first Promise, `$deferred->promise()`, will resolve with the value passed +Resolved promises forward resolution values to the next promise. +The first promise, `$deferred->promise()`, will resolve with the value passed to `$deferred->resolve()` below. -Each call to `then()` returns a new Promise that will resolve with the return -value of the previous handler. This creates a Promise "pipeline". +Each call to `then()` returns a new promise that will resolve with the return +value of the previous handler. This creates a promise "pipeline". ``` php $deferred = new React\Promise\Deferred(); @@ -368,7 +443,7 @@ $deferred = new React\Promise\Deferred(); $deferred->promise() ->then(function ($x) { // $x will be the value passed to $deferred->resolve() below - // and returns a *new Promise* for $x + 1 + // and returns a *new promise* for $x + 1 return $x + 1; }) ->then(function ($x) { @@ -395,12 +470,12 @@ $deferred->resolve(1); // Prints "Resolve 4" #### Rejection forwarding -Rejected Promises behave similarly, and also work similarly to try/catch: +Rejected promises behave similarly, and also work similarly to try/catch: When you catch an exception, you must rethrow for it to propagate. -Similarly, when you handle a rejected Promise, to propagate the rejection, -"rethrow" it by either returning a rejected Promise, or actually throwing -(since Promise translates thrown exceptions into rejections) +Similarly, when you handle a rejected promise, to propagate the rejection, +"rethrow" it by either returning a rejected promise, or actually throwing +(since promise translates thrown exceptions into rejections) ``` php $deferred = new React\Promise\Deferred(); @@ -415,7 +490,7 @@ $deferred->promise() }) ->then(null, function (\Exception $x) { // Can also propagate by returning another rejection - return React\Promise\When::reject((integer) $x->getMessage() + 1); + return React\Promise\reject((integer) $x->getMessage() + 1); }) ->then(null, function ($x) { echo 'Reject ' . $x; // 3 diff --git a/composer.json b/composer.json index 9f12fc1b..ee83717a 100644 --- a/composer.json +++ b/composer.json @@ -6,16 +6,17 @@ {"name": "Jan Sorgalla", "email": "jsorgalla@googlemail.com"} ], "require": { - "php": ">=5.3.3" + "php": ">=5.4.0" }, "autoload": { "psr-0": { "React\\Promise": "src/" - } + }, + "files": ["src/React/Promise/functions.php"] }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0f98aea7..0eebb58b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,7 +4,7 @@ backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" - convertNoticesToExceptions="false" + convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" diff --git a/src/React/Promise/Deferred.php b/src/React/Promise/Deferred.php index ef084aee..1a7e4895 100644 --- a/src/React/Promise/Deferred.php +++ b/src/React/Promise/Deferred.php @@ -2,104 +2,44 @@ namespace React\Promise; -class Deferred implements PromiseInterface, ResolverInterface, PromisorInterface +class Deferred implements PromisorInterface { - private $completed; private $promise; - private $resolver; - private $handlers = array(); - private $progressHandlers = array(); + private $resolveCallback; + private $rejectCallback; + private $progressCallback; - public function then($fulfilledHandler = null, $errorHandler = null, $progressHandler = null) + public function promise() { - if (null !== $this->completed) { - return $this->completed->then($fulfilledHandler, $errorHandler, $progressHandler); - } - - $deferred = new static(); - - if (is_callable($progressHandler)) { - $progHandler = function ($update) use ($deferred, $progressHandler) { - try { - $deferred->progress(call_user_func($progressHandler, $update)); - } catch (\Exception $e) { - $deferred->progress($e); - } - }; - } else { - if (null !== $progressHandler) { - trigger_error('Invalid $progressHandler argument passed to then(), must be null or callable.', E_USER_NOTICE); - } - - $progHandler = array($deferred, 'progress'); + if (null === $this->promise) { + $this->promise = new Promise(function ($resolve, $reject, $progress) { + $this->resolveCallback = $resolve; + $this->rejectCallback = $reject; + $this->progressCallback = $progress; + }); } - $this->handlers[] = function ($promise) use ($fulfilledHandler, $errorHandler, $deferred, $progHandler) { - $promise - ->then($fulfilledHandler, $errorHandler) - ->then( - array($deferred, 'resolve'), - array($deferred, 'reject'), - $progHandler - ); - }; - - $this->progressHandlers[] = $progHandler; - - return $deferred->promise(); + return $this->promise; } - public function resolve($result = null) + public function resolve($value = null) { - if (null !== $this->completed) { - return Util::promiseFor($result); - } - - $this->completed = Util::promiseFor($result); - - $this->processQueue($this->handlers, $this->completed); + $this->promise(); - $this->progressHandlers = $this->handlers = array(); - - return $this->completed; + call_user_func($this->resolveCallback, $value); } public function reject($reason = null) { - return $this->resolve(Util::rejectedPromiseFor($reason)); - } - - public function progress($update = null) - { - if (null !== $this->completed) { - return; - } - - $this->processQueue($this->progressHandlers, $update); - } - - public function promise() - { - if (null === $this->promise) { - $this->promise = new DeferredPromise($this); - } + $this->promise(); - return $this->promise; + call_user_func($this->rejectCallback, $reason); } - public function resolver() + public function progress($update = null) { - if (null === $this->resolver) { - $this->resolver = new DeferredResolver($this); - } + $this->promise(); - return $this->resolver; - } - - protected function processQueue($queue, $value) - { - foreach ($queue as $handler) { - call_user_func($handler, $value); - } + call_user_func($this->progressCallback, $update); } } diff --git a/src/React/Promise/DeferredPromise.php b/src/React/Promise/DeferredPromise.php deleted file mode 100644 index 6ca2f4fc..00000000 --- a/src/React/Promise/DeferredPromise.php +++ /dev/null @@ -1,18 +0,0 @@ -deferred = $deferred; - } - - public function then($fulfilledHandler = null, $errorHandler = null, $progressHandler = null) - { - return $this->deferred->then($fulfilledHandler, $errorHandler, $progressHandler); - } -} diff --git a/src/React/Promise/DeferredResolver.php b/src/React/Promise/DeferredResolver.php deleted file mode 100644 index aa6b5b6a..00000000 --- a/src/React/Promise/DeferredResolver.php +++ /dev/null @@ -1,28 +0,0 @@ -deferred = $deferred; - } - - public function resolve($result = null) - { - return $this->deferred->resolve($result); - } - - public function reject($reason = null) - { - return $this->deferred->reject($reason); - } - - public function progress($update = null) - { - return $this->deferred->progress($update); - } -} diff --git a/src/React/Promise/FulfilledPromise.php b/src/React/Promise/FulfilledPromise.php index 0d5aa144..44e1a798 100644 --- a/src/React/Promise/FulfilledPromise.php +++ b/src/React/Promise/FulfilledPromise.php @@ -4,25 +4,27 @@ class FulfilledPromise implements PromiseInterface { - private $result; + private $value; - public function __construct($result = null) + public function __construct($value = null) { - $this->result = $result; + if ($value instanceof PromiseInterface) { + throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.'); + } + + $this->value = $value; } - public function then($fulfilledHandler = null, $errorHandler = null, $progressHandler = null) + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) { try { - $result = $this->result; + $value = $this->value; - if (is_callable($fulfilledHandler)) { - $result = call_user_func($fulfilledHandler, $result); - } elseif (null !== $fulfilledHandler) { - trigger_error('Invalid $fulfilledHandler argument passed to then(), must be null or callable.', E_USER_NOTICE); + if (null !== $onFulfilled) { + $value = $onFulfilled($value); } - return Util::promiseFor($result); + return resolve($value); } catch (\Exception $exception) { return new RejectedPromise($exception); } diff --git a/src/React/Promise/LazyPromise.php b/src/React/Promise/LazyPromise.php index 308f7e95..82acce19 100644 --- a/src/React/Promise/LazyPromise.php +++ b/src/React/Promise/LazyPromise.php @@ -7,21 +7,21 @@ class LazyPromise implements PromiseInterface private $factory; private $promise; - public function __construct($factory) + public function __construct(callable $factory) { $this->factory = $factory; } - public function then($fulfilledHandler = null, $errorHandler = null, $progressHandler = null) + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) { if (null === $this->promise) { try { - $this->promise = Util::promiseFor(call_user_func($this->factory)); + $this->promise = resolve(call_user_func($this->factory)); } catch (\Exception $exception) { $this->promise = new RejectedPromise($exception); } } - return $this->promise->then($fulfilledHandler, $errorHandler, $progressHandler); + return $this->promise->then($onFulfilled, $onRejected, $onProgress); } } diff --git a/src/React/Promise/Promise.php b/src/React/Promise/Promise.php new file mode 100644 index 00000000..061d960c --- /dev/null +++ b/src/React/Promise/Promise.php @@ -0,0 +1,104 @@ +resolve($value); + }, + function ($reason = null) { + $this->reject($reason); + }, + function ($update = null) { + $this->progress($update); + } + ); + } catch (\Exception $e) { + $this->reject($e); + } + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null !== $this->result) { + return $this->result->then($onFulfilled, $onRejected, $onProgress); + } + + return new static($this->resolver($onFulfilled, $onRejected, $onProgress)); + } + + private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return function ($resolve, $reject, $progress) use ($onFulfilled, $onRejected, $onProgress) { + if ($onProgress) { + $progressHandler = function ($update) use ($progress, $onProgress) { + try { + $progress($onProgress($update)); + } catch (\Exception $e) { + $progress($e); + } + }; + } else { + $progressHandler = $progress; + } + + $this->handlers[] = function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) { + $promise + ->then($onFulfilled, $onRejected) + ->then($resolve, $reject, $progressHandler); + }; + + $this->progressHandlers[] = $progressHandler; + }; + } + + private function resolve($value = null) + { + if (null !== $this->result) { + return; + } + + $this->settle(resolve($value)); + } + + private function reject($reason = null) + { + if (null !== $this->result) { + return; + } + + $this->settle(reject($reason)); + } + + private function progress($update = null) + { + if (null !== $this->result) { + return; + } + + foreach ($this->progressHandlers as $handler) { + $handler($update); + } + } + + private function settle(PromiseInterface $result) + { + foreach ($this->handlers as $handler) { + $handler($result); + } + + $this->progressHandlers = $this->handlers = []; + + $this->result = $result; + } +} diff --git a/src/React/Promise/PromiseInterface.php b/src/React/Promise/PromiseInterface.php index 9d23be2e..422f9f16 100644 --- a/src/React/Promise/PromiseInterface.php +++ b/src/React/Promise/PromiseInterface.php @@ -4,5 +4,5 @@ interface PromiseInterface { - public function then($fulfilledHandler = null, $errorHandler = null, $progressHandler = null); + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null); } diff --git a/src/React/Promise/RejectedPromise.php b/src/React/Promise/RejectedPromise.php index ef507a53..994d7d8c 100644 --- a/src/React/Promise/RejectedPromise.php +++ b/src/React/Promise/RejectedPromise.php @@ -8,21 +8,21 @@ class RejectedPromise implements PromiseInterface public function __construct($reason = null) { + if ($reason instanceof PromiseInterface) { + throw new \InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.'); + } + $this->reason = $reason; } - public function then($fulfilledHandler = null, $errorHandler = null, $progressHandler = null) + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) { try { - if (!is_callable($errorHandler)) { - if (null !== $errorHandler) { - trigger_error('Invalid $errorHandler argument passed to then(), must be null or callable.', E_USER_NOTICE); - } - + if (null === $onRejected) { return new RejectedPromise($this->reason); } - return Util::promiseFor(call_user_func($errorHandler, $this->reason)); + return resolve($onRejected($this->reason)); } catch (\Exception $exception) { return new RejectedPromise($exception); } diff --git a/src/React/Promise/ResolverInterface.php b/src/React/Promise/ResolverInterface.php deleted file mode 100644 index 32d735e2..00000000 --- a/src/React/Promise/ResolverInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -then(function ($value) { - return new RejectedPromise($value); - }); - } - - return new RejectedPromise($promiseOrValue); - } -} diff --git a/src/React/Promise/When.php b/src/React/Promise/When.php deleted file mode 100644 index 321f0bc0..00000000 --- a/src/React/Promise/When.php +++ /dev/null @@ -1,168 +0,0 @@ -then($fulfilledHandler, $errorHandler, $progressHandler); - } - - public static function any($promisesOrValues, $fulfilledHandler = null, $errorHandler = null, $progressHandler = null) - { - $unwrapSingleResult = function ($val) use ($fulfilledHandler) { - $val = array_shift($val); - - return $fulfilledHandler ? $fulfilledHandler($val) : $val; - }; - - return static::some($promisesOrValues, 1, $unwrapSingleResult, $errorHandler, $progressHandler); - } - - public static function some($promisesOrValues, $howMany, $fulfilledHandler = null, $errorHandler = null, $progressHandler = null) - { - return When::resolve($promisesOrValues)->then(function ($array) use ($howMany, $fulfilledHandler, $errorHandler, $progressHandler) { - if (!is_array($array)) { - $array = array(); - } - - $len = count($array); - $toResolve = max(0, min($howMany, $len)); - $values = array(); - $deferred = new Deferred(); - - if (!$toResolve) { - $deferred->resolve($values); - } else { - $toReject = ($len - $toResolve) + 1; - $reasons = array(); - - $progress = array($deferred, 'progress'); - - $fulfillOne = function ($val, $i) use (&$values, &$toResolve, $deferred) { - $values[$i] = $val; - - if (0 === --$toResolve) { - $deferred->resolve($values); - - return true; - } - }; - - $rejectOne = function ($reason, $i) use (&$reasons, &$toReject, $deferred) { - $reasons[$i] = $reason; - - if (0 === --$toReject) { - $deferred->reject($reasons); - - return true; - } - }; - - foreach ($array as $i => $promiseOrValue) { - $fulfiller = function ($val) use ($i, &$fulfillOne, &$rejectOne) { - $reset = $fulfillOne($val, $i); - - if (true === $reset) { - $fulfillOne = $rejectOne = function () {}; - } - }; - - $rejecter = function ($val) use ($i, &$fulfillOne, &$rejectOne) { - $reset = $rejectOne($val, $i); - - if (true === $reset) { - $fulfillOne = $rejectOne = function () {}; - } - }; - - When::resolve($promiseOrValue)->then($fulfiller, $rejecter, $progress); - } - } - - return $deferred->then($fulfilledHandler, $errorHandler, $progressHandler); - }); - } - - public static function map($promisesOrValues, $mapFunc) - { - return When::resolve($promisesOrValues)->then(function ($array) use ($mapFunc) { - if (!is_array($array)) { - $array = array(); - } - - $toResolve = count($array); - $results = array(); - $deferred = new Deferred(); - - if (!$toResolve) { - $deferred->resolve($results); - } else { - $resolve = function ($item, $i) use ($mapFunc, &$results, &$toResolve, $deferred) { - When::resolve($item) - ->then($mapFunc) - ->then( - function ($mapped) use (&$results, $i, &$toResolve, $deferred) { - $results[$i] = $mapped; - - if (0 === --$toResolve) { - $deferred->resolve($results); - } - }, - array($deferred, 'reject') - ); - }; - - foreach ($array as $i => $item) { - $resolve($item, $i); - } - } - - return $deferred->promise(); - }); - } - - public static function reduce($promisesOrValues, $reduceFunc , $initialValue = null) - { - return When::resolve($promisesOrValues)->then(function ($array) use ($reduceFunc, $initialValue) { - if (!is_array($array)) { - $array = array(); - } - - $total = count($array); - $i = 0; - - // Wrap the supplied $reduceFunc with one that handles promises and then - // delegates to the supplied. - $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $total, &$i) { - return When::resolve($current)->then(function ($c) use ($reduceFunc, $total, &$i, $val) { - return When::resolve($val)->then(function ($value) use ($reduceFunc, $total, &$i, $c) { - return call_user_func($reduceFunc, $c, $value, $i++, $total); - }); - }); - }; - - return array_reduce($array, $wrappedReduceFunc, $initialValue); - }); - } -} diff --git a/src/React/Promise/functions.php b/src/React/Promise/functions.php new file mode 100644 index 00000000..e4b8aa57 --- /dev/null +++ b/src/React/Promise/functions.php @@ -0,0 +1,164 @@ +then(function ($value) { + return new RejectedPromise($value); + }); + } + + return new RejectedPromise($promiseOrValue); +} + +function all($promisesOrValues) +{ + return map($promisesOrValues, function ($val) { + return $val; + }); +} + +function race($promisesOrValues) +{ + return resolve($promisesOrValues) + ->then(function ($array) { + if (!is_array($array) || !$array) { + return resolve(); + } + + return new Promise(function ($resolve, $reject, $progress) use ($array) { + foreach ($array as $promiseOrValue) { + resolve($promiseOrValue) + ->then($resolve, $reject, $progress); + } + }); + }); +} + +function any($promisesOrValues) +{ + return some($promisesOrValues, 1) + ->then(function ($val) { + return array_shift($val); + }); +} + +function some($promisesOrValues, $howMany) +{ + return resolve($promisesOrValues) + ->then(function ($array) use ($howMany) { + if (!is_array($array) || !$array || $howMany < 1) { + return resolve([]); + } + + return new Promise(function ($resolve, $reject, $progress) use ($array, $howMany) { + $len = count($array); + $toResolve = min($howMany, $len); + $toReject = ($len - $toResolve) + 1; + $values = []; + $reasons = []; + + foreach ($array as $i => $promiseOrValue) { + $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $values[$i] = $val; + + if (0 === --$toResolve) { + $resolve($values); + } + }; + + $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $reasons[$i] = $reason; + + if (0 === --$toReject) { + $reject($reasons); + } + }; + + resolve($promiseOrValue) + ->then($fulfiller, $rejecter, $progress); + } + }); + }); +} + +function map($promisesOrValues, callable $mapFunc) +{ + return resolve($promisesOrValues) + ->then(function ($array) use ($mapFunc) { + if (!is_array($array) || !$array) { + return resolve([]); + } + + return new Promise(function ($resolve, $reject, $progress) use ($array, $mapFunc) { + $toResolve = count($array); + $values = []; + + foreach ($array as $i => $promiseOrValue) { + resolve($promiseOrValue) + ->then($mapFunc) + ->then( + function ($mapped) use ($i, &$values, &$toResolve, $resolve) { + if ($toResolve < 1) { + return; + } + + $values[$i] = $mapped; + + if (0 === --$toResolve) { + $resolve($values); + } + }, + $reject, + $progress + ); + } + }); + }); +} + +function reduce($promisesOrValues, callable $reduceFunc , $initialValue = null) +{ + return resolve($promisesOrValues) + ->then(function ($array) use ($reduceFunc, $initialValue) { + if (!is_array($array)) { + $array = []; + } + + $total = count($array); + $i = 0; + + // Wrap the supplied $reduceFunc with one that handles promises and then + // delegates to the supplied. + $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $total, &$i) { + return resolve($current) + ->then(function ($c) use ($reduceFunc, $total, &$i, $val) { + return resolve($val) + ->then(function ($value) use ($reduceFunc, $total, &$i, $c) { + return $reduceFunc($c, $value, $i++, $total); + }); + }); + }; + + return array_reduce($array, $wrappedReduceFunc, $initialValue); + }); +} diff --git a/tests/React/Promise/DeferredPromiseTest.php b/tests/React/Promise/DeferredPromiseTest.php deleted file mode 100644 index 123f65c7..00000000 --- a/tests/React/Promise/DeferredPromiseTest.php +++ /dev/null @@ -1,23 +0,0 @@ -getMock('React\\Promise\\Deferred'); - $mock - ->expects($this->once()) - ->method('then') - ->with(1, 2, 3); - - $p = new DeferredPromise($mock); - $p->then(1, 2, 3); - } -} diff --git a/tests/React/Promise/DeferredRejectTest.php b/tests/React/Promise/DeferredRejectTest.php deleted file mode 100644 index f10d9beb..00000000 --- a/tests/React/Promise/DeferredRejectTest.php +++ /dev/null @@ -1,160 +0,0 @@ -createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($this->expectCallableNever(), $mock); - - $d - ->resolver() - ->reject(1); - } - - /** @test */ - public function shouldRejectWithFulfilledPromise() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($this->expectCallableNever(), $mock); - - $d - ->resolver() - ->reject(new FulfilledPromise(1)); - } - - /** @test */ - public function shouldRejectWithRejectedPromise() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($this->expectCallableNever(), $mock); - - $d - ->resolver() - ->reject(new RejectedPromise(1)); - } - - /** @test */ - public function shouldReturnAPromiseForTheRejectionValue() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->resolver() - ->reject(1) - ->then($this->expectCallableNever(), $mock); - } - - /** @test */ - public function shouldInvokeNewlyAddedErrbackWhenAlreadyRejected() - { - $d = new Deferred(); - $d - ->resolver() - ->reject(1); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($this->expectCallableNever(), $mock); - } - - /** @test */ - public function shouldForwardReasonWhenCallbackIsNull() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d = new Deferred(); - $d - ->then( - $this->expectCallableNever() - ) - ->then( - $this->expectCallableNever(), - $mock - ); - - $d->reject(1); - } - - /** - * @test - * @dataProvider invalidCallbackDataProvider - **/ - public function shouldIgnoreNonFunctionsAndTriggerPhpNotice($var) - { - $errorCollector = new ErrorCollector(); - $errorCollector->register(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d = new Deferred(); - $d - ->then( - null, - $var - ) - ->then( - $this->expectCallableNever(), - $mock - ); - - $d->reject(1); - - $errorCollector->assertCollectedError('Invalid $errorHandler argument passed to then(), must be null or callable.', E_USER_NOTICE); - $errorCollector->unregister(); - } -} diff --git a/tests/React/Promise/DeferredResolveTest.php b/tests/React/Promise/DeferredResolveTest.php deleted file mode 100644 index 3ee7027c..00000000 --- a/tests/React/Promise/DeferredResolveTest.php +++ /dev/null @@ -1,196 +0,0 @@ -createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($mock); - - $d - ->resolver() - ->resolve(1); - } - - /** @test */ - public function shouldResolveWithPromisedValue() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($mock); - - $d - ->resolver() - ->resolve(new FulfilledPromise(1)); - } - - /** @test */ - public function shouldRejectWhenResolvedWithRejectedPromise() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($this->expectCallableNever(), $mock); - - $d - ->resolver() - ->resolve(new RejectedPromise(1)); - } - - /** @test */ - public function shouldReturnAPromiseForTheResolutionValue() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->resolver() - ->resolve(1) - ->then($mock); - } - - /** @test */ - public function shouldReturnAPromiseForAPromisedResolutionValue() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->resolver() - ->resolve(When::resolve(1)) - ->then($mock); - } - - /** @test */ - public function shouldReturnAPromiseForAPromisedRejectionValue() - { - $d = new Deferred(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - // Both the returned promise, and the deferred's own promise should - // be rejected with the same value - $d - ->resolver() - ->resolve(When::reject(1)) - ->then($this->expectCallableNever(), $mock); - } - - /** @test */ - public function shouldInvokeNewlyAddedCallbackWhenAlreadyResolved() - { - $d = new Deferred(); - $d - ->resolver() - ->resolve(1); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d - ->promise() - ->then($mock, $this->expectCallableNever()); - } - - /** @test */ - public function shouldForwardValueWhenCallbackIsNull() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d = new Deferred(); - $d - ->then( - null, - $this->expectCallableNever() - ) - ->then( - $mock, - $this->expectCallableNever() - ); - - $d->resolve(1); - } - - /** - * @test - * @dataProvider invalidCallbackDataProvider - **/ - public function shouldIgnoreNonFunctionsAndTriggerPhpNotice($var) - { - $errorCollector = new ErrorCollector(); - $errorCollector->register(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $d = new Deferred(); - $d - ->then( - $var - ) - ->then( - $mock, - $this->expectCallableNever() - ); - - $d->resolve(1); - - $errorCollector->assertCollectedError('Invalid $fulfilledHandler argument passed to then(), must be null or callable.', E_USER_NOTICE); - $errorCollector->unregister(); - } -} diff --git a/tests/React/Promise/DeferredResolverTest.php b/tests/React/Promise/DeferredResolverTest.php deleted file mode 100644 index 349bdc2c..00000000 --- a/tests/React/Promise/DeferredResolverTest.php +++ /dev/null @@ -1,33 +0,0 @@ -getMock('React\\Promise\\Deferred'); - $mock - ->expects($this->once()) - ->method('resolve') - ->with(1); - $mock - ->expects($this->once()) - ->method('reject') - ->with(1); - $mock - ->expects($this->once()) - ->method('progress') - ->with(1); - - $p = new DeferredResolver($mock); - $p->resolve(1); - $p->reject(1); - $p->progress(1); - } -} diff --git a/tests/React/Promise/DeferredTest.php b/tests/React/Promise/DeferredTest.php index 51eb2520..e66aceb9 100644 --- a/tests/React/Promise/DeferredTest.php +++ b/tests/React/Promise/DeferredTest.php @@ -2,86 +2,19 @@ namespace React\Promise; -/** - * @group Deferred - */ class DeferredTest extends TestCase { - /** @test */ - public function shouldReturnAPromiseForPassedInResolutionValueWhenAlreadyResolved() - { - $d = new Deferred(); - $d->resolve(1); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $d->resolve(2)->then($mock); - } - - /** @test */ - public function shouldReturnAPromiseForPassedInRejectionValueWhenAlreadyResolved() - { - $d = new Deferred(); - $d->resolve(1); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $d->reject(2)->then($this->expectCallableNever(), $mock); - } - - /** @test */ - public function shouldReturnSilentlyOnProgressWhenAlreadyResolved() - { - $d = new Deferred(); - $d->resolve(1); - - $this->assertNull($d->progress()); - } - - /** @test */ - public function shouldReturnAPromiseForPassedInResolutionValueWhenAlreadyRejected() - { - $d = new Deferred(); - $d->reject(1); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $d->resolve(2)->then($mock); - } - - /** @test */ - public function shouldReturnAPromiseForPassedInRejectionValueWhenAlreadyRejected() - { - $d = new Deferred(); - $d->reject(1); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $d->reject(2)->then($this->expectCallableNever(), $mock); - } + use PromiseTest\FullTestTrait; - /** @test */ - public function shouldReturnSilentlyOnProgressWhenAlreadyRejected() + public function getPromiseTestAdapter() { $d = new Deferred(); - $d->reject(1); - $this->assertNull($d->progress()); + return [ + 'promise' => [$d, 'promise'], + 'resolve' => [$d, 'resolve'], + 'reject' => [$d, 'reject'], + 'progress' => [$d, 'progress'], + ]; } } diff --git a/tests/React/Promise/ErrorCollector.php b/tests/React/Promise/ErrorCollector.php deleted file mode 100644 index 648ab68a..00000000 --- a/tests/React/Promise/ErrorCollector.php +++ /dev/null @@ -1,38 +0,0 @@ -errors = &$errors; - } - - public function unregister() - { - $this->errors = array(); - restore_error_handler(); - } - - public function assertCollectedError($errstr, $errno) - { - foreach ($this->errors as $error) { - if ($error['errstr'] === $errstr && $error['errno'] === $errno) { - return; - } - } - - $message = 'Error with level ' . $errno . ' and message "' . $errstr . '" not found in ' . var_export($this->errors, true); - - throw new \PHPUnit_Framework_AssertionFailedError($message); - } -} diff --git a/tests/React/Promise/FulfilledPromiseTest.php b/tests/React/Promise/FulfilledPromiseTest.php index ef574a8e..d34065b7 100644 --- a/tests/React/Promise/FulfilledPromiseTest.php +++ b/tests/React/Promise/FulfilledPromiseTest.php @@ -2,142 +2,43 @@ namespace React\Promise; -/** - * @group Promise - * @group FulfilledPromise - */ class FulfilledPromiseTest extends TestCase { - /** @test */ - public function shouldReturnAPromise() - { - $p = new FulfilledPromise(); - $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then()); - } - - /** @test */ - public function shouldReturnAllowNull() - { - $p = new FulfilledPromise(); - $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then(null, null, null)); - } - - /** @test */ - public function shouldForwardResultWhenCallbackIsNull() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $p = new FulfilledPromise(1); - $p - ->then( - null, - $this->expectCallableNever() - ) - ->then( - $mock, - $this->expectCallableNever() - ); - } - - /** @test */ - public function shouldForwardCallbackResultToNextCallback() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $p = new FulfilledPromise(1); - $p - ->then( - function ($val) { - return $val + 1; - }, - $this->expectCallableNever() - ) - ->then( - $mock, - $this->expectCallableNever() - ); - } + use PromiseTest\PromiseTestTrait, + PromiseTest\PromiseFulfilledTestTrait; - /** @test */ - public function shouldForwardPromisedCallbackResultValueToNextCallback() + public function getPromiseTestAdapter() { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $p = new FulfilledPromise(1); - $p - ->then( - function ($val) { - return new FulfilledPromise($val + 1); - }, - $this->expectCallableNever() - ) - ->then( - $mock, - $this->expectCallableNever() - ); + $val = null; + $promiseCalled = false; + + return [ + 'promise' => function () use (&$val, &$promiseCalled) { + $promiseCalled = true; + + return new FulfilledPromise($val); + }, + 'resolve' => function ($value) use (&$val, &$promiseCalled) { + if ($promiseCalled) { + throw new \LogicException('You must call resolve() before promise() for React\Promise\FulfilledPromise'); + } + + $val = $value; + }, + 'reject' => function () { + throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise'); + }, + 'progress' => function () { + throw new \LogicException('You cannot call progress() for React\Promise\FulfilledPromise'); + }, + ]; } /** @test */ - public function shouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection() + public function shouldThrowExceptionIfConstructedWithAPromise() { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $p = new FulfilledPromise(1); - $p - ->then( - function ($val) { - return new RejectedPromise($val + 1); - }, - $this->expectCallableNever() - ) - ->then( - $this->expectCallableNever(), - $mock - ); - } - - /** @test */ - public function shouldSwitchFromCallbacksToErrbacksWhenCallbackThrows() - { - $exception = new \Exception(); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->will($this->throwException($exception)); - - $mock2 = $this->createCallableMock(); - $mock2 - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception)); + $this->setExpectedException('\InvalidArgumentException'); - $p = new FulfilledPromise(1); - $p - ->then( - $mock, - $this->expectCallableNever() - ) - ->then( - $this->expectCallableNever(), - $mock2 - ); + return new FulfilledPromise(new FulfilledPromise()); } } diff --git a/tests/React/Promise/WhenAllTest.php b/tests/React/Promise/FunctionAllTest.php similarity index 59% rename from tests/React/Promise/WhenAllTest.php rename to tests/React/Promise/FunctionAllTest.php index 555eb382..fcd0a5df 100644 --- a/tests/React/Promise/WhenAllTest.php +++ b/tests/React/Promise/FunctionAllTest.php @@ -2,11 +2,7 @@ namespace React\Promise; -/** - * @group When - * @group WhenAll - */ -class WhenAllTest extends TestCase +class FunctionAllTest extends TestCase { /** @test */ public function shouldResolveEmptyInput() @@ -15,9 +11,10 @@ public function shouldResolveEmptyInput() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array())); + ->with($this->identicalTo([])); - When::all(array(), $mock); + all([]) + ->then($mock); } /** @test */ @@ -27,12 +24,10 @@ public function shouldResolveValuesArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1, 2, 3))); + ->with($this->identicalTo([1, 2, 3])); - When::all( - array(1, 2, 3), - $mock - ); + all([1, 2, 3]) + ->then($mock); } /** @test */ @@ -42,12 +37,10 @@ public function shouldResolvePromisesArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1, 2, 3))); + ->with($this->identicalTo([1, 2, 3])); - When::all( - array(When::resolve(1), When::resolve(2), When::resolve(3)), - $mock - ); + all([resolve(1), resolve(2), resolve(3)]) + ->then($mock); } /** @test */ @@ -57,12 +50,10 @@ public function shouldResolveSparseArrayInput() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(null, 1, null, 1, 1))); + ->with($this->identicalTo([null, 1, null, 1, 1])); - When::all( - array(null, 1, null, 1, 1), - $mock - ); + all([null, 1, null, 1, 1]) + ->then($mock); } /** @test */ @@ -74,11 +65,8 @@ public function shouldRejectIfAnyInputPromiseRejects() ->method('__invoke') ->with($this->identicalTo(2)); - When::all( - array(When::resolve(1), When::reject(2), When::resolve(3)), - $this->expectCallableNever(), - $mock - ); + all([resolve(1), reject(2), resolve(3)]) + ->then($this->expectCallableNever(), $mock); } /** @test */ @@ -88,12 +76,10 @@ public function shouldAcceptAPromiseForAnArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1, 2, 3))); + ->with($this->identicalTo([1, 2, 3])); - When::all( - When::resolve(array(1, 2, 3)), - $mock - ); + all(resolve([1, 2, 3])) + ->then($mock); } /** @test */ @@ -103,11 +89,9 @@ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array())); + ->with($this->identicalTo([])); - When::all( - When::resolve(1), - $mock - ); + all(resolve(1)) + ->then($mock); } } diff --git a/tests/React/Promise/WhenAnyTest.php b/tests/React/Promise/FunctionAnyTest.php similarity index 70% rename from tests/React/Promise/WhenAnyTest.php rename to tests/React/Promise/FunctionAnyTest.php index 467fec1a..bf8a0db4 100644 --- a/tests/React/Promise/WhenAnyTest.php +++ b/tests/React/Promise/FunctionAnyTest.php @@ -2,11 +2,7 @@ namespace React\Promise; -/** - * @group When - * @group WhenAny - */ -class WhenAnyTest extends TestCase +class FunctionAnyTest extends TestCase { /** @test */ public function shouldResolveToNullWithEmptyInputArray() @@ -17,7 +13,8 @@ public function shouldResolveToNullWithEmptyInputArray() ->method('__invoke') ->with($this->identicalTo(null)); - When::any(array(), $mock); + any([]) + ->then($mock); } /** @test */ @@ -29,10 +26,8 @@ public function shouldResolveWithAnInputValue() ->method('__invoke') ->with($this->identicalTo(1)); - When::any( - array(1, 2, 3), - $mock - ); + any([1, 2, 3]) + ->then($mock); } /** @test */ @@ -44,10 +39,8 @@ public function shouldResolveWithAPromisedInputValue() ->method('__invoke') ->with($this->identicalTo(1)); - When::any( - array(When::resolve(1), When::resolve(2), When::resolve(3)), - $mock - ); + any([resolve(1), resolve(2), resolve(3)]) + ->then($mock); } /** @test */ @@ -57,13 +50,10 @@ public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(0 => 1, 1 => 2, 2 => 3))); + ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3])); - When::any( - array(When::reject(1), When::reject(2), When::reject(3)), - $this->expectCallableNever(), - $mock - ); + any([reject(1), reject(2), reject(3)]) + ->then($this->expectCallableNever(), $mock); } /** @test */ @@ -75,10 +65,8 @@ public function shouldResolveWhenFirstInputPromiseResolves() ->method('__invoke') ->with($this->identicalTo(1)); - When::any( - array(When::resolve(1), When::reject(2), When::reject(3)), - $mock - ); + any([resolve(1), reject(2), reject(3)]) + ->then($mock); } /** @test */ @@ -90,10 +78,8 @@ public function shouldAcceptAPromiseForAnArray() ->method('__invoke') ->with($this->identicalTo(1)); - When::any( - When::resolve(array(1, 2, 3)), - $mock - ); + any(resolve([1, 2, 3])) + ->then($mock); } /** @test */ @@ -105,10 +91,8 @@ public function shouldResolveToNullArrayWhenInputPromiseDoesNotResolveToArray() ->method('__invoke') ->with($this->identicalTo(null)); - When::any( - When::resolve(1), - $mock - ); + any(resolve(1)) + ->then($mock); } /** @test */ @@ -123,10 +107,8 @@ public function shouldNotRelyOnArryIndexesWhenUnwrappingToASingleResolutionValue $d1 = new Deferred(); $d2 = new Deferred(); - When::any( - array('abc' => $d1->promise(), 1 => $d2->promise()), - $mock - ); + any(['abc' => $d1->promise(), 1 => $d2->promise()]) + ->then($mock); $d2->resolve(2); $d1->resolve(1); diff --git a/tests/React/Promise/WhenMapTest.php b/tests/React/Promise/FunctionMapTest.php similarity index 70% rename from tests/React/Promise/WhenMapTest.php rename to tests/React/Promise/FunctionMapTest.php index 9dfc88a9..b8bf3a83 100644 --- a/tests/React/Promise/WhenMapTest.php +++ b/tests/React/Promise/FunctionMapTest.php @@ -2,11 +2,7 @@ namespace React\Promise; -/** - * @group When - * @group WhenMap - */ -class WhenMapTest extends TestCase +class FunctionMapTest extends TestCase { protected function mapper() { @@ -18,7 +14,7 @@ protected function mapper() protected function promiseMapper() { return function ($val) { - return When::resolve($val * 2); + return resolve($val * 2); }; } @@ -29,10 +25,10 @@ public function shouldMapInputValuesArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(2, 4, 6))); + ->with($this->identicalTo([2, 4, 6])); - When::map( - array(1, 2, 3), + map( + [1, 2, 3], $this->mapper() )->then($mock); } @@ -44,10 +40,10 @@ public function shouldMapInputPromisesArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(2, 4, 6))); + ->with($this->identicalTo([2, 4, 6])); - When::map( - array(When::resolve(1), When::resolve(2), When::resolve(3)), + map( + [resolve(1), resolve(2), resolve(3)], $this->mapper() )->then($mock); } @@ -59,10 +55,10 @@ public function shouldMapMixedInputArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(2, 4, 6))); + ->with($this->identicalTo([2, 4, 6])); - When::map( - array(1, When::resolve(2), 3), + map( + [1, resolve(2), 3], $this->mapper() )->then($mock); } @@ -74,10 +70,10 @@ public function shouldMapInputWhenMapperReturnsAPromise() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(2, 4, 6))); + ->with($this->identicalTo([2, 4, 6])); - When::map( - array(1, 2, 3), + map( + [1, 2, 3], $this->promiseMapper() )->then($mock); } @@ -89,10 +85,10 @@ public function shouldAcceptAPromiseForAnArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(2, 4, 6))); + ->with($this->identicalTo([2, 4, 6])); - When::map( - When::resolve(array(1, When::resolve(2), 3)), + map( + resolve([1, resolve(2), 3]), $this->mapper() )->then($mock); } @@ -104,10 +100,10 @@ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array())); + ->with($this->identicalTo([])); - When::map( - When::resolve(1), + map( + resolve(1), $this->mapper() )->then($mock); } @@ -121,8 +117,8 @@ public function shouldRejectWhenInputContainsRejection() ->method('__invoke') ->with($this->identicalTo(2)); - When::map( - array(When::resolve(1), When::reject(2), When::resolve(3)), + map( + [resolve(1), reject(2), resolve(3)], $this->mapper() )->then($this->expectCallableNever(), $mock); } diff --git a/tests/React/Promise/FunctionRaceTest.php b/tests/React/Promise/FunctionRaceTest.php new file mode 100644 index 00000000..553220c5 --- /dev/null +++ b/tests/React/Promise/FunctionRaceTest.php @@ -0,0 +1,122 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + [] + )->then($mock); + } + + /** @test */ + public function shouldResolveValuesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + race( + [1, 2, 3] + )->then($mock); + } + + /** @test */ + public function shouldResolvePromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + race( + [$d1->promise(), $d2->promise(), $d3->promise()] + )->then($mock); + + $d2->resolve(2); + + $d1->resolve(1); + $d3->resolve(3); + } + + /** @test */ + public function shouldResolveSparseArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + [null, 1, null, 2, 3] + )->then($mock); + } + + /** @test */ + public function shouldRejectIfFirstSettledPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + race( + [$d1->promise(), $d2->promise(), $d3->promise()] + )->then($this->expectCallableNever(), $mock); + + $d2->reject(2); + + $d1->resolve(1); + $d3->resolve(3); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + race( + resolve([1, 2, 3]) + )->then($mock); + } + + /** @test */ + public function shouldResolveToNullWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + resolve(1) + )->then($mock); + } +} diff --git a/tests/React/Promise/WhenReduceTest.php b/tests/React/Promise/FunctionReduceTest.php similarity index 82% rename from tests/React/Promise/WhenReduceTest.php rename to tests/React/Promise/FunctionReduceTest.php index 425019f9..715e8477 100644 --- a/tests/React/Promise/WhenReduceTest.php +++ b/tests/React/Promise/FunctionReduceTest.php @@ -2,11 +2,7 @@ namespace React\Promise; -/** - * @group When - * @group WhenReduce - */ -class WhenReduceTest extends TestCase +class FunctionReduceTest extends TestCase { protected function plus() { @@ -31,8 +27,8 @@ public function shouldReduceValuesWithoutInitialValue() ->method('__invoke') ->with($this->identicalTo(6)); - When::reduce( - array(1, 2, 3), + reduce( + [1, 2, 3], $this->plus() )->then($mock); } @@ -46,8 +42,8 @@ public function shouldReduceValuesWithInitialValue() ->method('__invoke') ->with($this->identicalTo(7)); - When::reduce( - array(1, 2, 3), + reduce( + [1, 2, 3], $this->plus(), 1 )->then($mock); @@ -62,10 +58,10 @@ public function shouldReduceValuesWithInitialPromise() ->method('__invoke') ->with($this->identicalTo(7)); - When::reduce( - array(1, 2, 3), + reduce( + [1, 2, 3], $this->plus(), - When::resolve(1) + resolve(1) )->then($mock); } @@ -78,8 +74,8 @@ public function shouldReducePromisedValuesWithoutInitialValue() ->method('__invoke') ->with($this->identicalTo(6)); - When::reduce( - array(When::resolve(1), When::resolve(2), When::resolve(3)), + reduce( + [resolve(1), resolve(2), resolve(3)], $this->plus() )->then($mock); } @@ -93,8 +89,8 @@ public function shouldReducePromisedValuesWithInitialValue() ->method('__invoke') ->with($this->identicalTo(7)); - When::reduce( - array(When::resolve(1), When::resolve(2), When::resolve(3)), + reduce( + [resolve(1), resolve(2), resolve(3)], $this->plus(), 1 )->then($mock); @@ -109,10 +105,10 @@ public function shouldReducePromisedValuesWithInitialPromise() ->method('__invoke') ->with($this->identicalTo(7)); - When::reduce( - array(When::resolve(1), When::resolve(2), When::resolve(3)), + reduce( + [resolve(1), resolve(2), resolve(3)], $this->plus(), - When::resolve(1) + resolve(1) )->then($mock); } @@ -125,8 +121,8 @@ public function shouldReduceEmptyInputWithInitialValue() ->method('__invoke') ->with($this->identicalTo(1)); - When::reduce( - array(), + reduce( + [], $this->plus(), 1 )->then($mock); @@ -141,10 +137,10 @@ public function shouldReduceEmptyInputWithInitialPromise() ->method('__invoke') ->with($this->identicalTo(1)); - When::reduce( - array(), + reduce( + [], $this->plus(), - When::resolve(1) + resolve(1) )->then($mock); } @@ -157,10 +153,10 @@ public function shouldRejectWhenInputContainsRejection() ->method('__invoke') ->with($this->identicalTo(2)); - When::reduce( - array(When::resolve(1), When::reject(2), When::resolve(3)), + reduce( + [resolve(1), reject(2), resolve(3)], $this->plus(), - When::resolve(1) + resolve(1) )->then($this->expectCallableNever(), $mock); } @@ -177,8 +173,8 @@ public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseP ->method('__invoke') ->with($this->identicalTo(null)); - When::reduce( - array(), + reduce( + [], $this->plus() )->then($mock); } @@ -192,8 +188,8 @@ public function shouldAllowSparseArrayInputWithoutInitialValue() ->method('__invoke') ->with($this->identicalTo(3)); - When::reduce( - array(null, null, 1, null, 1, 1), + reduce( + [null, null, 1, null, 1, 1], $this->plus() )->then($mock); } @@ -207,8 +203,8 @@ public function shouldAllowSparseArrayInputWithInitialValue() ->method('__invoke') ->with($this->identicalTo(4)); - When::reduce( - array(null, null, 1, null, 1, 1), + reduce( + [null, null, 1, null, 1, 1], $this->plus(), 1 )->then($mock); @@ -223,8 +219,8 @@ public function shouldReduceInInputOrder() ->method('__invoke') ->with($this->identicalTo('123')); - When::reduce( - array(1, 2, 3), + reduce( + [1, 2, 3], $this->append(), '' )->then($mock); @@ -239,8 +235,8 @@ public function shouldAcceptAPromiseForAnArray() ->method('__invoke') ->with($this->identicalTo('123')); - When::reduce( - When::resolve(array(1, 2, 3)), + reduce( + resolve([1, 2, 3]), $this->append(), '' )->then($mock); @@ -255,8 +251,8 @@ public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArr ->method('__invoke') ->with($this->identicalTo(1)); - When::reduce( - When::resolve(1), + reduce( + resolve(1), $this->plus(), 1 )->then($mock); @@ -279,12 +275,12 @@ public function shouldProvideCorrectBasisValue() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1, 2, 3))); + ->with($this->identicalTo([1, 2, 3])); - When::reduce( - array($d1->promise(), $d2->promise(), $d3->promise()), + reduce( + [$d1->promise(), $d2->promise(), $d3->promise()], $insertIntoArray, - array() + [] )->then($mock); $d3->resolve(3); diff --git a/tests/React/Promise/WhenRejectTest.php b/tests/React/Promise/FunctionRejectTest.php similarity index 76% rename from tests/React/Promise/WhenRejectTest.php rename to tests/React/Promise/FunctionRejectTest.php index 1116eabd..84b8ec6a 100644 --- a/tests/React/Promise/WhenRejectTest.php +++ b/tests/React/Promise/FunctionRejectTest.php @@ -2,11 +2,7 @@ namespace React\Promise; -/** - * @group When - * @group WhenReject - */ -class WhenRejectTest extends TestCase +class FunctionRejectTest extends TestCase { /** @test */ public function shouldRejectAnImmediateValue() @@ -19,7 +15,7 @@ public function shouldRejectAnImmediateValue() ->method('__invoke') ->with($this->identicalTo($expected)); - When::reject($expected) + reject($expected) ->then( $this->expectCallableNever(), $mock @@ -27,12 +23,11 @@ public function shouldRejectAnImmediateValue() } /** @test */ - public function shouldRejectAResolvedPromise() + public function shouldRejectAFulfilledPromise() { $expected = 123; - $d = new Deferred(); - $d->resolve($expected); + $resolved = new FulfilledPromise($expected); $mock = $this->createCallableMock(); $mock @@ -40,7 +35,7 @@ public function shouldRejectAResolvedPromise() ->method('__invoke') ->with($this->identicalTo($expected)); - When::reject($d->promise()) + reject($resolved) ->then( $this->expectCallableNever(), $mock @@ -52,8 +47,7 @@ public function shouldRejectARejectedPromise() { $expected = 123; - $d = new Deferred(); - $d->reject($expected); + $resolved = new RejectedPromise($expected); $mock = $this->createCallableMock(); $mock @@ -61,7 +55,7 @@ public function shouldRejectARejectedPromise() ->method('__invoke') ->with($this->identicalTo($expected)); - When::reject($d->promise()) + reject($resolved) ->then( $this->expectCallableNever(), $mock diff --git a/tests/React/Promise/WhenResolveTest.php b/tests/React/Promise/FunctionResolveTest.php similarity index 78% rename from tests/React/Promise/WhenResolveTest.php rename to tests/React/Promise/FunctionResolveTest.php index ab591423..01307d5a 100644 --- a/tests/React/Promise/WhenResolveTest.php +++ b/tests/React/Promise/FunctionResolveTest.php @@ -2,11 +2,7 @@ namespace React\Promise; -/** - * @group When - * @group WhenResolve - */ -class WhenResolveTest extends TestCase +class FunctionResolveTest extends TestCase { /** @test */ public function shouldResolveAnImmediateValue() @@ -19,7 +15,7 @@ public function shouldResolveAnImmediateValue() ->method('__invoke') ->with($this->identicalTo($expected)); - When::resolve($expected) + resolve($expected) ->then( $mock, $this->expectCallableNever() @@ -27,12 +23,11 @@ public function shouldResolveAnImmediateValue() } /** @test */ - public function shouldResolveAResolvedPromise() + public function shouldResolveAFulfilledPromise() { $expected = 123; - $d = new Deferred(); - $d->resolve($expected); + $resolved = new FulfilledPromise($expected); $mock = $this->createCallableMock(); $mock @@ -40,7 +35,7 @@ public function shouldResolveAResolvedPromise() ->method('__invoke') ->with($this->identicalTo($expected)); - When::resolve($d->promise()) + resolve($resolved) ->then( $mock, $this->expectCallableNever() @@ -52,8 +47,7 @@ public function shouldRejectARejectedPromise() { $expected = 123; - $d = new Deferred(); - $d->reject($expected); + $resolved = new RejectedPromise($expected); $mock = $this->createCallableMock(); $mock @@ -61,7 +55,7 @@ public function shouldRejectARejectedPromise() ->method('__invoke') ->with($this->identicalTo($expected)); - When::resolve($d->promise()) + resolve($resolved) ->then( $this->expectCallableNever(), $mock @@ -74,7 +68,7 @@ public function shouldSupportDeepNestingInPromiseChains() $d = new Deferred(); $d->resolve(false); - $result = When::resolve(When::resolve($d->then(function ($val) { + $result = resolve(resolve($d->promise()->then(function ($val) { $d = new Deferred(); $d->resolve($val); @@ -82,7 +76,7 @@ public function shouldSupportDeepNestingInPromiseChains() return $val; }; - return When::resolve($d->then($identity))->then( + return resolve($d->promise()->then($identity))->then( function ($val) { return !$val; } diff --git a/tests/React/Promise/WhenSomeTest.php b/tests/React/Promise/FunctionSomeTest.php similarity index 54% rename from tests/React/Promise/WhenSomeTest.php rename to tests/React/Promise/FunctionSomeTest.php index 9704b734..09e53504 100644 --- a/tests/React/Promise/WhenSomeTest.php +++ b/tests/React/Promise/FunctionSomeTest.php @@ -2,11 +2,7 @@ namespace React\Promise; -/** - * @group When - * @group WhenSome - */ -class WhenSomeTest extends TestCase +class FunctionSomeTest extends TestCase { /** @test */ public function shouldResolveEmptyInput() @@ -15,9 +11,12 @@ public function shouldResolveEmptyInput() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array())); + ->with($this->identicalTo([])); - When::some(array(), 1, $mock); + some( + [], + 1 + )->then($mock); } /** @test */ @@ -27,13 +26,12 @@ public function shouldResolveValuesArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1, 2))); + ->with($this->identicalTo([1, 2])); - When::some( - array(1, 2, 3), - 2, - $mock - ); + some( + [1, 2, 3], + 2 + )->then($mock); } /** @test */ @@ -43,13 +41,12 @@ public function shouldResolvePromisesArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1, 2))); + ->with($this->identicalTo([1, 2])); - When::some( - array(When::resolve(1), When::resolve(2), When::resolve(3)), - 2, - $mock - ); + some( + [resolve(1), resolve(2), resolve(3)], + 2 + )->then($mock); } /** @test */ @@ -59,13 +56,12 @@ public function shouldResolveSparseArrayInput() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(null, 1))); + ->with($this->identicalTo([null, 1])); - When::some( - array(null, 1, null, 2, 3), - 2, - $mock - ); + some( + [null, 1, null, 2, 3], + 2 + )->then($mock); } /** @test */ @@ -75,14 +71,12 @@ public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsA $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1 => 2, 2 => 3))); - - When::some( - array(When::resolve(1), When::reject(2), When::reject(3)), - 2, - $this->expectCallableNever(), - $mock - ); + ->with($this->identicalTo([1 => 2, 2 => 3])); + + some( + [resolve(1), reject(2), reject(3)], + 2 + )->then($this->expectCallableNever(), $mock); } /** @test */ @@ -92,13 +86,27 @@ public function shouldAcceptAPromiseForAnArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array(1, 2))); + ->with($this->identicalTo([1, 2])); + + some( + resolve([1, 2, 3]), + 2 + )->then($mock); + } + + /** @test */ + public function shouldResolveWithEmptyArrayIfHowManyIsLessThanOne() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); - When::some( - When::resolve(array(1, 2, 3)), - 2, - $mock - ); + some( + [1], + 0 + )->then($mock); } /** @test */ @@ -108,12 +116,11 @@ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(array())); + ->with($this->identicalTo([])); - When::some( - When::resolve(1), - 1, - $mock - ); + some( + resolve(1), + 1 + )->then($mock); } } diff --git a/tests/React/Promise/LazyPromiseTest.php b/tests/React/Promise/LazyPromiseTest.php index 52a2517f..f10609cc 100644 --- a/tests/React/Promise/LazyPromiseTest.php +++ b/tests/React/Promise/LazyPromiseTest.php @@ -2,12 +2,28 @@ namespace React\Promise; -/** - * @group Promise - * @group LazyPromise - */ class LazyPromiseTest extends TestCase { + use PromiseTest\FullTestTrait; + + public function getPromiseTestAdapter() + { + $d = new Deferred(); + + $factory = function () use ($d) { + return $d->promise(); + }; + + return [ + 'promise' => function () use ($factory) { + return new LazyPromise($factory); + }, + 'resolve' => [$d, 'resolve'], + 'reject' => [$d, 'reject'], + 'progress' => [$d, 'progress'], + ]; + } + /** @test */ public function shouldNotCallFactoryIfThenIsNotInvoked() { @@ -40,15 +56,15 @@ public function shouldReturnPromiseFromFactory() ->method('__invoke') ->will($this->returnValue(new FulfilledPromise(1))); - $fulfilledHandler = $this->createCallableMock(); - $fulfilledHandler + $onFulfilled = $this->createCallableMock(); + $onFulfilled ->expects($this->once()) ->method('__invoke') ->with($this->identicalTo(1)); $p = new LazyPromise($factory); - $p->then($fulfilledHandler); + $p->then($onFulfilled); } /** @test */ @@ -63,26 +79,26 @@ public function shouldReturnPromiseIfFactoryReturnsNull() $p = new LazyPromise($factory); $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then()); } - + /** @test */ public function shouldReturnRejectedPromiseIfFactoryThrowsException() { $exception = new \Exception(); - + $factory = $this->createCallableMock(); $factory ->expects($this->once()) ->method('__invoke') ->will($this->throwException($exception)); - $errorHandler = $this->createCallableMock(); - $errorHandler + $onRejected = $this->createCallableMock(); + $onRejected ->expects($this->once()) ->method('__invoke') ->with($this->identicalTo($exception)); $p = new LazyPromise($factory); - $p->then($this->expectCallableNever(), $errorHandler); + $p->then($this->expectCallableNever(), $onRejected); } } diff --git a/tests/React/Promise/PromiseTest.php b/tests/React/Promise/PromiseTest.php new file mode 100644 index 00000000..46f0abe3 --- /dev/null +++ b/tests/React/Promise/PromiseTest.php @@ -0,0 +1,47 @@ + function () use ($promise) { + return $promise; + }, + 'resolve' => $resolveCallback, + 'reject' => $rejectCallback, + 'progress' => $progressCallback, + ]; + } + + /** @test */ + public function shouldRejectIfResolverThrowsException() + { + $exception = new \Exception('foo'); + + $promise = new Promise(function () use ($exception) { + throw $exception; + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $promise + ->then($this->expectCallableNever(), $mock); + } +} diff --git a/tests/React/Promise/PromiseTest/FullTestTrait.php b/tests/React/Promise/PromiseTest/FullTestTrait.php new file mode 100644 index 00000000..ca3cda78 --- /dev/null +++ b/tests/React/Promise/PromiseTest/FullTestTrait.php @@ -0,0 +1,13 @@ +getPromiseTestAdapter()); - $d = new Deferred(); + $sentinel = new \stdClass(); $mock = $this->createCallableMock(); $mock @@ -21,21 +19,18 @@ public function shouldProgress() ->method('__invoke') ->with($sentinel); - $d - ->promise() + $promise() ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock); - $d - ->resolver() - ->progress($sentinel); + $progress($sentinel); } /** @test */ - public function shouldPropagateProgressToDownstreamPromises() + public function progressShouldPropagateProgressToDownstreamPromises() { - $sentinel = new \stdClass(); + extract($this->getPromiseTestAdapter()); - $d = new Deferred(); + $sentinel = new \stdClass(); $mock = $this->createCallableMock(); $mock @@ -49,8 +44,7 @@ public function shouldPropagateProgressToDownstreamPromises() ->method('__invoke') ->with($sentinel); - $d - ->promise() + $promise() ->then( $this->expectCallableNever(), $this->expectCallableNever(), @@ -62,17 +56,15 @@ public function shouldPropagateProgressToDownstreamPromises() $mock2 ); - $d - ->resolver() - ->progress($sentinel); + $progress($sentinel); } /** @test */ - public function shouldPropagateTransformedProgressToDownstreamPromises() + public function progressShouldPropagateTransformedProgressToDownstreamPromises() { - $sentinel = new \stdClass(); + extract($this->getPromiseTestAdapter()); - $d = new Deferred(); + $sentinel = new \stdClass(); $mock = $this->createCallableMock(); $mock @@ -86,8 +78,7 @@ public function shouldPropagateTransformedProgressToDownstreamPromises() ->method('__invoke') ->with($sentinel); - $d - ->promise() + $promise() ->then( $this->expectCallableNever(), $this->expectCallableNever(), @@ -99,17 +90,15 @@ public function shouldPropagateTransformedProgressToDownstreamPromises() $mock2 ); - $d - ->resolver() - ->progress(1); + $progress(1); } /** @test */ - public function shouldPropagateCaughtExceptionValueAsProgress() + public function progressShouldPropagateCaughtExceptionValueAsProgress() { - $exception = new \Exception(); + extract($this->getPromiseTestAdapter()); - $d = new Deferred(); + $exception = new \Exception(); $mock = $this->createCallableMock(); $mock @@ -123,8 +112,7 @@ public function shouldPropagateCaughtExceptionValueAsProgress() ->method('__invoke') ->with($this->identicalTo($exception)); - $d - ->promise() + $promise() ->then( $this->expectCallableNever(), $this->expectCallableNever(), @@ -136,18 +124,16 @@ public function shouldPropagateCaughtExceptionValueAsProgress() $mock2 ); - $d - ->resolver() - ->progress(1); + $progress(1); } /** @test */ - public function shouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise() + public function progressShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise() { - $sentinel = new \stdClass(); + extract($this->getPromiseTestAdapter()); + extract($this->getPromiseTestAdapter(), EXTR_PREFIX_ALL, 'other'); - $d = new Deferred(); - $d2 = new Deferred(); + $sentinel = new \stdClass(); $mock = $this->createCallableMock(); $mock @@ -155,15 +141,12 @@ public function shouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolv ->method('__invoke') ->with($sentinel); - // resolve $d BEFORE calling attaching progress handler - $d - ->resolver() - ->resolve(); + // resolve BEFORE attaching progress handler + $resolve(); - $d - ->promise() - ->then(function () use ($d2) { - return $d2->promise(); + $promise() + ->then(function () use ($other_promise) { + return $other_promise(); }) ->then( $this->expectCallableNever(), @@ -171,18 +154,16 @@ public function shouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolv $mock ); - $d2 - ->resolver() - ->progress($sentinel); + $other_progress($sentinel); } /** @test */ - public function shouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise() + public function progressShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise() { - $sentinel = new \stdClass(); + extract($this->getPromiseTestAdapter()); + extract($this->getPromiseTestAdapter(), EXTR_PREFIX_ALL, 'other'); - $d = new Deferred(); - $d2 = new Deferred(); + $sentinel = new \stdClass(); $mock = $this->createCallableMock(); $mock @@ -190,10 +171,9 @@ public function shouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnres ->method('__invoke') ->with($sentinel); - $d - ->promise() - ->then(function () use ($d2) { - return $d2->promise(); + $promise() + ->then(function () use ($other_promise) { + return $other_promise(); }) ->then( $this->expectCallableNever(), @@ -201,22 +181,18 @@ public function shouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnres $mock ); - // resolve $d AFTER calling attaching progress handler - $d - ->resolver() - ->resolve(); - $d2 - ->resolver() - ->progress($sentinel); + // resolve AFTER attaching progress handler + $resolve(); + $other_progress($sentinel); } /** @test */ - public function shouldForwardProgressWhenResolvedWithAnotherPromise() + public function progressShouldForwardProgressWhenResolvedWithAnotherPromise() { - $sentinel = new \stdClass(); + extract($this->getPromiseTestAdapter()); + extract($this->getPromiseTestAdapter(), EXTR_PREFIX_ALL, 'other'); - $d = new Deferred(); - $d2 = new Deferred(); + $sentinel = new \stdClass(); $mock = $this->createCallableMock(); $mock @@ -230,8 +206,7 @@ public function shouldForwardProgressWhenResolvedWithAnotherPromise() ->method('__invoke') ->with($sentinel); - $d - ->promise() + $promise() ->then( $this->expectCallableNever(), $this->expectCallableNever(), @@ -243,18 +218,14 @@ public function shouldForwardProgressWhenResolvedWithAnotherPromise() $mock2 ); - $d - ->resolver() - ->resolve($d2->promise()); - $d2 - ->resolver() - ->progress($sentinel); + $resolve($other_promise()); + $other_progress($sentinel); } /** @test */ - public function shouldAllowResolveAfterProgress() + public function progressShouldAllowResolveAfterProgress() { - $d = new Deferred(); + extract($this->getPromiseTestAdapter()); $mock = $this->createCallableMock(); $mock @@ -266,26 +237,21 @@ public function shouldAllowResolveAfterProgress() ->method('__invoke') ->with($this->identicalTo(2)); - $d - ->promise() + $promise() ->then( $mock, $this->expectCallableNever(), $mock ); - $d - ->resolver() - ->progress(1); - $d - ->resolver() - ->resolve(2); + $progress(1); + $resolve(2); } /** @test */ - public function shouldAllowRejectAfterProgress() + public function progressShouldAllowRejectAfterProgress() { - $d = new Deferred(); + extract($this->getPromiseTestAdapter()); $mock = $this->createCallableMock(); $mock @@ -297,53 +263,34 @@ public function shouldAllowRejectAfterProgress() ->method('__invoke') ->with($this->identicalTo(2)); - $d - ->promise() + $promise() ->then( $this->expectCallableNever(), $mock, $mock ); - $d - ->resolver() - ->progress(1); - $d - ->resolver() - ->reject(2); + $progress(1); + $reject(2); } - /** - * @test - * @dataProvider invalidCallbackDataProvider - **/ - public function shouldIgnoreNonFunctionsAndTriggerPhpNotice($var) + /** @test */ + public function progressShouldReturnSilentlyOnProgressWhenAlreadyResolved() { - $errorCollector = new ErrorCollector(); - $errorCollector->register(); + extract($this->getPromiseTestAdapter()); - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); + $resolve(1); - $d = new Deferred(); - $d - ->then( - null, - null, - $var - ) - ->then( - $this->expectCallableNever(), - $this->expectCallableNever(), - $mock - ); + $this->assertNull($progress()); + } + + /** @test */ + public function progressShouldReturnSilentlyOnProgressWhenAlreadyRejected() + { + extract($this->getPromiseTestAdapter()); - $d->progress(1); + $reject(1); - $errorCollector->assertCollectedError('Invalid $progressHandler argument passed to then(), must be null or callable.', E_USER_NOTICE); - $errorCollector->unregister(); + $this->assertNull($progress()); } } diff --git a/tests/React/Promise/PromiseTest/PromiseFulfilledTestTrait.php b/tests/React/Promise/PromiseTest/PromiseFulfilledTestTrait.php new file mode 100644 index 00000000..0a1967af --- /dev/null +++ b/tests/React/Promise/PromiseTest/PromiseFulfilledTestTrait.php @@ -0,0 +1,137 @@ +getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $resolve(1); + $promise() + ->then( + null, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldForwardCallbackResultToNextCallback() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $resolve(1); + $promise() + ->then( + function ($val) { + return $val + 1; + }, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldForwardPromisedCallbackResultValueToNextCallback() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $resolve(1); + $promise() + ->then( + function ($val) { + return \React\Promise\resolve($val + 1); + }, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $resolve(1); + $promise() + ->then( + function ($val) { + return \React\Promise\reject($val + 1); + }, + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackThrows() + { + extract($this->getPromiseTestAdapter()); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $resolve(1); + $promise() + ->then( + $mock, + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock2 + ); + } +} diff --git a/tests/React/Promise/PromiseTest/PromiseRejectedTestTrait.php b/tests/React/Promise/PromiseTest/PromiseRejectedTestTrait.php new file mode 100644 index 00000000..8c97186e --- /dev/null +++ b/tests/React/Promise/PromiseTest/PromiseRejectedTestTrait.php @@ -0,0 +1,142 @@ +getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with(null); + + $reject(1); + $promise() + ->then( + $this->expectCallableNever(), + function () { + // Presence of rejection handler is enough to switch back + // to resolve mode, even though it returns undefined. + // The ONLY way to propagate a rejection is to re-throw or + // return a rejected promise; + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $reject(1); + $promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return $val + 1; + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $reject(1); + $promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return \React\Promise\resolve($val + 1); + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldPropagateRejectionsWhenErrbackThrows() + { + extract($this->getPromiseTestAdapter()); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $reject(1); + $promise() + ->then( + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $mock2 + ); + } + + /** @test */ + public function shouldPropagateRejectionsWhenErrbackReturnsARejection() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $reject(1); + $promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return \React\Promise\reject($val + 1); + } + ) + ->then( + $this->expectCallableNever(), + $mock + ); + } +} diff --git a/tests/React/Promise/PromiseTest/PromiseTestTrait.php b/tests/React/Promise/PromiseTest/PromiseTestTrait.php new file mode 100644 index 00000000..9c83b937 --- /dev/null +++ b/tests/React/Promise/PromiseTest/PromiseTestTrait.php @@ -0,0 +1,24 @@ +getPromiseTestAdapter()); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $promise()->then()); + } + + /** @test */ + public function thenShouldReturnAllowNull() + { + extract($this->getPromiseTestAdapter()); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $promise()->then(null, null, null)); + } +} diff --git a/tests/React/Promise/PromiseTest/RejectTestTrait.php b/tests/React/Promise/PromiseTest/RejectTestTrait.php new file mode 100644 index 00000000..09119ead --- /dev/null +++ b/tests/React/Promise/PromiseTest/RejectTestTrait.php @@ -0,0 +1,101 @@ +getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($this->expectCallableNever(), $mock); + + $reject(1); + } + + /** @test */ + public function rejectShouldRejectWithFulfilledPromise() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($this->expectCallableNever(), $mock); + + $reject(Promise\resolve(1)); + } + + /** @test */ + public function rejectShouldRejectWithRejectedPromise() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($this->expectCallableNever(), $mock); + + $reject(Promise\reject(1)); + } + + /** @test */ + public function rejectShouldInvokeNewlyAddedErrbackWhenAlreadyRejected() + { + extract($this->getPromiseTestAdapter()); + + $reject(1); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function rejectShouldForwardReasonWhenCallbackIsNull() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then( + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock + ); + + $reject(1); + } +} diff --git a/tests/React/Promise/PromiseTest/ResolveTestTrait.php b/tests/React/Promise/PromiseTest/ResolveTestTrait.php new file mode 100644 index 00000000..d6bdd9d0 --- /dev/null +++ b/tests/React/Promise/PromiseTest/ResolveTestTrait.php @@ -0,0 +1,102 @@ +getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($mock); + + $resolve(1); + } + + /** @test */ + public function resolveShouldResolveWithPromisedValue() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($mock); + + $resolve(Promise\resolve(1)); + } + + /** @test */ + public function resolveShouldRejectWhenResolvedWithRejectedPromise() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($this->expectCallableNever(), $mock); + + $resolve(Promise\reject(1)); + } + + /** @test */ + public function resolveShouldInvokeNewlyAddedCallbackWhenAlreadyResolved() + { + extract($this->getPromiseTestAdapter()); + + $resolve(1); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then($mock, $this->expectCallableNever()); + } + + /** @test */ + public function resolveShouldForwardValueWhenCallbackIsNull() + { + extract($this->getPromiseTestAdapter()); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $promise() + ->then( + null, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + + $resolve(1); + } +} diff --git a/tests/React/Promise/RejectedPromiseTest.php b/tests/React/Promise/RejectedPromiseTest.php index df953803..97cd6da1 100644 --- a/tests/React/Promise/RejectedPromiseTest.php +++ b/tests/React/Promise/RejectedPromiseTest.php @@ -2,147 +2,43 @@ namespace React\Promise; -/** - * @group Promise - * @group RejectedPromise - */ class RejectedPromiseTest extends TestCase { - /** @test */ - public function shouldReturnAPromise() - { - $p = new RejectedPromise(); - $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then()); - } - - /** @test */ - public function shouldReturnAllowNull() - { - $p = new RejectedPromise(); - $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then(null, null, null)); - } - - /** @test */ - public function shouldForwardUndefinedRejectionValue() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with(null); - - $p = new RejectedPromise(1); - $p - ->then( - $this->expectCallableNever(), - function () { - // Presence of rejection handler is enough to switch back - // to resolve mode, even though it returns undefined. - // The ONLY way to propagate a rejection is to re-throw or - // return a rejected promise; - } - ) - ->then( - $mock, - $this->expectCallableNever() - ); - } - - /** @test */ - public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); + use PromiseTest\PromiseTestTrait, + PromiseTest\PromiseRejectedTestTrait; - $p = new RejectedPromise(1); - $p - ->then( - $this->expectCallableNever(), - function ($val) { - return $val + 1; - } - ) - ->then( - $mock, - $this->expectCallableNever() - ); - } - - /** @test */ - public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution() + public function getPromiseTestAdapter() { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); - - $p = new RejectedPromise(1); - $p - ->then( - $this->expectCallableNever(), - function ($val) { - return new FulfilledPromise($val + 1); + $val = null; + $promiseCalled = false; + + return [ + 'promise' => function () use (&$val, &$promiseCalled) { + $promiseCalled = true; + + return new RejectedPromise($val); + }, + 'resolve' => function ($value) { + throw new \LogicException('You cannot call resolve() for React\Promise\RejectedPromise'); + }, + 'reject' => function ($reason) use (&$val, &$promiseCalled) { + if ($promiseCalled) { + throw new \LogicException('You must call reject() before promise() for React\Promise\RejectedPromise'); } - ) - ->then( - $mock, - $this->expectCallableNever() - ); - } - - /** @test */ - public function shouldPropagateRejectionsWhenErrbackThrows() - { - $exception = new \Exception(); - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->will($this->throwException($exception)); - - $mock2 = $this->createCallableMock(); - $mock2 - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception)); - - $p = new RejectedPromise(1); - $p - ->then( - $this->expectCallableNever(), - $mock - ) - ->then( - $this->expectCallableNever(), - $mock2 - ); + $val = $reason; + }, + 'progress' => function () { + throw new \LogicException('You cannot call progress() for React\Promise\RejectedPromise'); + }, + ]; } /** @test */ - public function shouldPropagateRejectionsWhenErrbackReturnsARejection() + public function shouldThrowExceptionIfConstructedWithAPromise() { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(2)); + $this->setExpectedException('\InvalidArgumentException'); - $p = new RejectedPromise(1); - $p - ->then( - $this->expectCallableNever(), - function ($val) { - return new RejectedPromise($val + 1); - } - ) - ->then( - $this->expectCallableNever(), - $mock - ); + return new RejectedPromise(new RejectedPromise()); } } diff --git a/tests/React/Promise/TestCase.php b/tests/React/Promise/TestCase.php index 1d5e4f97..60ea46d7 100644 --- a/tests/React/Promise/TestCase.php +++ b/tests/React/Promise/TestCase.php @@ -38,16 +38,4 @@ public function createCallableMock() { return $this->getMock('React\\Promise\Stub\CallableStub'); } - - public function invalidCallbackDataProvider() - { - return array( - 'empty string' => array(''), - 'true' => array(true), - 'false' => array(false), - 'object' => array(new \stdClass), - 'truthy' => array(1), - 'falsey' => array(0) - ); - } } diff --git a/tests/React/Promise/UtilPromiseForTest.php b/tests/React/Promise/UtilPromiseForTest.php deleted file mode 100644 index cdadf2e3..00000000 --- a/tests/React/Promise/UtilPromiseForTest.php +++ /dev/null @@ -1,68 +0,0 @@ -createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - Util::promiseFor($expected) - ->then( - $mock, - $this->expectCallableNever() - ); - } - - /** @test */ - public function shouldResolveAFulfilledPromise() - { - $expected = 123; - - $resolved = new FulfilledPromise($expected); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - Util::promiseFor($resolved) - ->then( - $mock, - $this->expectCallableNever() - ); - } - - /** @test */ - public function shouldRejectARejectedPromise() - { - $expected = 123; - - $resolved = new RejectedPromise($expected); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - Util::promiseFor($resolved) - ->then( - $this->expectCallableNever(), - $mock - ); - } -} diff --git a/tests/React/Promise/UtilRejectedPromiseForTest.php b/tests/React/Promise/UtilRejectedPromiseForTest.php deleted file mode 100644 index 3c2265a7..00000000 --- a/tests/React/Promise/UtilRejectedPromiseForTest.php +++ /dev/null @@ -1,68 +0,0 @@ -createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - Util::rejectedPromiseFor($expected) - ->then( - $this->expectCallableNever(), - $mock - ); - } - - /** @test */ - public function shouldRejectWithFulfilledPromise() - { - $expected = 123; - - $resolved = new FulfilledPromise($expected); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - Util::rejectedPromiseFor($resolved) - ->then( - $this->expectCallableNever(), - $mock - ); - } - - /** @test */ - public function shouldRejectWithRejectedPromise() - { - $expected = 123; - - $resolved = new RejectedPromise($expected); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - Util::rejectedPromiseFor($resolved) - ->then( - $this->expectCallableNever(), - $mock - ); - } -} diff --git a/tests/React/Promise/WhenLazyTest.php b/tests/React/Promise/WhenLazyTest.php deleted file mode 100644 index a6512388..00000000 --- a/tests/React/Promise/WhenLazyTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertInstanceOf('React\\Promise\\PromiseInterface', When::lazy(function () {})); - } - - /** @test */ - public function shouldCallFactoryIfThenIsInvoked() - { - $factory = $this->createCallableMock(); - $factory - ->expects($this->once()) - ->method('__invoke'); - - When::lazy($factory) - ->then(); - } -}