1<?php
2
3namespace React\Promise;
4
5class FunctionReduceTest extends TestCase
6{
7    protected function plus()
8    {
9        return function ($sum, $val) {
10            return $sum + $val;
11        };
12    }
13
14    protected function append()
15    {
16        return function ($sum, $val) {
17            return $sum . $val;
18        };
19    }
20
21    /** @test */
22    public function shouldReduceValuesWithoutInitialValue()
23    {
24        $mock = $this->createCallableMock();
25        $mock
26            ->expects($this->once())
27            ->method('__invoke')
28            ->with($this->identicalTo(6));
29
30        reduce(
31            [1, 2, 3],
32            $this->plus()
33        )->then($mock);
34    }
35
36    /** @test */
37    public function shouldReduceValuesWithInitialValue()
38    {
39        $mock = $this->createCallableMock();
40        $mock
41            ->expects($this->once())
42            ->method('__invoke')
43            ->with($this->identicalTo(7));
44
45        reduce(
46            [1, 2, 3],
47            $this->plus(),
48            1
49        )->then($mock);
50    }
51
52    /** @test */
53    public function shouldReduceValuesWithInitialPromise()
54    {
55        $mock = $this->createCallableMock();
56        $mock
57            ->expects($this->once())
58            ->method('__invoke')
59            ->with($this->identicalTo(7));
60
61        reduce(
62            [1, 2, 3],
63            $this->plus(),
64            resolve(1)
65        )->then($mock);
66    }
67
68    /** @test */
69    public function shouldReducePromisedValuesWithoutInitialValue()
70    {
71        $mock = $this->createCallableMock();
72        $mock
73            ->expects($this->once())
74            ->method('__invoke')
75            ->with($this->identicalTo(6));
76
77        reduce(
78            [resolve(1), resolve(2), resolve(3)],
79            $this->plus()
80        )->then($mock);
81    }
82
83    /** @test */
84    public function shouldReducePromisedValuesWithInitialValue()
85    {
86        $mock = $this->createCallableMock();
87        $mock
88            ->expects($this->once())
89            ->method('__invoke')
90            ->with($this->identicalTo(7));
91
92        reduce(
93            [resolve(1), resolve(2), resolve(3)],
94            $this->plus(),
95            1
96        )->then($mock);
97    }
98
99    /** @test */
100    public function shouldReducePromisedValuesWithInitialPromise()
101    {
102        $mock = $this->createCallableMock();
103        $mock
104            ->expects($this->once())
105            ->method('__invoke')
106            ->with($this->identicalTo(7));
107
108        reduce(
109            [resolve(1), resolve(2), resolve(3)],
110            $this->plus(),
111            resolve(1)
112        )->then($mock);
113    }
114
115    /** @test */
116    public function shouldReduceEmptyInputWithInitialValue()
117    {
118        $mock = $this->createCallableMock();
119        $mock
120            ->expects($this->once())
121            ->method('__invoke')
122            ->with($this->identicalTo(1));
123
124        reduce(
125            [],
126            $this->plus(),
127            1
128        )->then($mock);
129    }
130
131    /** @test */
132    public function shouldReduceEmptyInputWithInitialPromise()
133    {
134        $mock = $this->createCallableMock();
135        $mock
136            ->expects($this->once())
137            ->method('__invoke')
138            ->with($this->identicalTo(1));
139
140        reduce(
141            [],
142            $this->plus(),
143            resolve(1)
144        )->then($mock);
145    }
146
147    /** @test */
148    public function shouldRejectWhenInputContainsRejection()
149    {
150        $mock = $this->createCallableMock();
151        $mock
152            ->expects($this->once())
153            ->method('__invoke')
154            ->with($this->identicalTo(2));
155
156        reduce(
157            [resolve(1), reject(2), resolve(3)],
158            $this->plus(),
159            resolve(1)
160        )->then($this->expectCallableNever(), $mock);
161    }
162
163    /** @test */
164    public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseProvided()
165    {
166        // Note: this is different from when.js's behavior!
167        // In when.reduce(), this rejects with a TypeError exception (following
168        // JavaScript's [].reduce behavior.
169        // We're following PHP's array_reduce behavior and resolve with NULL.
170        $mock = $this->createCallableMock();
171        $mock
172            ->expects($this->once())
173            ->method('__invoke')
174            ->with($this->identicalTo(null));
175
176        reduce(
177            [],
178            $this->plus()
179        )->then($mock);
180    }
181
182    /** @test */
183    public function shouldAllowSparseArrayInputWithoutInitialValue()
184    {
185        $mock = $this->createCallableMock();
186        $mock
187            ->expects($this->once())
188            ->method('__invoke')
189            ->with($this->identicalTo(3));
190
191        reduce(
192            [null, null, 1, null, 1, 1],
193            $this->plus()
194        )->then($mock);
195    }
196
197    /** @test */
198    public function shouldAllowSparseArrayInputWithInitialValue()
199    {
200        $mock = $this->createCallableMock();
201        $mock
202            ->expects($this->once())
203            ->method('__invoke')
204            ->with($this->identicalTo(4));
205
206        reduce(
207            [null, null, 1, null, 1, 1],
208            $this->plus(),
209            1
210        )->then($mock);
211    }
212
213    /** @test */
214    public function shouldReduceInInputOrder()
215    {
216        $mock = $this->createCallableMock();
217        $mock
218            ->expects($this->once())
219            ->method('__invoke')
220            ->with($this->identicalTo('123'));
221
222        reduce(
223            [1, 2, 3],
224            $this->append(),
225            ''
226        )->then($mock);
227    }
228
229    /** @test */
230    public function shouldAcceptAPromiseForAnArray()
231    {
232        $mock = $this->createCallableMock();
233        $mock
234            ->expects($this->once())
235            ->method('__invoke')
236            ->with($this->identicalTo('123'));
237
238        reduce(
239            resolve([1, 2, 3]),
240            $this->append(),
241            ''
242        )->then($mock);
243    }
244
245    /** @test */
246    public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArray()
247    {
248        $mock = $this->createCallableMock();
249        $mock
250            ->expects($this->once())
251            ->method('__invoke')
252            ->with($this->identicalTo(1));
253
254        reduce(
255            resolve(1),
256            $this->plus(),
257            1
258        )->then($mock);
259    }
260
261    /** @test */
262    public function shouldProvideCorrectBasisValue()
263    {
264        $insertIntoArray = function ($arr, $val, $i) {
265            $arr[$i] = $val;
266
267            return $arr;
268        };
269
270        $d1 = new Deferred();
271        $d2 = new Deferred();
272        $d3 = new Deferred();
273
274        $mock = $this->createCallableMock();
275        $mock
276            ->expects($this->once())
277            ->method('__invoke')
278            ->with($this->identicalTo([1, 2, 3]));
279
280        reduce(
281            [$d1->promise(), $d2->promise(), $d3->promise()],
282            $insertIntoArray,
283            []
284        )->then($mock);
285
286        $d3->resolve(3);
287        $d1->resolve(1);
288        $d2->resolve(2);
289    }
290
291    /** @test */
292    public function shouldRejectWhenInputPromiseRejects()
293    {
294        $mock = $this->createCallableMock();
295        $mock
296            ->expects($this->once())
297            ->method('__invoke')
298            ->with($this->identicalTo(null));
299
300        reduce(
301            reject(),
302            $this->plus(),
303            1
304        )->then($this->expectCallableNever(), $mock);
305    }
306
307    /** @test */
308    public function shouldCancelInputPromise()
309    {
310        $mock = $this
311            ->getMockBuilder('React\Promise\CancellablePromiseInterface')
312            ->getMock();
313        $mock
314            ->expects($this->once())
315            ->method('cancel');
316
317        reduce(
318            $mock,
319            $this->plus(),
320            1
321        )->cancel();
322    }
323
324    /** @test */
325    public function shouldCancelInputArrayPromises()
326    {
327        $mock1 = $this
328            ->getMockBuilder('React\Promise\CancellablePromiseInterface')
329            ->getMock();
330        $mock1
331            ->expects($this->once())
332            ->method('cancel');
333
334        $mock2 = $this
335            ->getMockBuilder('React\Promise\CancellablePromiseInterface')
336            ->getMock();
337        $mock2
338            ->expects($this->once())
339            ->method('cancel');
340
341        reduce(
342            [$mock1, $mock2],
343            $this->plus(),
344            1
345        )->cancel();
346    }
347}
348