a922a897419443ca73368965f227f74a0d334a6e
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / SignalSlot / DispatcherTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\SignalSlot;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException;
18 use TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException;
19 use TYPO3\CMS\Extbase\Tests\Unit\SignalSlot\Fixtures\OnlyClassNameSpecifiedFixture;
20 use TYPO3\CMS\Extbase\Tests\Unit\SignalSlot\Fixtures\SlotMethodDoesNotExistFixture;
21
22 /**
23 * Test case
24 */
25 class DispatcherTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
26 {
27 /**
28 * Subject is not notice free, disable E_NOTICES
29 */
30 protected static $suppressNotices = true;
31
32 /**
33 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface
34 */
35 protected $signalSlotDispatcher;
36
37 protected function setUp()
38 {
39 $accessibleClassName = $this->getAccessibleMock(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class, ['dummy']);
40 $this->signalSlotDispatcher = new $accessibleClassName();
41 }
42
43 /**
44 * @test
45 */
46 public function connectAllowsForConnectingASlotWithASignal()
47 {
48 $mockSignal = $this->getMockBuilder('ClassA')
49 ->setMethods(['emitSomeSignal'])
50 ->getMock();
51 $mockSlot = $this->getMockBuilder('ClassB')
52 ->setMethods(['someSlotMethod'])
53 ->getMock();
54 $this->signalSlotDispatcher->connect(get_class($mockSignal), 'emitSomeSignal', get_class($mockSlot), 'someSlotMethod', true);
55 $expectedSlots = [
56 ['class' => get_class($mockSlot), 'method' => 'someSlotMethod', 'object' => null, 'passSignalInformation' => true]
57 ];
58 $this->assertSame($expectedSlots, $this->signalSlotDispatcher->getSlots(get_class($mockSignal), 'emitSomeSignal'));
59 }
60
61 /**
62 * @test
63 */
64 public function connectAlsoAcceptsObjectsInPlaceOfTheClassName()
65 {
66 $mockSignal = $this->getMockBuilder('ClassA')
67 ->setMethods(['emitSomeSignal'])
68 ->getMock();
69 $mockSlot = $this->getMockBuilder('ClassB')
70 ->setMethods(['someSlotMethod'])
71 ->getMock();
72 $this->signalSlotDispatcher->connect(get_class($mockSignal), 'emitSomeSignal', $mockSlot, 'someSlotMethod', true);
73 $expectedSlots = [
74 ['class' => null, 'method' => 'someSlotMethod', 'object' => $mockSlot, 'passSignalInformation' => true]
75 ];
76 $this->assertSame($expectedSlots, $this->signalSlotDispatcher->getSlots(get_class($mockSignal), 'emitSomeSignal'));
77 }
78
79 /**
80 * @test
81 */
82 public function connectAlsoAcceptsClosuresActingAsASlot()
83 {
84 $mockSignal = $this->getMockBuilder('ClassA')
85 ->setMethods(['emitSomeSignal'])
86 ->getMock();
87 $mockSlot = function () {
88 };
89 $this->signalSlotDispatcher->connect(get_class($mockSignal), 'emitSomeSignal', $mockSlot, 'foo', true);
90 $expectedSlots = [
91 ['class' => null, 'method' => '__invoke', 'object' => $mockSlot, 'passSignalInformation' => true]
92 ];
93 $this->assertSame($expectedSlots, $this->signalSlotDispatcher->getSlots(get_class($mockSignal), 'emitSomeSignal'));
94 }
95
96 /**
97 * @test
98 */
99 public function dispatchPassesTheSignalArgumentsToTheSlotMethod()
100 {
101 $arguments = [];
102 $mockSlot = function () use (&$arguments) {
103 $arguments = func_get_args();
104 };
105 $this->signalSlotDispatcher->connect('Foo', 'bar', $mockSlot, null, false);
106 $this->signalSlotDispatcher->dispatch('Foo', 'bar', ['bar', 'quux']);
107 $this->assertSame(['bar', 'quux'], $arguments);
108 }
109
110 /**
111 * @test
112 */
113 public function dispatchRetrievesSlotInstanceFromTheObjectManagerIfOnlyAClassNameWasSpecified()
114 {
115 $slotClassName = OnlyClassNameSpecifiedFixture::class;
116 $mockSlot = new OnlyClassNameSpecifiedFixture();
117 $mockObjectManager = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
118 $mockObjectManager->expects($this->once())->method('isRegistered')->with($slotClassName)->will($this->returnValue(true));
119 $mockObjectManager->expects($this->once())->method('get')->with($slotClassName)->will($this->returnValue($mockSlot));
120 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
121 $this->signalSlotDispatcher->_set('isInitialized', true);
122 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $slotClassName, 'slot', false);
123 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', ['bar', 'quux']);
124 $this->assertSame($mockSlot->arguments, ['bar', 'quux']);
125 }
126
127 /**
128 * @test
129 */
130 public function dispatchHandsOverArgumentsReturnedByAFormerSlot()
131 {
132 $this->signalSlotDispatcher->_set('isInitialized', true);
133
134 $firstMockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
135 $firstMockSlot->expects($this->once())
136 ->method('slot')
137 ->will($this->returnCallback(
138 function ($foo, $baz) {
139 return ['modified_' . $foo, 'modified_' . $baz];
140 }
141 ));
142
143 $secondMockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
144 $secondMockSlot->expects($this->once())
145 ->method('slot')
146 ->with('modified_bar', 'modified_quux');
147
148 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot', false);
149 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot', false);
150
151 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', ['bar', 'quux']);
152 }
153
154 /**
155 * @test
156 */
157 public function dispatchHandsOverArgumentsReturnedByAFormerSlotWithoutInterferingWithSignalSlotInformation()
158 {
159 $this->signalSlotDispatcher->_set('isInitialized', true);
160
161 $firstMockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
162 $firstMockSlot->expects($this->once())
163 ->method('slot')
164 ->will($this->returnCallback(
165 function ($foo, $baz) {
166 return ['modified_' . $foo, 'modified_' . $baz];
167 }
168 ));
169
170 $secondMockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
171 $secondMockSlot->expects($this->once())
172 ->method('slot')
173 ->with('modified_bar', 'modified_quux');
174
175 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot');
176 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot');
177
178 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', ['bar', 'quux']);
179 }
180
181 /**
182 * @test
183 */
184 public function dispatchHandsOverFormerArgumentsIfPreviousSlotDoesNotReturnAnything()
185 {
186 $this->signalSlotDispatcher->_set('isInitialized', true);
187
188 $firstMockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
189 $firstMockSlot->expects($this->once())
190 ->method('slot')
191 ->will($this->returnCallback(
192 function ($foo, $baz) {
193 return ['modified_' . $foo, 'modified_' . $baz];
194 }
195 ));
196
197 $secondMockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
198 $secondMockSlot->expects($this->once())
199 ->method('slot');
200
201 $thirdMockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
202 $thirdMockSlot->expects($this->once())
203 ->method('slot')
204 ->with('modified_bar', 'modified_quux');
205
206 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot');
207 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot');
208 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $thirdMockSlot, 'slot');
209
210 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', ['bar', 'quux']);
211 }
212
213 /**
214 * @test
215 */
216 public function dispatchThrowsAnExceptionIfTheSlotReturnsNonArray()
217 {
218 $this->expectException(InvalidSlotReturnException::class);
219 $this->expectExceptionCode(1376683067);
220 $this->signalSlotDispatcher->_set('isInitialized', true);
221
222 $mockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
223 $mockSlot->expects($this->once())
224 ->method('slot')
225 ->will($this->returnCallback(
226 function () {
227 return 'string';
228 }
229 ));
230
231 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $mockSlot, 'slot', false);
232 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', ['bar', 'quux']);
233 }
234
235 /**
236 * @test
237 */
238 public function dispatchThrowsAnExceptionIfTheSlotReturnsDifferentNumberOfItems()
239 {
240 $this->expectException(InvalidSlotReturnException::class);
241 $this->expectExceptionCode(1376683066);
242 $this->signalSlotDispatcher->_set('isInitialized', true);
243
244 $mockSlot = $this->createMock(\TYPO3\CMS\Extbase\Tests\Fixture\SlotFixture::class);
245 $mockSlot->expects($this->once())
246 ->method('slot')
247 ->will($this->returnCallback(
248 function () {
249 return [1, 2, 3];
250 }
251 ));
252
253 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $mockSlot, 'slot', false);
254 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', ['bar', 'quux']);
255 }
256
257 /**
258 * @test
259 */
260 public function dispatchThrowsAnExceptionIfTheSpecifiedClassOfASlotIsUnknown()
261 {
262 $this->expectException(InvalidSlotException::class);
263 $this->expectExceptionCode(1245673367);
264 $mockObjectManager = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
265 $mockObjectManager->expects($this->once())->method('isRegistered')->with('NonExistingClassName')->will($this->returnValue(false));
266 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
267 $this->signalSlotDispatcher->_set('isInitialized', true);
268 $this->signalSlotDispatcher->connect('Foo', 'emitBar', 'NonExistingClassName', 'slot', true);
269 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', []);
270 }
271
272 /**
273 * @test
274 */
275 public function dispatchThrowsAnExceptionIfTheSpecifiedSlotMethodDoesNotExist()
276 {
277 $this->expectException(InvalidSlotException::class);
278 $this->expectExceptionCode(1245673368);
279 $slotClassName = SlotMethodDoesNotExistFixture::class;
280 $mockSlot = new SlotMethodDoesNotExistFixture();
281 $mockObjectManager = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
282 $mockObjectManager->expects($this->once())->method('isRegistered')->with($slotClassName)->will($this->returnValue(true));
283 $mockObjectManager->expects($this->once())->method('get')->with($slotClassName)->will($this->returnValue($mockSlot));
284 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
285 $this->signalSlotDispatcher->_set('isInitialized', true);
286 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $slotClassName, 'unknownMethodName', true);
287 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', ['bar', 'quux']);
288 $this->assertSame($mockSlot->arguments, ['bar', 'quux']);
289 }
290
291 /**
292 * @test
293 */
294 public function dispatchPassesFirstArgumentContainingSlotInformationIfTheConnectionStatesSo()
295 {
296 $arguments = [];
297 $mockSlot = function () use (&$arguments) {
298 $arguments = func_get_args();
299 };
300 $mockObjectManager = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
301 $this->signalSlotDispatcher->connect('SignalClassName', 'methodName', $mockSlot, null, true);
302 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
303 $this->signalSlotDispatcher->_set('isInitialized', true);
304 $this->signalSlotDispatcher->dispatch('SignalClassName', 'methodName', ['bar', 'quux']);
305 $this->assertSame(['bar', 'quux', 'SignalClassName::methodName'], $arguments);
306 }
307
308 /**
309 * @test
310 */
311 public function connectThrowsInvalidArgumentExceptionIfSlotMethodNameIsEmptyAndSlotClassNameIsNoClosure()
312 {
313 $this->expectException(\InvalidArgumentException::class);
314 $this->expectExceptionCode(1229531659);
315 $this->signalSlotDispatcher->connect('ClassA', 'emitSomeSignal', 'ClassB', '');
316 }
317
318 /**
319 * @test
320 */
321 public function dispatchReturnsEmptyArrayIfSignalNameAndOrSignalClassNameIsNotRegistered()
322 {
323 $this->assertSame([], $this->signalSlotDispatcher->dispatch('ClassA', 'someNotRegisteredSignalName'));
324 }
325
326 /**
327 * @test
328 */
329 public function dispatchReturnsEmptyArrayIfSignalDoesNotProvideAnyArguments()
330 {
331 $this->assertSame([], $this->signalSlotDispatcher->dispatch('ClassA', 'emitSomeSignal'));
332 }
333
334 /**
335 * @test
336 */
337 public function dispatchReturnsArgumentsArrayAsIsIfSignalIsNotRegistered()
338 {
339 $arguments = [
340 42,
341 'a string',
342 new \stdClass()
343 ];
344 $this->assertSame($arguments, $this->signalSlotDispatcher->dispatch('ClassA', 'emitSomeSignal', $arguments));
345 }
346
347 /**
348 * @test
349 */
350 public function dispatchThrowsInvalidSlotExceptionIfObjectManagerOfSignalSlotDispatcherIsNotSet()
351 {
352 $this->expectException(InvalidSlotException::class);
353 $this->expectExceptionCode(1298113624);
354 $this->signalSlotDispatcher->_set('isInitialized', true);
355 $this->signalSlotDispatcher->_set('objectManager', null);
356 $this->signalSlotDispatcher->_set('slots', ['ClassA' => ['emitSomeSignal' => [[]]]]);
357
358 $this->assertSame(null, $this->signalSlotDispatcher->dispatch('ClassA', 'emitSomeSignal'));
359 }
360 }