1<?php
2
3namespace React\Promise\PromiseTest;
4
5use React\Promise;
6
7trait CancelTestTrait
8{
9    /**
10     * @return \React\Promise\PromiseAdapter\PromiseAdapterInterface
11     */
12    abstract public function getPromiseTestAdapter(callable $canceller = null);
13
14    /** @test */
15    public function cancelShouldCallCancellerWithResolverArguments()
16    {
17        $args = null;
18        $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $notify) use (&$args) {
19            $args = func_get_args();
20        });
21
22        $adapter->promise()->cancel();
23
24        $this->assertCount(3, $args);
25        $this->assertTrue(is_callable($args[0]));
26        $this->assertTrue(is_callable($args[1]));
27        $this->assertTrue(is_callable($args[2]));
28    }
29
30    /** @test */
31    public function cancelShouldCallCancellerWithoutArgumentsIfNotAccessed()
32    {
33        $args = null;
34        $adapter = $this->getPromiseTestAdapter(function () use (&$args) {
35            $args = func_num_args();
36        });
37
38        $adapter->promise()->cancel();
39
40        $this->assertSame(0, $args);
41    }
42
43    /** @test */
44    public function cancelShouldFulfillPromiseIfCancellerFulfills()
45    {
46        $adapter = $this->getPromiseTestAdapter(function ($resolve) {
47            $resolve(1);
48        });
49
50        $mock = $this->createCallableMock();
51        $mock
52            ->expects($this->once())
53            ->method('__invoke')
54            ->with($this->identicalTo(1));
55
56        $adapter->promise()
57            ->then($mock, $this->expectCallableNever());
58
59        $adapter->promise()->cancel();
60    }
61
62    /** @test */
63    public function cancelShouldRejectPromiseIfCancellerRejects()
64    {
65        $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) {
66            $reject(1);
67        });
68
69        $mock = $this->createCallableMock();
70        $mock
71            ->expects($this->once())
72            ->method('__invoke')
73            ->with($this->identicalTo(1));
74
75        $adapter->promise()
76            ->then($this->expectCallableNever(), $mock);
77
78        $adapter->promise()->cancel();
79    }
80
81    /** @test */
82    public function cancelShouldRejectPromiseWithExceptionIfCancellerThrows()
83    {
84        $e = new \Exception();
85
86        $adapter = $this->getPromiseTestAdapter(function () use ($e) {
87            throw $e;
88        });
89
90        $mock = $this->createCallableMock();
91        $mock
92            ->expects($this->once())
93            ->method('__invoke')
94            ->with($this->identicalTo($e));
95
96        $adapter->promise()
97            ->then($this->expectCallableNever(), $mock);
98
99        $adapter->promise()->cancel();
100    }
101
102    /** @test */
103    public function cancelShouldProgressPromiseIfCancellerNotifies()
104    {
105        $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $progress) {
106            $progress(1);
107        });
108
109        $mock = $this->createCallableMock();
110        $mock
111            ->expects($this->once())
112            ->method('__invoke')
113            ->with($this->identicalTo(1));
114
115        $adapter->promise()
116            ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
117
118        $adapter->promise()->cancel();
119    }
120
121    /** @test */
122    public function cancelShouldCallCancellerOnlyOnceIfCancellerResolves()
123    {
124        $mock = $this->createCallableMock();
125        $mock
126            ->expects($this->once())
127            ->method('__invoke')
128            ->will($this->returnCallback(function ($resolve) {
129                $resolve();
130            }));
131
132        $adapter = $this->getPromiseTestAdapter($mock);
133
134        $adapter->promise()->cancel();
135        $adapter->promise()->cancel();
136    }
137
138    /** @test */
139    public function cancelShouldHaveNoEffectIfCancellerDoesNothing()
140    {
141        $adapter = $this->getPromiseTestAdapter(function () {});
142
143        $adapter->promise()
144            ->then($this->expectCallableNever(), $this->expectCallableNever());
145
146        $adapter->promise()->cancel();
147        $adapter->promise()->cancel();
148    }
149
150    /** @test */
151    public function cancelShouldCallCancellerFromDeepNestedPromiseChain()
152    {
153        $mock = $this->createCallableMock();
154        $mock
155            ->expects($this->once())
156            ->method('__invoke');
157
158        $adapter = $this->getPromiseTestAdapter($mock);
159
160        $promise = $adapter->promise()
161            ->then(function () {
162                return new Promise\Promise(function () {});
163            })
164            ->then(function () {
165                $d = new Promise\Deferred();
166
167                return $d->promise();
168            })
169            ->then(function () {
170                return new Promise\Promise(function () {});
171            });
172
173        $promise->cancel();
174    }
175
176    /** @test */
177    public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled()
178    {
179        $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
180
181        $child1 = $adapter->promise()
182            ->then()
183            ->then();
184
185        $adapter->promise()
186            ->then();
187
188        $child1->cancel();
189    }
190
191    /** @test */
192    public function cancelShouldTriggerCancellerWhenAllChildrenCancel()
193    {
194        $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
195
196        $child1 = $adapter->promise()
197            ->then()
198            ->then();
199
200        $child2 = $adapter->promise()
201            ->then();
202
203        $child1->cancel();
204        $child2->cancel();
205    }
206
207    /** @test */
208    public function cancelShouldNotTriggerCancellerWhenCancellingOneChildrenMultipleTimes()
209    {
210        $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
211
212        $child1 = $adapter->promise()
213            ->then()
214            ->then();
215
216        $child2 = $adapter->promise()
217            ->then();
218
219        $child1->cancel();
220        $child1->cancel();
221    }
222
223    /** @test */
224    public function cancelShouldTriggerCancellerOnlyOnceWhenCancellingMultipleTimes()
225    {
226        $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
227
228        $adapter->promise()->cancel();
229        $adapter->promise()->cancel();
230    }
231
232    /** @test */
233    public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise()
234    {
235        $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
236
237        $adapter->promise()
238            ->then()
239            ->then();
240
241        $adapter->promise()
242            ->then();
243
244        $adapter->promise()->cancel();
245    }
246}
247