[BUGFIX] Reduce expensive calls in AbstractMenuContentObject
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Unit / ContentObject / Menu / AbstractMenuContentObjectTest.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject\Menu;
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 use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
17 use TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject;
18
19 /**
20 * Test case
21 *
22 */
23 class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
24 {
25 /**
26 * @var \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject
27 */
28 protected $subject = null;
29
30 /**
31 * Set up this testcase
32 */
33 protected function setUp()
34 {
35 $proxyClassName = $this->buildAccessibleProxy(\TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::class);
36 $this->subject = $this->getMockForAbstractClass($proxyClassName);
37 $GLOBALS['TYPO3_DB'] = $this->getMock(\TYPO3\CMS\Core\Database\DatabaseConnection::class);
38 $GLOBALS['TSFE'] = $this->getMock(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class, [], [$GLOBALS['TYPO3_CONF_VARS'], 1, 1]);
39 $GLOBALS['TSFE']->cObj = new \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer();
40 $GLOBALS['TSFE']->page = [];
41 }
42
43 ////////////////////////////////
44 // Tests concerning sectionIndex
45 ////////////////////////////////
46 /**
47 * Prepares a test for the method sectionIndex
48 *
49 * @return void
50 */
51 protected function prepareSectionIndexTest()
52 {
53 $this->subject->sys_page = $this->getMock(\TYPO3\CMS\Frontend\Page\PageRepository::class);
54 $this->subject->parent_cObj = $this->getMock(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
55 }
56
57 /**
58 * @test
59 */
60 public function sectionIndexReturnsEmptyArrayIfTheRequestedPageCouldNotBeFetched()
61 {
62 $this->prepareSectionIndexTest();
63 $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue(null));
64 $result = $this->subject->_call('sectionIndex', 'field');
65 $this->assertEquals($result, []);
66 }
67
68 /**
69 * @test
70 */
71 public function sectionIndexUsesTheInternalIdIfNoPageIdWasGiven()
72 {
73 $this->prepareSectionIndexTest();
74 $this->subject->id = 10;
75 $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue(null))->with(10);
76 $result = $this->subject->_call('sectionIndex', 'field');
77 $this->assertEquals($result, []);
78 }
79
80 /**
81 * @test
82 * @expectedException UnexpectedValueException
83 */
84 public function sectionIndexThrowsAnExceptionIfTheInternalQueryFails()
85 {
86 $this->prepareSectionIndexTest();
87 $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue([]));
88 $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->will($this->returnValue(0));
89 $this->subject->_call('sectionIndex', 'field');
90 }
91
92 /**
93 * @test
94 */
95 public function sectionIndexReturnsOverlaidRowBasedOnTheLanguageOfTheGivenPage()
96 {
97 $this->prepareSectionIndexTest();
98 $this->subject->mconf['sectionIndex.']['type'] = 'all';
99 $GLOBALS['TSFE']->sys_language_contentOL = 1;
100 $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue(['_PAGES_OVERLAY_LANGUAGE' => 1]));
101 $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->will($this->returnValue(1));
102 $GLOBALS['TYPO3_DB']->expects($this->exactly(2))->method('sql_fetch_assoc')->will($this->onConsecutiveCalls($this->returnValue(['uid' => 0, 'header' => 'NOT_OVERLAID']), $this->returnValue(false)));
103 $this->subject->sys_page->expects($this->once())->method('getRecordOverlay')->will($this->returnValue(['uid' => 0, 'header' => 'OVERLAID']));
104 $result = $this->subject->_call('sectionIndex', 'field');
105 $this->assertEquals($result[0]['title'], 'OVERLAID');
106 }
107
108 /**
109 * @return array
110 */
111 public function sectionIndexFiltersDataProvider()
112 {
113 return [
114 'unfiltered fields' => [
115 1,
116 [
117 'sectionIndex' => 1,
118 'header' => 'foo',
119 'header_layout' => 1
120 ]
121 ],
122 'with unset section index' => [
123 0,
124 [
125 'sectionIndex' => 0,
126 'header' => 'foo',
127 'header_layout' => 1
128 ]
129 ],
130 'with unset header' => [
131 0,
132 [
133 'sectionIndex' => 1,
134 'header' => '',
135 'header_layout' => 1
136 ]
137 ],
138 'with header layout 100' => [
139 0,
140 [
141 'sectionIndex' => 1,
142 'header' => 'foo',
143 'header_layout' => 100
144 ]
145 ]
146 ];
147 }
148
149 /**
150 * @test
151 * @dataProvider sectionIndexFiltersDataProvider
152 * @param int $expectedAmount
153 * @param array $dataRow
154 */
155 public function sectionIndexFilters($expectedAmount, array $dataRow)
156 {
157 $this->prepareSectionIndexTest();
158 $this->subject->mconf['sectionIndex.']['type'] = 'header';
159 $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue([]));
160 $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->will($this->returnValue(1));
161 $GLOBALS['TYPO3_DB']->expects($this->exactly(2))->method('sql_fetch_assoc')->will($this->onConsecutiveCalls($this->returnValue($dataRow), $this->returnValue(false)));
162 $result = $this->subject->_call('sectionIndex', 'field');
163 $this->assertCount($expectedAmount, $result);
164 }
165
166 /**
167 * @return array
168 */
169 public function sectionIndexQueriesWithDifferentColPosDataProvider()
170 {
171 return [
172 'no configuration' => [
173 [],
174 'colPos=0'
175 ],
176 'with useColPos 2' => [
177 ['useColPos' => 2],
178 'colPos=2'
179 ],
180 'with useColPos -1' => [
181 ['useColPos' => -1],
182 ''
183 ],
184 'with stdWrap useColPos' => [
185 [
186 'useColPos.' => [
187 'wrap' => '2|'
188 ]
189 ],
190 'colPos=2'
191 ]
192 ];
193 }
194
195 /**
196 * @test
197 * @dataProvider sectionIndexQueriesWithDifferentColPosDataProvider
198 * @param array $configuration
199 * @param string $whereClausePrefix
200 */
201 public function sectionIndexQueriesWithDifferentColPos($configuration, $whereClausePrefix)
202 {
203 $this->prepareSectionIndexTest();
204 $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue([]));
205 $this->subject->mconf['sectionIndex.'] = $configuration;
206 $queryConfiguration = [
207 'pidInList' => 12,
208 'orderBy' => 'field',
209 'languageField' => 'sys_language_uid',
210 'where' => $whereClausePrefix
211 ];
212 $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->with('tt_content', $queryConfiguration)->will($this->returnValue(1));
213 $this->subject->_call('sectionIndex', 'field', 12);
214 }
215
216 ////////////////////////////////////
217 // Tests concerning menu item states
218 ////////////////////////////////////
219 /**
220 * @return array
221 */
222 public function ifsubHasToCheckExcludeUidListDataProvider()
223 {
224 return [
225 'none excluded' => [
226 [12, 34, 56],
227 '1, 23, 456',
228 true
229 ],
230 'one excluded' => [
231 [1, 234, 567],
232 '1, 23, 456',
233 true
234 ],
235 'three excluded' => [
236 [1, 23, 456],
237 '1, 23, 456',
238 false
239 ],
240 'empty excludeList' => [
241 [1, 123, 45],
242 '',
243 true
244 ],
245 'empty menu' => [
246 [],
247 '1, 23, 456',
248 false
249 ],
250 ];
251 }
252
253 /**
254 * @test
255 * @dataProvider ifsubHasToCheckExcludeUidListDataProvider
256 * @param array $menuItems
257 * @param string $excludeUidList
258 * @param bool $expectedResult
259 */
260 public function ifsubHasToCheckExcludeUidList($menuItems, $excludeUidList, $expectedResult)
261 {
262 $menu = [];
263 foreach ($menuItems as $page) {
264 $menu[] = ['uid' => $page];
265 }
266 $runtimeCacheMock = $this->getMockBuilder(VariableFrontend::class)->setMethods(['get', 'set'])->disableOriginalConstructor()->getMock();
267 $runtimeCacheMock->expects($this->once())->method('get')->with($this->anything())->willReturn(false);
268 $runtimeCacheMock->expects($this->once())->method('set')->with($this->anything(), ['result' => $expectedResult]);
269 $this->subject = $this->getMockBuilder(AbstractMenuContentObject::class)->setMethods(['getRuntimeCache'])->getMockForAbstractClass();
270 $this->subject->expects($this->once())->method('getRuntimeCache')->willReturn($runtimeCacheMock);
271 $this->prepareSectionIndexTest();
272 $this->subject->parent_cObj = $this->getMock(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class, []);
273
274 $this->subject->sys_page->expects($this->once())->method('getMenu')->will($this->returnValue($menu));
275 $this->subject->menuArr = [
276 0 => ['uid' => 1]
277 ];
278 $this->subject->conf['excludeUidList'] = $excludeUidList;
279
280 $this->assertEquals($expectedResult, $this->subject->isItemState('IFSUB', 0));
281 }
282 }