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