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