Passed
Pull Request — master (#14)
by Alex
02:11
created

getDispatchWillPreventEventPropagationIfItIsStoppedWithinAListenerData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArpTest\EventDispatcher;
6
7
use Arp\EventDispatcher\EventDispatcher;
8
use Arp\EventDispatcher\Listener\AddListenerAwareInterface;
9
use Arp\EventDispatcher\Listener\Exception\EventListenerException;
10
use Arp\EventDispatcher\Listener\ListenerProvider;
11
use PHPUnit\Framework\MockObject\MockObject;
12
use PHPUnit\Framework\TestCase;
13
use Psr\EventDispatcher\EventDispatcherInterface;
14
use Psr\EventDispatcher\StoppableEventInterface;
15
16
/**
17
 * @author  Alex Patterson <[email protected]>
18
 * @package ArpTest\EventDispatcher
19
 */
20
final class EventDispatcherTest extends TestCase
21
{
22
    /**
23
     * @var ListenerProvider|MockObject
24
     */
25
    private $listenerProvider;
26
27
    /**
28
     * Prepare the test dependencies.
29
     *
30
     * @return void
31
     */
32
    public function setUp(): void
33
    {
34
        $this->listenerProvider = $this->createMock(ListenerProvider::class);
35
    }
36
37
    /**
38
     * Ensure that the event manager implements EventDispatcherInterface.
39
     *
40
     * @covers \Arp\EventDispatcher\EventDispatcher
41
     */
42
    public function testImplementsEventDispatcherInterface(): void
43
    {
44
        $eventManager = new EventDispatcher($this->listenerProvider);
45
46
        $this->assertInstanceOf(EventDispatcherInterface::class, $eventManager);
47
    }
48
49
    /**
50
     * Ensure that the event manager implements AddListenerAwareInterface.
51
     *
52
     * @covers \Arp\EventDispatcher\EventDispatcher
53
     */
54
    public function testImplementsAddListenerAwareInterface(): void
55
    {
56
        $eventManager = new EventDispatcher($this->listenerProvider);
57
58
        $this->assertInstanceOf(AddListenerAwareInterface::class, $eventManager);
59
    }
60
61
    /**
62
     * If we call dispatch with a StoppableEventInterface that already has propagation stopped, no event listeners
63
     * should be triggered.
64
     *
65
     * @covers \Arp\EventDispatcher\EventDispatcher::dispatch
66
     * @covers \Arp\EventDispatcher\EventDispatcher::isPropagationStopped
67
     */
68
    public function testDispatchWillPreventEventPropagationIfProvidedEventHasPropagationStopped(): void
69
    {
70
        $eventDispatcher = new EventDispatcher($this->listenerProvider);
71
72
        /** @var StoppableEventInterface|MockObject $event */
73
        $event = $this->getMockForAbstractClass(StoppableEventInterface::class);
74
75
        $event->expects($this->once())
76
            ->method('isPropagationStopped')
77
            ->willReturn(true);
78
79
        $this->listenerProvider->expects($this->never())
80
            ->method('getListenersForEvent');
81
82
        $this->assertSame($event, $eventDispatcher->dispatch($event));
83
    }
84
85
    /**
86
     * Assert that the event propagation is stopped if we modify the StoppableEventInterface within an event.
87
     *
88
     * @param integer $listenerCount The number of event listeners attached to the dispatched event.
89
     * @param integer $stopIndex     The index that the event listener should stop propagation.
90
     *
91
     * @dataProvider getDispatchWillPreventEventPropagationIfItIsStoppedWithinAListenerData
92
     *
93
     * @covers \Arp\EventDispatcher\EventDispatcher::dispatch
94
     * @covers \Arp\EventDispatcher\EventDispatcher::isPropagationStopped
95
     */
96
    public function testDispatchWillNotPropagationEventIfItIsStoppedWithinAListener(
97
        int $listenerCount,
98
        int $stopIndex
99
    ): void {
100
        if ($stopIndex >= $listenerCount) {
101
            $this->fail(sprintf(
102
                'The stop index \'%d\' must be less than the number of event listeners \'%d\'.',
103
                $listenerCount,
104
                $stopIndex
105
            ));
106
        }
107
108
        $eventDispatcher = new EventDispatcher($this->listenerProvider);
109
110
        /** @var StoppableEventInterface|MockObject $event */
111
        $event = $this->getMockForAbstractClass(StoppableEventInterface::class);
112
113
        $eventListeners = [];
114
        $isStopped = [];
115
116
        for ($x = 0; $x < $listenerCount; $x++) {
117
            $eventListeners[] = static function (StoppableEventInterface $event) use ($x, $stopIndex) {
0 ignored issues
show
Unused Code introduced by
The import $x is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

117
            $eventListeners[] = static function (/** @scrutinizer ignore-unused */ StoppableEventInterface $event) use ($x, $stopIndex) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The import $stopIndex is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
118
            };
119
120
            if ($x < ($stopIndex + 1)) {
121
                $isStopped[] = ($x === $stopIndex);
122
            }
123
        }
124
125
        $this->listenerProvider->expects($this->once())
126
            ->method('getListenersForEvent')
127
            ->willReturn($eventListeners);
128
129
        $event->expects($this->exactly(1 + count($isStopped)))
130
            ->method('isPropagationStopped')
131
            ->willReturn(false, ...$isStopped);
132
133
        $this->assertSame($event, $eventDispatcher->dispatch($event));
134
    }
135
136
    /**
137
     * @return array
138
     */
139
    public function getDispatchWillPreventEventPropagationIfItIsStoppedWithinAListenerData(): array
140
    {
141
        return [
142
            [1, 0],
143
            [7, 2],
144
            [23, 20],
145
            [10, 4],
146
            [100, 40],
147
        ];
148
    }
149
150
    /**
151
     * Assert that dispatch() will invoke the require event listeners returned from the listener provider.
152
     *
153
     * @param object $event
154
     * @param int    $numberOfListeners
155
     *
156
     * @dataProvider getDispatchWillInvokeEventListenersForProvidedEventData
157
     *
158
     * @covers \Arp\EventDispatcher\EventDispatcher::dispatch
159
     * @covers \Arp\EventDispatcher\EventDispatcher::isPropagationStopped
160
     */
161
    public function testDispatchWillInvokeEventListenersForProvidedEvent($event, $numberOfListeners = 0): void
162
    {
163
        $eventDispatcher = new EventDispatcher($this->listenerProvider);
164
165
        $listeners = [];
166
167
        for ($x = 0; $x < $numberOfListeners; $x++) {
168
            $listeners[] = static function ($event) use ($x) {
0 ignored issues
show
Unused Code introduced by
The import $x is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
169
                get_class($event);
170
            };
171
        }
172
173
        $this->listenerProvider->expects($this->once())
174
            ->method('getListenersForEvent')
175
            ->willReturn($listeners);
176
177
        $result = $eventDispatcher->dispatch($event);
178
179
        $this->assertIsObject($result);
180
        $this->assertSame($result, $event);
181
    }
182
183
    /**
184
     * @return array
185
     */
186
    public function getDispatchWillInvokeEventListenersForProvidedEventData(): array
187
    {
188
        return [
189
            [
190
                new \stdClass(),
191
                7,
192
            ],
193
            [
194
                $this->getMockForAbstractClass(StoppableEventInterface::class)
195
                    ->expects($this->exactly(5))
196
                    ->method('isPropagationStopped')
197
                    ->willReturn(false),
198
                5,
199
            ],
200
        ];
201
    }
202
203
    /**
204
     * Assert that calls to addListenerForEvent() proxies to the internal ListenerProvider.
205
     *
206
     * @covers \Arp\EventDispatcher\EventDispatcher::addListenerForEvent
207
     *
208
     * @throws EventListenerException
209
     */
210
    public function testAddListenerForEventWillProxyToInternalListenerProvider(): void
211
    {
212
        $dispatcher = new EventDispatcher($this->listenerProvider);
213
214
        $event = new \stdClass();
215
        $priority = 10;
216
        $listener = static function (\stdClass $event): void {
217
            echo $event->name;
218
        };
219
220
        $this->listenerProvider->expects($this->once())
221
            ->method('addListenerForEvent')
222
            ->with($event, $listener, $priority);
223
224
        $dispatcher->addListenerForEvent($event, $listener, $priority);
225
    }
226
227
    /**
228
     * Assert that calls to addListenerForEvent() proxies to the internal ListenerProvider.
229
     *
230
     * @covers \Arp\EventDispatcher\EventDispatcher::addListenersForEvent
231
     *
232
     * @throws EventListenerException
233
     */
234
    public function testAddListenersForEventWillProxyToInternalListenerProvider(): void
235
    {
236
        $dispatcher = new EventDispatcher($this->listenerProvider);
237
238
        $event = new \stdClass();
239
        $priority = 100;
240
        $listeners = [
241
            static function (\stdClass $event): void {
242
                echo $event->name;
243
            },
244
            static function (\stdClass $event): void {
245
                echo $event->name;
246
            },
247
        ];
248
249
        $this->listenerProvider->expects($this->once())
250
            ->method('addListenersForEvent')
251
            ->with($event, $listeners, $priority);
252
253
        $dispatcher->addListenersForEvent($event, $listeners, $priority);
254
    }
255
}
256