[FEATURE] Remove ViewHelper xmlns-attributes and specified html tag
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Tests / Unit / Core / Parser / TemplateParserTest.php
1 <?php
2 namespace TYPO3\CMS\Fluid\Tests\Unit\Core\Parser;
3
4 /* *
5 * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13 use TYPO3\CMS\Fluid\Core\Parser\TemplateParser;
14 use TYPO3\CMS\Core\Tests\AccessibleObjectInterface;
15
16 /**
17 * Testcase for TemplateParser.
18 *
19 * This is to at least half a system test, as it compares rendered results to
20 * expectations, and does not strictly check the parsing...
21 */
22 class TemplateParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
23
24 /**
25 * @test
26 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
27 */
28 public function parseThrowsExceptionWhenStringArgumentMissing() {
29 $templateParser = new \TYPO3\CMS\Fluid\Core\Parser\TemplateParser();
30 $templateParser->parse(123);
31 }
32
33 /**
34 * @test
35 */
36 public function extractNamespaceDefinitionsExtractsNamespacesCorrectly() {
37 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
38 $templateParser->_call('extractNamespaceDefinitions', ' \\{namespace f4=F7\\Rocks} {namespace f4=TYPO3\Rocks\Really}');
39 $expected = array(
40 'f' => \TYPO3\CMS\Fluid\ViewHelpers::class,
41 'f4' => 'TYPO3\Rocks\Really'
42 );
43 $this->assertEquals($expected, $templateParser->getNamespaces(), 'Namespaces do not match.');
44 }
45
46 /**
47 * @test
48 */
49 public function extractNamespaceDefinitionsExtractsXmlNamespacesCorrectly() {
50 $mockSettings = array(
51 'namespaces' => array(
52 'http://domain.tld/ns/my/viewhelpers' => 'My\Namespace',
53 'http://otherdomain.tld/ns/other/viewhelpers' => 'My\Other\Namespace'
54 )
55 );
56 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
57 $templateParser->injectSettings($mockSettings);
58 $templateParser->_call('extractNamespaceDefinitions', 'Some content <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f5="http://domain.tld/ns/my/viewhelpers"
59 xmlns:xyz="http://otherdomain.tld/ns/other/viewhelpers" />');
60 $expected = array(
61 'f' => \TYPO3\CMS\Fluid\ViewHelpers::class,
62 'f5' => 'My\Namespace',
63 'xyz' => 'My\Other\Namespace'
64 );
65 $this->assertEquals($expected, $templateParser->getNamespaces(), 'Namespaces do not match.');
66 }
67
68 /**
69 * @test
70 */
71 public function extractNamespaceDefinitionsResolveNamespacesWithDefaultPattern() {
72 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
73 $templateParser->_call('extractNamespaceDefinitions', '<xml xmlns="http://www.w3.org/1999/xhtml" xmlns:xyz="http://typo3.org/ns/Some/Package/ViewHelpers" />');
74 $expected = array(
75 'f' => \TYPO3\CMS\Fluid\ViewHelpers::class,
76 'xyz' => 'Some\Package\ViewHelpers'
77 );
78 $this->assertEquals($expected, $templateParser->getNamespaces(), 'Namespaces do not match.');
79 }
80
81 /**
82 * @test
83 */
84 public function extractNamespaceDefinitionsSilentlySkipsXmlNamespaceDeclarationsThatCantBeResolved() {
85 $mockSettings = array(
86 'namespaces' => array(
87 'http://domain.tld/ns/my/viewhelpers' => 'My\Namespace'
88 )
89 );
90 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
91 $templateParser->injectSettings($mockSettings);
92 $templateParser->_call('extractNamespaceDefinitions', '<xml xmlns="http://www.w3.org/1999/xhtml" xmlns:f5="http://domain.tld/ns/my/viewhelpers"
93 xmlns:xyz="http://otherdomain.tld/ns/other/viewhelpers" />');
94 $expected = array(
95 'f' => \TYPO3\CMS\Fluid\ViewHelpers::class,
96 'f5' => 'My\Namespace'
97 );
98 $this->assertEquals($expected, $templateParser->getNamespaces(), 'Namespaces do not match.');
99 }
100
101 /**
102 * @test
103 */
104 public function extractNamespaceDefinitionsSilentlySkipsXmlNamespaceDeclarationForTheDefaultFluidNamespace() {
105 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
106 $templateParser->_call('extractNamespaceDefinitions', '<foo xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://domain.tld/this/will/be/ignored" />');
107 $expected = array(
108 'f' => \TYPO3\CMS\Fluid\ViewHelpers::class
109 );
110 $this->assertEquals($expected, $templateParser->getNamespaces(), 'Namespaces do not match.');
111 }
112
113 /**
114 * @test
115 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
116 */
117 public function extractNamespaceDefinitionsThrowsExceptionIfNamespaceIsRedeclared() {
118 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
119 $templateParser->_call('extractNamespaceDefinitions', '{namespace typo3=TYPO3\CMS\Fluid\Blablubb} {namespace typo3= TYPO3\Rocks\Blu}');
120 }
121
122 /**
123 * @test
124 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
125 */
126 public function extractNamespaceDefinitionsThrowsExceptionIfXmlNamespaceIsRedeclaredAsFluidNamespace() {
127 $mockSettings = array(
128 'namespaces' => array(
129 'http://domain.tld/ns/my/viewhelpers' => 'My\Namespace'
130 )
131 );
132 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
133 $templateParser->injectSettings($mockSettings);
134 $templateParser->_call('extractNamespaceDefinitions', '<foo xmlns="http://www.w3.org/1999/xhtml" xmlns:typo3="http://domain.tld/ns/my/viewhelpers" />{namespace typo3=TYPO3\CMS\Fluid\Blablubb}');
135 }
136
137 /**
138 * @test
139 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
140 */
141 public function extractNamespaceDefinitionsThrowsExceptionIfFluidNamespaceIsRedeclaredAsXmlNamespace() {
142 $mockSettings = array(
143 'namespaces' => array(
144 'http://domain.tld/ns/my/viewhelpers' => 'My\Namespace'
145 )
146 );
147 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
148 $templateParser->injectSettings($mockSettings);
149 $templateParser->_call('extractNamespaceDefinitions', '{namespace typo3=TYPO3\CMS\Fluid\Blablubb} <foo xmlns="http://www.w3.org/1999/xhtml" xmlns:typo3="http://domain.tld/ns/my/viewhelpers" />');
150 }
151
152 /**
153 * @param array $expectedFoundIdentifiers
154 * @param string $templateString
155 * @param array $namespaces
156 * @test
157 * @dataProvider extractNamespaceDefinitionsCallsRemoveXmlnsViewHelperNamespaceDeclarationsWithCorrectFoundIdentifiersDataProvider
158 */
159 public function extractNamespaceDefinitionsCallsRemoveXmlnsViewHelperNamespaceDeclarationsWithCorrectFoundIdentifiers(array $expectedFoundIdentifiers, $templateString, array $namespaces) {
160 $mockSettings = array(
161 'namespaces' => $namespaces
162 );
163
164 /** @var TemplateParser|\PHPUnit_Framework_MockObject_MockObject|AccessibleObjectInterface $templateParser */
165 $templateParser = $this->getAccessibleMock(TemplateParser::class, array('removeXmlnsViewHelperNamespaceDeclarations'));
166 $templateParser->injectSettings($mockSettings);
167
168 // this verifies that the method is called with the correct found identifiers
169 // and also that the templateString was not modified before calling removeXmlnsViewHelperNamespaceDeclarations
170 $templateParser
171 ->expects($this->once())
172 ->method('removeXmlnsViewHelperNamespaceDeclarations')
173 ->with($templateString, $expectedFoundIdentifiers)
174 ->willReturnArgument(0);
175 $templateParser->_call('extractNamespaceDefinitions', $templateString);
176 }
177
178 /**
179 * @return array
180 */
181 public function extractNamespaceDefinitionsCallsRemoveXmlnsViewHelperNamespaceDeclarationsWithCorrectFoundIdentifiersDataProvider() {
182 return [
183 'bothViewHelperNamespacesDefinitionsOnlyProvideXmlnsViewHelpersUsingNonDefaultPatternViewHelpers' => array(
184 ['foo'],
185 '{namespace typo3=TYPO3\\CMS\\Fluid\\Blablubb} <div xmlns:foo="http://domain.tld/ns/foo/viewhelpers">Content</div>',
186 ['http://domain.tld/ns/foo/viewhelpers' => 'My\\Namespace']
187 ),
188 'bothViewHelperNamespacesDefinitionsOnlyProvideXmlnsViewHelpersUsingDefaultPatternViewHelpers' => array(
189 ['xyz'],
190 '{namespace typo3=TYPO3\\CMS\\Fluid\\Blablubb} <div xmlns:xyz="http://typo3.org/ns/Some/Package/ViewHelpers">Content</div>',
191 []
192 ),
193 'xmlnsIdentifiersWithWhitespaces' => array(
194 [' ', 'foo bar', '"x y z"'],
195 '
196 <div xmlns: ="http://typo3.org/ns/Some/Package/ViewHelpers"
197 xmlns:foo bar="http://domain.tld/ns/foobar/viewhelpers"
198 xmlns:"x y z"="http://typo3.org/ns/My/Xyz/ViewHelpers">
199
200 Content
201 </div>
202 ',
203 ['http://domain.tld/ns/foobar/viewhelpers' => 'My\\Namespace']
204 ),
205 'xmlnsWithEqualsSign' => array(
206 ['=', 'foo=bar', '"x=y=z"'],
207 '
208 <div xmlns:=="http://typo3.org/ns/Some/Package/ViewHelpers"
209 xmlns:foo=bar="http://domain.tld/ns/foobar/viewhelpers"
210 xmlns:"x=y=z"="http://typo3.org/ns/My/Xyz/ViewHelpers">
211
212 Content
213 </div>
214 ',
215 ['http://domain.tld/ns/foobar/viewhelpers' => 'My\\Namespace']
216 ),
217 'nonViewHelpersXmlnsAreNotIncludedButDefaultPatternAndNonDefaultAreIncluded' => array(
218 ['xyz', 'foo'],
219 '<div xmlns:xyz="http://typo3.org/ns/Some/Package/ViewHelpers"
220 xmlns:foo="http://domain.tld/ns/foo/viewhelpers"
221 xmlns:bar="http://typo3.org/foo/bar">
222
223 Content
224 </div>',
225 ['http://domain.tld/ns/foo/viewhelpers' => 'My\\Namespace']
226 ),
227 'nonViewHelpersInBetweenViewHelperXmlnsAreNotIncludedButDefaultPatternAndNonDefaultAreIncluded' => array(
228 ['xyz', 'foo'],
229 '<div xmlns:xyz="http://typo3.org/ns/Some/Package/ViewHelpers"
230 xmlns:bar="http://typo3.org/foo/bar"
231 xmlns:foo="http://domain.tld/ns/foo/viewhelpers">
232
233 Content
234 </div>',
235 ['http://domain.tld/ns/foo/viewhelpers' => 'My\\Namespace']
236 ),
237 'fluidNamespaceIsFound' => array(
238 ['f'],
239 '<div xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers">Content</div>',
240 []
241 ),
242 'xmlnsWithoutIdentifierIsIgnored' => array(
243 [],
244 '<div xmlns="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers">Content</div>',
245 []
246 ),
247 'htmlTagAlsoFindsIdentifiers' => array(
248 ['f', 'xyz'],
249 '<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
250 xmlns:xyz="http://typo3.org/ns/Some/Package/ViewHelpers">
251
252 Content
253 </html>',
254 []
255 ),
256 'htmlTagWithNamespaceTypo3FluidAttributeTagAlsoFindsIdentifiers' => array(
257 ['f', 'xyz'],
258 '<html data-namespace-typo3-fluid="true"
259 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
260 xmlns:xyz="http://typo3.org/ns/Some/Package/ViewHelpers">
261
262 Content
263 </html>',
264 []
265 ),
266 'nonHtmlTagAlsoFindsIdentifiers' => array(
267 ['f', 'xyz'],
268 '<typo3-root
269 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
270 xmlns:xyz="http://typo3.org/ns/Some/Package/ViewHelpers">
271
272 Content
273 </typo3-root>',
274 []
275 ),
276 ];
277 }
278
279 /**
280 * @param string $expectedOut
281 * @param string $templateString
282 * @param array $foundIdentifiers
283 * @test
284 * @dataProvider removeXmlnsViewHelperNamespaceDeclarationsDataProvider
285 */
286 public function removeXmlnsViewHelperNamespaceDeclarationsWorks($expectedOut, array $foundIdentifiers, $templateString) {
287 $templateParser = $this->getAccessibleMock(TemplateParser::class, array('dummy'));
288 $templateString = $templateParser->_call('removeXmlnsViewHelperNamespaceDeclarations', $templateString, $foundIdentifiers);
289
290 // remove tabs and trim because expected result and given have a different tab count in dataProvider which is not relevant for the parser (xml and html)
291 $this->assertSame(trim(str_replace("\t", '', $expectedOut)), trim(str_replace("\t", '', $templateString)));
292 }
293
294 /**
295 * DataProvider for removeXmlnsViewHelperNamespaceDeclarationsWorks test
296 *
297 * @return array
298 */
299 public function removeXmlnsViewHelperNamespaceDeclarationsDataProvider() {
300 return [
301 'onlyViewHelperXmlns' => array(
302 '
303 <div >
304 <f:if condition="{demo}">Hello World</f:if>
305 </div>
306 ',
307 ['f', 'fe'],
308 '<div xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
309 xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers">
310 <f:if condition="{demo}">Hello World</f:if>
311 </div>'
312 ),
313 'xmlnsViewHelpersFoundWithNonViewHelperXmlnsAtBeginning' => array(
314 '
315 <div xmlns:z="http://www.typo3.org/foo" >
316 <f:if condition="{demo}">Hello World</f:if>
317 </div>
318 ',
319 ['f', 'fe'],
320 '
321 <div xmlns:z="http://www.typo3.org/foo"
322 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
323 xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers">
324 <f:if condition="{demo}">Hello World</f:if>
325 </div>
326 '
327 ),
328 'xmlnsViewHelpersFoundWithNonViewHelperXmlnsAtEnd' => array(
329 '
330 <div xmlns:z="http://www.typo3.org/foo">
331 <f:if condition="{demo}">Hello World</f:if>
332 </div>
333 ',
334 ['f', 'fe'],
335 '
336 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
337 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
338 xmlns:z="http://www.typo3.org/foo">
339 <f:if condition="{demo}">Hello World</f:if>
340 </div>
341 '
342 ),
343 'xmlnsViewHelpersFoundWithMultipleNonViewHelperXmlns' => array(
344 '
345 <div xmlns:y="http://www.typo3.org/bar" xmlns:z="http://www.typo3.org/foo">
346 <f:if condition="{demo}">Hello World</f:if>
347 </div>
348 ',
349 ['f', 'fe'],
350 '
351 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
352 xmlns:y="http://www.typo3.org/bar"
353 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
354 xmlns:z="http://www.typo3.org/foo">
355 <f:if condition="{demo}">Hello World</f:if>
356 </div>
357 '
358 ),
359 'xmlnsViewHelpersFoundWithNonViewHelperXmlnsBetween' => array(
360 '
361 <div xmlns:z="http://www.typo3.org/foo" >
362 <f:if condition="{demo}">Hello World</f:if>
363 </div>
364 ',
365 ['fe', 'f'],
366 '
367 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
368 xmlns:z="http://www.typo3.org/foo"
369 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers">
370 <f:if condition="{demo}">Hello World</f:if>
371 </div>
372 '
373 ),
374 'removeHtmlTagWithAttributeButNoXmlnsViewHelpersFound' => array(
375 '<f:if condition="{demo}">Hello World</f:if>',
376 [],
377 '
378 <html data-namespace-typo3-fluid="true">
379 <f:if condition="{demo}">Hello World</f:if>
380 </html>
381 '
382 ),
383 'doNotRemoveHtmlTagBecauseHtmlTagNotMarkedAsFluidNamespaceDefinitionTag' => array(
384 '
385 <html xmlns:z="http://www.typo3.org/foo">
386 <f:if condition="{demo}">Hello World</f:if>
387 </html>
388 ',
389 ['fe', 'f'],
390 '
391 <html xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
392 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
393 xmlns:z="http://www.typo3.org/foo">
394 <f:if condition="{demo}">Hello World</f:if>
395 </html>
396 '
397 ),
398 'doNotModifyHtmlTagBecauseViewHelperXmlnsNotFoundInTagAndNotMarkedForRemoval' => array(
399 '
400 <html xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
401 xmlns:z="http://www.typo3.org/foo">
402 <f:if condition="{demo}">Hello World</f:if>
403 </html>
404 ',
405 ['f'], // required because without any found namespaces the method will not do any replacements
406 '
407 <html xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
408 xmlns:z="http://www.typo3.org/foo">
409 <f:if condition="{demo}">Hello World</f:if>
410 </html>
411 '
412 ),
413 'removeHtmlTagBecauseXmlnsFoundInTagAndMarkedAsFluidViewHelperDefinitionTag' => array(
414 '<f:if condition="{demo}">Hello World</f:if>',
415 ['fe'],
416 '
417 <html data-namespace-typo3-fluid="true"
418 xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
419 xmlns:z="http://www.typo3.org/foo">
420 <f:if condition="{demo}">Hello World</f:if>
421 </html>
422 '
423 ),
424 'removeHtmlTagBecauseXmlnsFoundInDifferentTagAndMarkedAsFluidViewHelperDefinitionTag' => array(
425 '<f:if condition="{demo}">Hello World</f:if>',
426 ['f'],
427 '
428 <html data-namespace-typo3-fluid="true" xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
429 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
430 xmlns:z="http://www.typo3.org/foo">
431 <f:if condition="{demo}">Hello World</f:if>
432 </html>
433 '
434 ),
435 'producesExcpedtedOutputIfFouundIdentifiersAreWrongButContainNoExistingNonViewHelperXmlns' => array(
436 '
437 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers" xmlns:z="http://www.typo3.org/foo">
438 <f:if condition="{demo}">Hello World</f:if>
439 </div>
440 ',
441 ['f', 'i'],
442 '
443 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
444 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
445 xmlns:z="http://www.typo3.org/foo">
446 <f:if condition="{demo}">Hello World</f:if>
447 </div>
448 '
449 ),
450 // this test verifies that the expected output can be wrong if the foundNameSpaces are incorrect
451 // which is why extractNamespaceDefinitionsCallsRemoveXmlnsViewHelperNamespaceDeclarationsWithCorrectFoundIdentifiers
452 // tests if the correct identifiers are found
453 'removesNonViewHelperNamespaceIfFoundIdentifiersAreWrong' => array(
454 '
455 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers" >
456 <f:if condition="{demo}">Hello World</f:if>
457 </div>
458 ',
459 ['f', 'z'],
460 '
461 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
462 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
463 xmlns:z="http://www.typo3.org/foo">
464 <f:if condition="{demo}">Hello World</f:if>
465 </div>
466 '
467 ),
468 // this verifies that the scan pattern was correctly quoted for the regular expression
469 // because if the regular expression delimiter were to be modified in the pattern,
470 // the corresponding preg_quote will fail without adaptions
471 'xmlnsWithScanPatternAsIdentifier' => array(
472 '
473 <div >
474 <f:if condition="{demo}">Hello World</f:if>
475 </div>
476 ',
477 [TemplateParser::$SCAN_PATTERN_REMOVE_VIEWHELPERS_XMLNSDECLARATIONS],
478 '
479 <div xmlns:' . TemplateParser::$SCAN_PATTERN_REMOVE_VIEWHELPERS_XMLNSDECLARATIONS . '="http://typo3.org/ns/TYPO3/CMS\Demo/ViewHelpers">
480 <f:if condition="{demo}">Hello World</f:if>
481 </div>
482 '
483 ),
484 // these scenarios also need to because even if the foundIdentifiers are
485 // invalid the method should still work as expected,
486 // Furthermore, currently these patterns are allowed for foundIdentifiers
487 // see also test extractNamespaceDefinitionsCallsRemoveXmlnsViewHelperNamespaceDeclarationsWithCorrectFoundIdentifiers
488 'xmlnsIdentifiersWithWhitespaces' => array(
489 '
490 <div xmlns:none xyz="http://domain.tld/ns/NoneXyz/viewhelpers" >
491
492 <f:if condition="{demo}">Hello World</f:if>
493 </div>
494 ',
495 [' ', 'foo bar', '"x y z"'],
496 '
497 <div xmlns: ="http://typo3.org/ns/Some/Package/ViewHelpers"
498 xmlns:foo bar="http://domain.tld/ns/foobar/viewhelpers"
499 xmlns:none xyz="http://domain.tld/ns/NoneXyz/viewhelpers"
500 xmlns:"x y z"="http://typo3.org/ns/My/Xyz/ViewHelpers">
501
502 <f:if condition="{demo}">Hello World</f:if>
503 </div>
504 '
505 ),
506 'xmlnsWithRegularExpressionAsIdentifier' => array(
507 '
508 <div xmlns:z="http://www.typo3.org/foo">
509 <f:if condition="{demo}">Hello World</f:if>
510 </div>
511 ',
512 ['f', 'fe', '.*.?\\s'],
513 '
514 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
515 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
516 xmlns:.*.?\\s="http://typo3.org/ns/TYPO3/CMS\Demo/ViewHelpers"
517 xmlns:z="http://www.typo3.org/foo">
518 <f:if condition="{demo}">Hello World</f:if>
519 </div>
520 '
521 ),
522 'xmlnsWithRegularExpressionDelimiterAsIdentifier' => array(
523 '
524 <div xmlns:z="http://www.typo3.org/foo">
525 <f:if condition="{demo}">Hello World</f:if>
526 </div>
527 ',
528 ['f', 'fe', '/'],
529 '
530 <div xmlns:fe="http://typo3.org/ns/TYPO3/CMS/Frontend/ViewHelpers"
531 xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
532 xmlns:/="http://typo3.org/ns/TYPO3/CMS\Demo/ViewHelpers"
533 xmlns:z="http://www.typo3.org/foo">
534 <f:if condition="{demo}">Hello World</f:if>
535 </div>
536 '
537 ),
538 'xmlnsWithEqualsSign' => array(
539 '
540 <div xmlns:none=xyz="http://domain.tld/ns/NoneXyz/viewhelpers" >
541
542 <f:if condition="{demo}">Hello World</f:if>
543 </div>
544 ',
545 ['=', 'foo=bar', '"x=y=z"'],
546 '
547 <div xmlns:=="http://typo3.org/ns/Some/Package/ViewHelpers"
548 xmlns:foo=bar="http://domain.tld/ns/foobar/viewhelpers"
549 xmlns:none=xyz="http://domain.tld/ns/NoneXyz/viewhelpers"
550 xmlns:"x=y=z"="http://typo3.org/ns/My/Xyz/ViewHelpers">
551
552 <f:if condition="{demo}">Hello World</f:if>
553 </div>
554 '
555 )
556 ];
557 }
558
559 /**
560 * @test
561 */
562 public function viewHelperNameWithMultipleLevelsCanBeResolvedByResolveViewHelperName() {
563 $mockTemplateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'), array(), '', FALSE);
564 $result = $mockTemplateParser->_call('resolveViewHelperName', 'f', 'foo.bar.baz');
565 $expected = 'TYPO3\CMS\Fluid\ViewHelpers\Foo\Bar\BazViewHelper';
566 $this->assertEquals($expected, $result, 'The name of the View Helper Name could not be resolved.');
567 }
568
569 /**
570 * @test
571 */
572 public function viewHelperNameWithOneLevelCanBeResolvedByResolveViewHelperName() {
573 $mockTemplateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'), array(), '', FALSE);
574 $actual = $mockTemplateParser->_call('resolveViewHelperName', 'f', 'myown');
575 $expected = 'TYPO3\CMS\Fluid\ViewHelpers\MyownViewHelper';
576 $this->assertEquals($expected, $actual);
577 }
578
579 /**
580 */
581 public function quotedStrings() {
582 return array(
583 array('"no quotes here"', 'no quotes here'),
584 array("'no quotes here'", 'no quotes here'),
585 array("'this \"string\" had \\'quotes\\' in it'", 'this "string" had \'quotes\' in it'),
586 array('"this \\"string\\" had \'quotes\' in it"', 'this "string" had \'quotes\' in it'),
587 array('"a weird \"string\" \'with\' *freaky* \\\\stuff', 'a weird "string" \'with\' *freaky* \\stuff'),
588 array('\'\\\'escaped quoted string in string\\\'\'', '\'escaped quoted string in string\'')
589 );
590 }
591
592 /**
593 * @dataProvider quotedStrings
594 * @test
595 */
596 public function unquoteStringReturnsUnquotedStrings($quoted, $unquoted) {
597 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
598 $this->assertEquals($unquoted, $templateParser->_call('unquoteString', $quoted));
599 }
600
601 /**
602 */
603 public function templatesToSplit() {
604 return array(
605 array('TemplateParserTestFixture01-shorthand'),
606 array('TemplateParserTestFixture06'),
607 array('TemplateParserTestFixture14')
608 );
609 }
610
611 /**
612 * @dataProvider templatesToSplit
613 * @test
614 */
615 public function splitTemplateAtDynamicTagsReturnsCorrectlySplitTemplate($templateName) {
616 $template = file_get_contents(__DIR__ . '/Fixtures/' . $templateName . '.html', FILE_TEXT);
617 $expectedResult = require(__DIR__ . '/Fixtures/' . $templateName . '-split.php');
618 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
619 $this->assertSame($expectedResult, $templateParser->_call('splitTemplateAtDynamicTags', $template), 'Filed for ' . $templateName);
620 }
621
622 /**
623 * @test
624 */
625 public function buildObjectTreeCreatesRootNodeAndSetsUpParsingState() {
626 $mockRootNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode::class);
627
628 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
629 $mockState->expects($this->once())->method('setRootNode')->with($mockRootNode);
630 $mockState->expects($this->once())->method('pushNodeToStack')->with($mockRootNode);
631 $mockState->expects($this->once())->method('countNodeStack')->will($this->returnValue(1));
632
633 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
634 $mockObjectManager->expects($this->at(0))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class)->will($this->returnValue($mockState));
635 $mockObjectManager->expects($this->at(1))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode::class)->will($this->returnValue($mockRootNode));
636
637 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
638 $templateParser->_set('objectManager', $mockObjectManager);
639 $templateParser->_call('buildObjectTree', array(), \TYPO3\CMS\Fluid\Core\Parser\TemplateParser::CONTEXT_OUTSIDE_VIEWHELPER_ARGUMENTS);
640 }
641
642 /**
643 * @test
644 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
645 */
646 public function buildObjectTreeThrowsExceptionIfOpenTagsRemain() {
647 $mockRootNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode::class);
648
649 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
650 $mockState->expects($this->once())->method('countNodeStack')->will($this->returnValue(2));
651
652 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
653 $mockObjectManager->expects($this->at(0))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class)->will($this->returnValue($mockState));
654 $mockObjectManager->expects($this->at(1))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode::class)->will($this->returnValue($mockRootNode));
655
656 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
657 $templateParser->_set('objectManager', $mockObjectManager);
658 $templateParser->_call('buildObjectTree', array(), \TYPO3\CMS\Fluid\Core\Parser\TemplateParser::CONTEXT_OUTSIDE_VIEWHELPER_ARGUMENTS);
659 }
660
661 /**
662 * @test
663 */
664 public function buildObjectTreeDelegatesHandlingOfTemplateElements() {
665 $mockRootNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode::class);
666 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
667 $mockState->expects($this->once())->method('countNodeStack')->will($this->returnValue(1));
668 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
669 $mockObjectManager->expects($this->at(0))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class)->will($this->returnValue($mockState));
670 $mockObjectManager->expects($this->at(1))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode::class)->will($this->returnValue($mockRootNode));
671
672 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('textHandler', 'openingViewHelperTagHandler', 'closingViewHelperTagHandler', 'textAndShorthandSyntaxHandler'));
673 $templateParser->_set('objectManager', $mockObjectManager);
674 $templateParser->expects($this->at(0))->method('textAndShorthandSyntaxHandler')->with($mockState, 'The first part is simple');
675 $templateParser->expects($this->at(1))->method('textHandler')->with($mockState, '<f:for each="{a: {a: 0, b: 2, c: 4}}" as="array"><f:for each="{array}" as="value">{value} </f:for>');
676 $templateParser->expects($this->at(2))->method('openingViewHelperTagHandler')->with($mockState, 'f', 'format.printf', ' arguments="{number : 362525200}"', FALSE);
677 $templateParser->expects($this->at(3))->method('textAndShorthandSyntaxHandler')->with($mockState, '%.3e');
678 $templateParser->expects($this->at(4))->method('closingViewHelperTagHandler')->with($mockState, 'f', 'format.printf');
679 $templateParser->expects($this->at(5))->method('textAndShorthandSyntaxHandler')->with($mockState, 'and here goes some {text} that could have {shorthand}');
680
681 $splitTemplate = $templateParser->_call('splitTemplateAtDynamicTags', 'The first part is simple<![CDATA[<f:for each="{a: {a: 0, b: 2, c: 4}}" as="array"><f:for each="{array}" as="value">{value} </f:for>]]><f:format.printf arguments="{number : 362525200}">%.3e</f:format.printf>and here goes some {text} that could have {shorthand}');
682 $templateParser->_call('buildObjectTree', $splitTemplate, \TYPO3\CMS\Fluid\Core\Parser\TemplateParser::CONTEXT_OUTSIDE_VIEWHELPER_ARGUMENTS);
683 }
684
685 /**
686 * @test
687 */
688 public function openingViewHelperTagHandlerDelegatesViewHelperInitialization() {
689 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
690 $mockState->expects($this->never())->method('popNodeFromStack');
691
692 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('parseArguments', 'initializeViewHelperAndAddItToStack'));
693 $templateParser->expects($this->once())->method('parseArguments')->with(array('arguments'))->will($this->returnValue(array('parsedArguments')));
694 $templateParser->expects($this->once())->method('initializeViewHelperAndAddItToStack')->with($mockState, 'namespaceIdentifier', 'methodIdentifier', array('parsedArguments'));
695
696 $templateParser->_call('openingViewHelperTagHandler', $mockState, 'namespaceIdentifier', 'methodIdentifier', array('arguments'), FALSE);
697 }
698
699 /**
700 * @test
701 */
702 public function openingViewHelperTagHandlerPopsNodeFromStackForSelfClosingTags() {
703 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
704 $mockState->expects($this->once())->method('popNodeFromStack')->will($this->returnValue($this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface::class)));
705
706 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('parseArguments', 'initializeViewHelperAndAddItToStack'));
707
708 $templateParser->_call('openingViewHelperTagHandler', $mockState, '', '', array(), TRUE);
709 }
710
711 /**
712 * @test
713 */
714 public function initializeViewHelperAndAddItToStackCreatesRequestedViewHelperAndViewHelperNode() {
715 $mockViewHelper = $this->getMock(\TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper::class);
716 $mockViewHelperNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class, array(), array(), '', FALSE);
717
718 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface::class);
719 $mockNodeOnStack->expects($this->once())->method('addChildNode')->with($mockViewHelperNode);
720
721 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
722 $mockObjectManager->expects($this->at(0))->method('get')->with('TYPO3\\CMS\\Fluid\\ViewHelpers\\MyownViewHelper')->will($this->returnValue($mockViewHelper));
723 $mockObjectManager->expects($this->at(1))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class)->will($this->returnValue($mockViewHelperNode));
724
725 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
726 $mockState->expects($this->once())->method('getNodeFromStack')->will($this->returnValue($mockNodeOnStack));
727 $mockState->expects($this->once())->method('pushNodeToStack')->with($mockViewHelperNode);
728
729 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('abortIfUnregisteredArgumentsExist', 'abortIfRequiredArgumentsAreMissing', 'rewriteBooleanNodesInArgumentsObjectTree'));
730 $templateParser->_set('objectManager', $mockObjectManager);
731
732 $templateParser->_call('initializeViewHelperAndAddItToStack', $mockState, 'f', 'myown', array('arguments'));
733 }
734
735 /**
736 * @test
737 */
738 public function initializeViewHelperAndAddItToStackChecksViewHelperArguments() {
739 $expectedArguments = array('expectedArguments');
740 $argumentsObjectTree = array('arguments');
741
742 $mockViewHelper = $this->getMock(\TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper::class);
743 $mockViewHelper->expects($this->once())->method('prepareArguments')->will($this->returnValue($expectedArguments));
744 $mockViewHelperNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class, array(), array(), '', FALSE);
745
746 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface::class);
747
748 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
749 $mockObjectManager->expects($this->at(0))->method('get')->with('TYPO3\\CMS\\Fluid\\ViewHelpers\\MyownViewHelper')->will($this->returnValue($mockViewHelper));
750 $mockObjectManager->expects($this->at(1))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class)->will($this->returnValue($mockViewHelperNode));
751
752 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
753 $mockState->expects($this->once())->method('getNodeFromStack')->will($this->returnValue($mockNodeOnStack));
754
755 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('abortIfUnregisteredArgumentsExist', 'abortIfRequiredArgumentsAreMissing', 'rewriteBooleanNodesInArgumentsObjectTree'));
756 $templateParser->_set('objectManager', $mockObjectManager);
757 $templateParser->expects($this->once())->method('abortIfUnregisteredArgumentsExist')->with($expectedArguments, $argumentsObjectTree);
758 $templateParser->expects($this->once())->method('abortIfRequiredArgumentsAreMissing')->with($expectedArguments, $argumentsObjectTree);
759
760 $templateParser->_call('initializeViewHelperAndAddItToStack', $mockState, 'f', 'myown', $argumentsObjectTree);
761 }
762
763 /**
764 * @test
765 */
766 public function initializeViewHelperAndAddItToStackHandlesPostParseFacets() {
767 $mockViewHelper = $this->getMock(\TYPO3\CMS\Fluid\Tests\Unit\Core\Parser\Fixtures\PostParseFacetViewHelper::class, array('prepareArguments'));
768 $mockViewHelperNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class, array(), array(), '', FALSE);
769
770 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface::class);
771 $mockNodeOnStack->expects($this->once())->method('addChildNode')->with($mockViewHelperNode);
772
773 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
774 $mockObjectManager->expects($this->at(0))->method('get')->with('TYPO3\\CMS\\Fluid\\ViewHelpers\\MyownViewHelper')->will($this->returnValue($mockViewHelper));
775 $mockObjectManager->expects($this->at(1))->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class)->will($this->returnValue($mockViewHelperNode));
776
777 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
778 $mockState->expects($this->once())->method('getNodeFromStack')->will($this->returnValue($mockNodeOnStack));
779 $mockState->expects($this->once())->method('getVariableContainer')->will($this->returnValue($this->getMock(\TYPO3\CMS\Fluid\Core\ViewHelper\TemplateVariableContainer::class)));
780
781 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('abortIfUnregisteredArgumentsExist', 'abortIfRequiredArgumentsAreMissing', 'rewriteBooleanNodesInArgumentsObjectTree'));
782 $templateParser->_set('objectManager', $mockObjectManager);
783
784 $templateParser->_call('initializeViewHelperAndAddItToStack', $mockState, 'f', 'myown', array('arguments'));
785 $this->assertTrue(\TYPO3\CMS\Fluid\Tests\Unit\Core\Parser\Fixtures\PostParseFacetViewHelper::$wasCalled, 'PostParse was not called!');
786 }
787
788 /**
789 * @test
790 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
791 */
792 public function abortIfUnregisteredArgumentsExistThrowsExceptionOnUnregisteredArguments() {
793 $expected = array(new \TYPO3\CMS\Fluid\Core\ViewHelper\ArgumentDefinition('firstArgument', 'string', '', FALSE));
794 $actual = array('firstArgument' => 'foo', 'secondArgument' => 'bar');
795
796 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
797
798 $templateParser->_call('abortIfUnregisteredArgumentsExist', $expected, $actual);
799 }
800
801 /**
802 * @test
803 */
804 public function abortIfUnregisteredArgumentsExistDoesNotThrowExceptionIfEverythingIsOk() {
805 $expectedArguments = array(
806 new \TYPO3\CMS\Fluid\Core\ViewHelper\ArgumentDefinition('name1', 'string', 'desc', FALSE),
807 new \TYPO3\CMS\Fluid\Core\ViewHelper\ArgumentDefinition('name2', 'string', 'desc', TRUE)
808 );
809 $actualArguments = array(
810 'name1' => 'bla'
811 );
812
813 $mockTemplateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
814
815 $mockTemplateParser->_call('abortIfUnregisteredArgumentsExist', $expectedArguments, $actualArguments);
816 // dummy assertion to avoid "did not perform any assertions" error
817 $this->assertTrue(TRUE);
818 }
819
820 /**
821 * @test
822 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
823 */
824 public function abortIfRequiredArgumentsAreMissingThrowsException() {
825 $expected = array(
826 new \TYPO3\CMS\Fluid\Core\ViewHelper\ArgumentDefinition('firstArgument', 'string', '', FALSE),
827 new \TYPO3\CMS\Fluid\Core\ViewHelper\ArgumentDefinition('secondArgument', 'string', '', TRUE)
828 );
829
830 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
831
832 $templateParser->_call('abortIfRequiredArgumentsAreMissing', $expected, array());
833 }
834
835 /**
836 * @test
837 */
838 public function abortIfRequiredArgumentsAreMissingDoesNotThrowExceptionIfRequiredArgumentExists() {
839 $expectedArguments = array(
840 new \TYPO3\CMS\Fluid\Core\ViewHelper\ArgumentDefinition('name1', 'string', 'desc', FALSE),
841 new \TYPO3\CMS\Fluid\Core\ViewHelper\ArgumentDefinition('name2', 'string', 'desc', TRUE)
842 );
843 $actualArguments = array(
844 'name2' => 'bla'
845 );
846
847 $mockTemplateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
848
849 $mockTemplateParser->_call('abortIfRequiredArgumentsAreMissing', $expectedArguments, $actualArguments);
850 // dummy assertion to avoid "did not perform any assertions" error
851 $this->assertTrue(TRUE);
852 }
853
854 /**
855 * @test
856 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
857 */
858 public function closingViewHelperTagHandlerThrowsExceptionBecauseOfClosingTagWhichWasNeverOpened() {
859 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface::class, array(), array(), '', FALSE);
860 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
861 $mockState->expects($this->once())->method('popNodeFromStack')->will($this->returnValue($mockNodeOnStack));
862
863 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
864
865 $templateParser->_call('closingViewHelperTagHandler', $mockState, 'f', 'method');
866 }
867
868 /**
869 * @test
870 * @expectedException \TYPO3\CMS\Fluid\Core\Parser\Exception
871 */
872 public function closingViewHelperTagHandlerThrowsExceptionBecauseOfWrongTagNesting() {
873 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class, array(), array(), '', FALSE);
874 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
875 $mockState->expects($this->once())->method('popNodeFromStack')->will($this->returnValue($mockNodeOnStack));
876
877 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
878
879 $templateParser->_call('closingViewHelperTagHandler', $mockState, 'f', 'method');
880 }
881
882 /**
883 * @test
884 */
885 public function objectAccessorHandlerCallsInitializeViewHelperAndAddItToStackIfViewHelperSyntaxIsPresent() {
886 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
887 $mockState->expects($this->exactly(2))->method('popNodeFromStack')->will($this->returnValue($this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface::class)));
888
889 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('recursiveArrayHandler', 'postProcessArgumentsForObjectAccessor', 'initializeViewHelperAndAddItToStack'));
890 $templateParser->expects($this->at(0))->method('recursiveArrayHandler')->with('format: "H:i"')->will($this->returnValue(array('format' => 'H:i')));
891 $templateParser->expects($this->at(1))->method('postProcessArgumentsForObjectAccessor')->with(array('format' => 'H:i'))->will($this->returnValue(array('processedArguments')));
892 $templateParser->expects($this->at(2))->method('initializeViewHelperAndAddItToStack')->with($mockState, 'f', 'format.date', array('processedArguments'));
893 $templateParser->expects($this->at(3))->method('initializeViewHelperAndAddItToStack')->with($mockState, 'f', 'base', array());
894
895 $templateParser->_call('objectAccessorHandler', $mockState, '', '', 'f:base() f:format.date(format: "H:i")', '');
896 }
897
898 /**
899 * @test
900 */
901 public function objectAccessorHandlerCreatesObjectAccessorNodeWithExpectedValueAndAddsItToStack() {
902 $objectAccessorNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode::class, array(), array(), '', FALSE);
903
904 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
905 $mockObjectManager->expects($this->once())->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode::class, 'objectAccessorString')->will($this->returnValue($objectAccessorNode));
906
907 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode::class, array(), array(), '', FALSE);
908 $mockNodeOnStack->expects($this->once())->method('addChildNode')->with($objectAccessorNode);
909 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
910 $mockState->expects($this->once())->method('getNodeFromStack')->will($this->returnValue($mockNodeOnStack));
911
912 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
913 $templateParser->_set('objectManager', $mockObjectManager);
914
915 $templateParser->_call('objectAccessorHandler', $mockState, 'objectAccessorString', '', '', '');
916 }
917
918 /**
919 * @test
920 */
921 public function valuesFromObjectAccessorsAreRunThroughValueInterceptorsByDefault() {
922 $objectAccessorNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode::class, array(), array(), '', FALSE);
923 $objectAccessorNodeInterceptor = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::class);
924 $objectAccessorNodeInterceptor->expects($this->once())->method('process')->with($objectAccessorNode)->will($this->returnArgument(0));
925
926 $parserConfiguration = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\Configuration::class);
927 $parserConfiguration->expects($this->once())->method('getInterceptors')->with(\TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_OBJECTACCESSOR)->will($this->returnValue(array($objectAccessorNodeInterceptor)));
928
929 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
930 $mockObjectManager->expects($this->once())->method('get')->will($this->returnValue($objectAccessorNode));
931
932 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode::class, array(), array(), '', FALSE);
933 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
934 $mockState->expects($this->once())->method('getNodeFromStack')->will($this->returnValue($mockNodeOnStack));
935
936 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
937 $templateParser->_set('objectManager', $mockObjectManager);
938 $templateParser->_set('configuration', $parserConfiguration);
939
940 $templateParser->_call('objectAccessorHandler', $mockState, 'objectAccessorString', '', '', '');
941 }
942
943 /**
944 */
945 public function argumentsStrings() {
946 return array(
947 array('a="2"', array('a' => '2')),
948 array('a="2" b="foobar \' with \\" quotes"', array('a' => '2', 'b' => 'foobar \' with " quotes')),
949 array(' arguments="{number : 362525200}"', array('arguments' => '{number : 362525200}'))
950 );
951 }
952
953 /**
954 * @test
955 * @dataProvider argumentsStrings
956 * @param string $argumentsString
957 * @param array $expected
958 */
959 public function parseArgumentsWorksAsExpected($argumentsString, array $expected) {
960 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('buildArgumentObjectTree'));
961 $templateParser->expects($this->any())->method('buildArgumentObjectTree')->will($this->returnArgument(0));
962
963 $this->assertSame($expected, $templateParser->_call('parseArguments', $argumentsString));
964 }
965
966 /**
967 * @test
968 */
969 public function buildArgumentObjectTreeReturnsTextNodeForSimplyString() {
970 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
971 $mockObjectManager->expects($this->once())->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode::class, 'a very plain string')->will($this->returnValue('theTextNode'));
972
973 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('dummy'));
974 $templateParser->_set('objectManager', $mockObjectManager);
975
976 $this->assertEquals('theTextNode', $templateParser->_call('buildArgumentObjectTree', 'a very plain string'));
977 }
978
979 /**
980 * @test
981 */
982 public function buildArgumentObjectTreeBuildsObjectTreeForComlexString() {
983 $objectTree = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
984 $objectTree->expects($this->once())->method('getRootNode')->will($this->returnValue('theRootNode'));
985
986 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('splitTemplateAtDynamicTags', 'buildObjectTree'));
987 $templateParser->expects($this->at(0))->method('splitTemplateAtDynamicTags')->with('a <very> {complex} string')->will($this->returnValue('split string'));
988 $templateParser->expects($this->at(1))->method('buildObjectTree')->with('split string')->will($this->returnValue($objectTree));
989
990 $this->assertEquals('theRootNode', $templateParser->_call('buildArgumentObjectTree', 'a <very> {complex} string'));
991 }
992
993 /**
994 * @test
995 */
996 public function textAndShorthandSyntaxHandlerDelegatesAppropriately() {
997 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
998 $mockObjectManager->expects($this->any())->method('get')->will($this->returnArgument(1));
999 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
1000
1001 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('objectAccessorHandler', 'arrayHandler', 'textHandler'));
1002 $templateParser->_set('objectManager', $mockObjectManager);
1003 $templateParser->expects($this->at(0))->method('objectAccessorHandler')->with($mockState, 'someThing.absolutely', '', '', '');
1004 $templateParser->expects($this->at(1))->method('textHandler')->with($mockState, ' "fishy" is \'going\' ');
1005 $templateParser->expects($this->at(2))->method('arrayHandler')->with($mockState, 'on: "here"');
1006
1007 $text = '{someThing.absolutely} "fishy" is \'going\' {on: "here"}';
1008 $templateParser->_call('textAndShorthandSyntaxHandler', $mockState, $text, \TYPO3\CMS\Fluid\Core\Parser\TemplateParser::CONTEXT_INSIDE_VIEWHELPER_ARGUMENTS);
1009 }
1010
1011 /**
1012 * @test
1013 */
1014 public function arrayHandlerAddsArrayNodeWithProperContentToStack() {
1015 $arrayNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode::class, array(), array(array()));
1016 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode::class, array(), array(), '', FALSE);
1017 $mockNodeOnStack->expects($this->once())->method('addChildNode')->with($arrayNode);
1018 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
1019 $mockState->expects($this->once())->method('getNodeFromStack')->will($this->returnValue($mockNodeOnStack));
1020
1021 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
1022 $mockObjectManager->expects($this->once())->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode::class, 'processedArrayText')->will($this->returnValue($arrayNode));
1023
1024 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('recursiveArrayHandler'));
1025 $templateParser->_set('objectManager', $mockObjectManager);
1026 $templateParser->expects($this->once())->method('recursiveArrayHandler')->with('arrayText')->will($this->returnValue('processedArrayText'));
1027
1028 $templateParser->_call('arrayHandler', $mockState, 'arrayText');
1029 }
1030
1031 /**
1032 */
1033 public function arrayTexts() {
1034 return array(
1035 array(
1036 'key1: "foo", key2: \'bar\', key3: someVar, key4: 123, key5: { key6: "baz" }',
1037 array('key1' => 'foo', 'key2' => 'bar', 'key3' => 'someVar', 'key4' => 123.0, 'key5' => array('key6' => 'baz'))
1038 ),
1039 array(
1040 'key1: "\'foo\'", key2: \'\\\'bar\\\'\'',
1041 array('key1' => '\'foo\'', 'key2' => '\'bar\'')
1042 )
1043 );
1044 }
1045
1046 /**
1047 * @test
1048 * @dataProvider arrayTexts
1049 */
1050 public function recursiveArrayHandlerReturnsExpectedArray($arrayText, $expectedArray) {
1051 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
1052 $mockObjectManager->expects($this->any())->method('get')->will($this->returnArgument(1));
1053
1054 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('buildArgumentObjectTree'));
1055 $templateParser->_set('objectManager', $mockObjectManager);
1056 $templateParser->expects($this->any())->method('buildArgumentObjectTree')->will($this->returnArgument(0));
1057
1058 $this->assertSame($expectedArray, $templateParser->_call('recursiveArrayHandler', $arrayText));
1059 }
1060
1061 /**
1062 * @test
1063 */
1064 public function textNodesAreRunThroughTextInterceptors() {
1065 $textNode = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode::class, array(), array(), '', FALSE);
1066 $textInterceptor = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::class);
1067 $textInterceptor->expects($this->once())->method('process')->with($textNode)->will($this->returnArgument(0));
1068
1069 $parserConfiguration = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\Configuration::class);
1070 $parserConfiguration->expects($this->once())->method('getInterceptors')->with(\TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_TEXT)->will($this->returnValue(array($textInterceptor)));
1071
1072 $mockObjectManager = $this->getMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
1073 $mockObjectManager->expects($this->once())->method('get')->with(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode::class, 'string')->will($this->returnValue($textNode));
1074
1075 $mockNodeOnStack = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode::class, array(), array(), '', FALSE);
1076 $mockNodeOnStack->expects($this->once())->method('addChildNode')->with($textNode);
1077 $mockState = $this->getMock(\TYPO3\CMS\Fluid\Core\Parser\ParsingState::class);
1078 $mockState->expects($this->once())->method('getNodeFromStack')->will($this->returnValue($mockNodeOnStack));
1079
1080 $templateParser = $this->getAccessibleMock(\TYPO3\CMS\Fluid\Core\Parser\TemplateParser::class, array('splitTemplateAtDynamicTags', 'buildObjectTree'));
1081 $templateParser->_set('objectManager', $mockObjectManager);
1082 $templateParser->_set('configuration', $parserConfiguration);
1083
1084 $templateParser->_call('textHandler', $mockState, 'string');
1085 }
1086
1087 }