[FEATURE] Allow signalSlots to modify arguments
[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 * Copyright notice
6 *
7 * (c) 2010-2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * Testcase for the Signal Dispatcher Class
32 *
33 * @author Felix Oertel <f@oer.tel>
34 */
35 class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
36
37 /**
38 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher|\PHPUnit_Framework_MockObject_MockObject|\Tx_Phpunit_Interface_AccessibleObject
39 */
40 protected $signalSlotDispatcher;
41
42 public function setUp() {
43 $accessibleClassName = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher', array('dummy'));
44 $this->signalSlotDispatcher = new $accessibleClassName();
45 }
46
47 /**
48 * @test
49 */
50 public function connectAllowsForConnectingASlotWithASignal() {
51 $mockSignal = $this->getMock('ClassA', array('emitSomeSignal'));
52 $mockSlot = $this->getMock('ClassB', array('someSlotMethod'));
53 $this->signalSlotDispatcher->connect(get_class($mockSignal), 'emitSomeSignal', get_class($mockSlot), 'someSlotMethod', TRUE);
54 $expectedSlots = array(
55 array('class' => get_class($mockSlot), 'method' => 'someSlotMethod', 'object' => NULL, 'passSignalInformation' => TRUE)
56 );
57 $this->assertSame($expectedSlots, $this->signalSlotDispatcher->getSlots(get_class($mockSignal), 'emitSomeSignal'));
58 }
59
60 /**
61 * @test
62 */
63 public function connectAlsoAcceptsObjectsInPlaceOfTheClassName() {
64 $mockSignal = $this->getMock('ClassA', array('emitSomeSignal'));
65 $mockSlot = $this->getMock('ClassB', array('someSlotMethod'));
66 $this->signalSlotDispatcher->connect(get_class($mockSignal), 'emitSomeSignal', $mockSlot, 'someSlotMethod', TRUE);
67 $expectedSlots = array(
68 array('class' => NULL, 'method' => 'someSlotMethod', 'object' => $mockSlot, 'passSignalInformation' => TRUE)
69 );
70 $this->assertSame($expectedSlots, $this->signalSlotDispatcher->getSlots(get_class($mockSignal), 'emitSomeSignal'));
71 }
72
73 /**
74 * @test
75 */
76 public function connectAlsoAcceptsClosuresActingAsASlot() {
77 $mockSignal = $this->getMock('ClassA', array('emitSomeSignal'));
78 $mockSlot = function () {
79 };
80 $this->signalSlotDispatcher->connect(get_class($mockSignal), 'emitSomeSignal', $mockSlot, 'foo', TRUE);
81 $expectedSlots = array(
82 array('class' => NULL, 'method' => '__invoke', 'object' => $mockSlot, 'passSignalInformation' => TRUE)
83 );
84 $this->assertSame($expectedSlots, $this->signalSlotDispatcher->getSlots(get_class($mockSignal), 'emitSomeSignal'));
85 }
86
87 /**
88 * @test
89 */
90 public function dispatchPassesTheSignalArgumentsToTheSlotMethod() {
91 $arguments = array();
92 $mockSlot = function () use (&$arguments) {
93 ($arguments = func_get_args());
94 };
95 $this->signalSlotDispatcher->connect('Foo', 'bar', $mockSlot, NULL, FALSE);
96 $this->signalSlotDispatcher->dispatch('Foo', 'bar', array('foo' => 'bar', 'baz' => 'quux'));
97 $this->assertSame(array('bar', 'quux'), $arguments);
98 }
99
100 /**
101 * @test
102 */
103 public function dispatchRetrievesSlotInstanceFromTheObjectManagerIfOnlyAClassNameWasSpecified() {
104 $slotClassName = uniqid('Mock_');
105 eval('class ' . $slotClassName . ' { function slot($foo, $baz) { $this->arguments = array($foo, $baz); } }');
106 $mockSlot = new $slotClassName();
107 $mockObjectManager = $this->getMock('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerInterface');
108 $mockObjectManager->expects($this->once())->method('isRegistered')->with($slotClassName)->will($this->returnValue(TRUE));
109 $mockObjectManager->expects($this->once())->method('get')->with($slotClassName)->will($this->returnValue($mockSlot));
110 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
111 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
112 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $slotClassName, 'slot', FALSE);
113 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('foo' => 'bar', 'baz' => 'quux'));
114 $this->assertSame($mockSlot->arguments, array('bar', 'quux'));
115 }
116
117 /**
118 * @test
119 */
120 public function dispatchHandsOverArgumentsReturnedByAFormerSlot() {
121 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
122
123 $firstMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
124 $firstMockSlot->expects($this->once())
125 ->method('slot')
126 ->will($this->returnCallback(
127 function($foo, $baz) {
128 return array('modified_' . $foo, 'modified_' . $baz);}
129 ));
130
131 $secondMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
132 $secondMockSlot->expects($this->once())
133 ->method('slot')
134 ->with('modified_bar', 'modified_quux');
135
136
137 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot', FALSE);
138 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot', FALSE);
139
140 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
141 }
142
143 /**
144 * @test
145 */
146 public function dispatchHandsOverArgumentsReturnedByAFormerSlotWithoutInterferingWithSignalSlotInformation() {
147 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
148
149 $firstMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
150 $firstMockSlot->expects($this->once())
151 ->method('slot')
152 ->will($this->returnCallback(
153 function($foo, $baz) {
154 return array('modified_' . $foo, 'modified_' . $baz);}
155 ));
156
157 $secondMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
158 $secondMockSlot->expects($this->once())
159 ->method('slot')
160 ->with('modified_bar', 'modified_quux');
161
162 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot');
163 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot');
164
165 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
166 }
167
168 /**
169 * @test
170 */
171 public function dispatchHandsOverFormerArgumentsIfPreviousSlotDoesNotReturnAnything() {
172 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
173
174 $firstMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
175 $firstMockSlot->expects($this->once())
176 ->method('slot')
177 ->will($this->returnCallback(
178 function($foo, $baz) {
179 return array('modified_' . $foo, 'modified_' . $baz);}
180 ));
181
182 $secondMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
183 $secondMockSlot->expects($this->once())
184 ->method('slot');
185
186 $thirdMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
187 $thirdMockSlot->expects($this->once())
188 ->method('slot')
189 ->with('modified_bar', 'modified_quux');
190
191
192 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot');
193 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot');
194 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $thirdMockSlot, 'slot');
195
196 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
197 }
198
199 /**
200 * @test
201 * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException
202 */
203 public function dispatchThrowsAnExceptionIfTheSlotReturnsNonArray() {
204 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
205
206 $mockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
207 $mockSlot->expects($this->once())
208 ->method('slot')
209 ->will($this->returnCallback(
210 function() {
211 return 'string';}
212 ));
213
214 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $mockSlot, 'slot', FALSE);
215 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('foo' => 'bar', 'baz' => 'quux'));
216 }
217
218 /**
219 * @test
220 * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException
221 */
222 public function dispatchThrowsAnExceptionIfTheSlotReturnsDifferentNumberOfItems() {
223 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
224
225 $mockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
226 $mockSlot->expects($this->once())
227 ->method('slot')
228 ->will($this->returnCallback(
229 function() {
230 return array(1, 2, 3);}
231 ));
232
233 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $mockSlot, 'slot', FALSE);
234 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
235 }
236
237 /**
238 * @test
239 * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
240 */
241 public function dispatchThrowsAnExceptionIfTheSpecifiedClassOfASlotIsUnknown() {
242 $mockObjectManager = $this->getMock('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerInterface');
243 $mockObjectManager->expects($this->once())->method('isRegistered')->with('NonExistingClassName')->will($this->returnValue(FALSE));
244 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
245 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
246 $this->signalSlotDispatcher->connect('Foo', 'emitBar', 'NonExistingClassName', 'slot', TRUE);
247 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array());
248 }
249
250 /**
251 * @test
252 * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
253 */
254 public function dispatchThrowsAnExceptionIfTheSpecifiedSlotMethodDoesNotExist() {
255 $slotClassName = uniqid('Mock_');
256 eval('class ' . $slotClassName . ' { function slot($foo, $baz) { $this->arguments = array($foo, $baz); } }');
257 $mockSlot = new $slotClassName();
258 $mockObjectManager = $this->getMock('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerInterface');
259 $mockObjectManager->expects($this->once())->method('isRegistered')->with($slotClassName)->will($this->returnValue(TRUE));
260 $mockObjectManager->expects($this->once())->method('get')->with($slotClassName)->will($this->returnValue($mockSlot));
261 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
262 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
263 $this->signalSlotDispatcher->connect('Foo', 'emitBar', $slotClassName, 'unknownMethodName', TRUE);
264 $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('foo' => 'bar', 'baz' => 'quux'));
265 $this->assertSame($mockSlot->arguments, array('bar', 'quux'));
266 }
267
268 /**
269 * @test
270 */
271 public function dispatchPassesFirstArgumentContainingSlotInformationIfTheConnectionStatesSo() {
272 $arguments = array();
273 $mockSlot = function () use (&$arguments) {
274 ($arguments = func_get_args());
275 };
276 $mockObjectManager = $this->getMock('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerInterface');
277 $this->signalSlotDispatcher->connect('SignalClassName', 'methodName', $mockSlot, NULL, TRUE);
278 $this->signalSlotDispatcher->_set('objectManager', $mockObjectManager);
279 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
280 $this->signalSlotDispatcher->dispatch('SignalClassName', 'methodName', array('foo' => 'bar', 'baz' => 'quux'));
281 $this->assertSame(array('bar', 'quux', 'SignalClassName::methodName'), $arguments);
282 }
283
284 /**
285 * @test
286 * @expectedException \InvalidArgumentException
287 * @author Alexander Schnitzler <alex.schnitzler@typovision.de>
288 */
289 public function connectThrowsInvalidArgumentExceptionIfSlotMethodNameIsEmptyAndSlotClassNameIsNoClosure() {
290 $this->signalSlotDispatcher->connect('ClassA', 'emitSomeSignal', 'ClassB', '');
291 }
292
293 /**
294 * @test
295 * @author Alexander Schnitzler <alex.schnitzler@typovision.de>
296 */
297 public function dispatchReturnsVoidIfSignalNameAndOrSignalClassNameIsNotRegistered() {
298 $this->assertSame(NULL, $this->signalSlotDispatcher->dispatch('ClassA', 'emitSomeSignal'));
299 }
300
301 /**
302 * @test
303 * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
304 * @author Alexander Schnitzler <alex.schnitzler@typovision.de>
305 */
306 public function dispatchThrowsInvalidSlotExceptionIfObjectManagerOfSignalSlotDispatcherIsNotSet() {
307 $this->signalSlotDispatcher->_set('isInitialized', TRUE);
308 $this->signalSlotDispatcher->_set('objectManager', NULL);
309 $this->signalSlotDispatcher->_set('slots', array('ClassA' => array('emitSomeSignal' => array(array()))));
310
311 $this->assertSame(NULL, $this->signalSlotDispatcher->dispatch('ClassA', 'emitSomeSignal'));
312 }
313 }
314
315 ?>