[TASK] Add unit tests for HtmlParser::getSubpart()
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Html / HtmlParserTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Html;
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\Core\Html\HtmlParser;
18
19 /**
20 * Testcase for \TYPO3\CMS\Core\Html\HtmlParser
21 */
22 class HtmlParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
23
24 /**
25 * @var \TYPO3\CMS\Core\Html\HtmlParser
26 */
27 protected $subject = NULL;
28
29 protected function setUp() {
30 $this->subject = new HtmlParser();
31 }
32
33 /**
34 * Data provider for getSubpart
35 *
36 * @return array
37 */
38 public function getSubpartDataProvider() {
39 return array(
40 'No start marker' => array(
41 '<body>text</body>',
42 '###SUBPART###',
43 ''
44 ),
45 'No stop marker' => array(
46 '<body>
47 <!-- ###SUBPART### Start -->
48 text
49 </body>',
50 '###SUBPART###',
51 ''
52 ),
53 'Start and stop marker in HTML comment' => array(
54 '<body>
55 <!-- ###SUBPART### Start -->
56 text
57 <!-- ###SUBPART### End -->
58 </body>',
59 '###SUBPART###',
60 '
61 text
62 '
63 ),
64 'Stop marker in HTML comment' => array(
65 '<body>
66 ###SUBPART###
67 text
68 <!-- ###SUBPART### End -->
69 </body>',
70 '###SUBPART###',
71 '
72 text
73 '
74 ),
75 'Start marker in HTML comment' => array(
76 '<body>
77 <!-- ###SUBPART### Start -->
78 text
79 ###SUBPART###
80 </body>',
81 '###SUBPART###',
82 '
83 text
84 '
85 ),
86 'Start and stop marker direct' => array(
87 '<body>
88 ###SUBPART###
89 text
90 ###SUBPART###
91 </body>',
92 '###SUBPART###',
93 '
94 text
95 '
96 ),
97 );
98 }
99
100 /**
101 * @test
102 * @dataProvider getSubpartDataProvider
103 */
104 public function getSubpart($content, $marker, $expected) {
105 $this->assertSame($expected, HtmlParser::getSubpart($content, $marker));
106 }
107
108 /**
109 * Data provider for substituteMarkerAndSubpartArrayRecursiveResolvesMarkersAndSubpartsArray
110 */
111 public function substituteMarkerAndSubpartArrayRecursiveResolvesMarkersAndSubpartsArrayDataProvider() {
112 $template = '###SINGLEMARKER1###
113 <!-- ###FOO### begin -->
114 <!-- ###BAR### begin -->
115 ###SINGLEMARKER2###
116 <!-- ###BAR### end -->
117 <!-- ###FOOTER### begin -->
118 ###SINGLEMARKER3###
119 <!-- ###FOOTER### end -->
120 <!-- ###FOO### end -->';
121
122 $expected ='Value 1
123
124
125 Value 2.1
126
127 Value 2.2
128
129
130 Value 3.1
131
132 Value 3.2
133
134 ';
135
136 return array(
137 'Single marker' => array(
138 '###SINGLEMARKER###',
139 array(
140 '###SINGLEMARKER###' => 'Value 1'
141 ),
142 '',
143 FALSE,
144 FALSE,
145 'Value 1'
146 ),
147 'Subpart marker' => array(
148 $template,
149 array(
150 '###SINGLEMARKER1###' => 'Value 1',
151 '###FOO###' => array(
152 array(
153 '###BAR###' => array(
154 array(
155 '###SINGLEMARKER2###' => 'Value 2.1'
156 ),
157 array(
158 '###SINGLEMARKER2###' => 'Value 2.2'
159 )
160 ),
161 '###FOOTER###' => array(
162 array(
163 '###SINGLEMARKER3###' => 'Value 3.1'
164 ),
165 array(
166 '###SINGLEMARKER3###' => 'Value 3.2'
167 )
168 )
169 )
170 )
171 ),
172 '',
173 FALSE,
174 FALSE,
175 $expected
176 ),
177 'Subpart marker with wrap' => array(
178 $template,
179 array(
180 'SINGLEMARKER1' => 'Value 1',
181 'FOO' => array(
182 array(
183 'BAR' => array(
184 array(
185 'SINGLEMARKER2' => 'Value 2.1'
186 ),
187 array(
188 'SINGLEMARKER2' => 'Value 2.2'
189 )
190 ),
191 'FOOTER' => array(
192 array(
193 'SINGLEMARKER3' => 'Value 3.1'
194 ),
195 array(
196 'SINGLEMARKER3' => 'Value 3.2'
197 )
198 )
199 )
200 )
201 ),
202 '###|###',
203 FALSE,
204 FALSE,
205 $expected
206 ),
207 'Subpart marker with lower marker array keys' => array(
208 $template,
209 array(
210 '###singlemarker1###' => 'Value 1',
211 '###foo###' => array(
212 array(
213 '###bar###' => array(
214 array(
215 '###singlemarker2###' => 'Value 2.1'
216 ),
217 array(
218 '###singlemarker2###' => 'Value 2.2'
219 )
220 ),
221 '###footer###' => array(
222 array(
223 '###singlemarker3###' => 'Value 3.1'
224 ),
225 array(
226 '###singlemarker3###' => 'Value 3.2'
227 )
228 )
229 )
230 )
231 ),
232 '',
233 TRUE,
234 FALSE,
235 $expected
236 ),
237 'Subpart marker with unused markers' => array(
238 $template,
239 array(
240 '###FOO###' => array(
241 array(
242 '###BAR###' => array(
243 array(
244 '###SINGLEMARKER2###' => 'Value 2.1'
245 )
246 ),
247 '###FOOTER###' => array(
248 array(
249 '###SINGLEMARKER3###' => 'Value 3.1'
250 )
251 )
252 )
253 )
254 ),
255 '',
256 FALSE,
257 TRUE,
258 '
259
260
261 Value 2.1
262
263
264 Value 3.1
265
266 '
267 ),
268 'Subpart marker with empty subpart' => array(
269 $template,
270 array(
271 '###SINGLEMARKER1###' => 'Value 1',
272 '###FOO###' => array(
273 array(
274 '###BAR###' => array(
275 array(
276 '###SINGLEMARKER2###' => 'Value 2.1'
277 ),
278 array(
279 '###SINGLEMARKER2###' => 'Value 2.2'
280 )
281 ),
282 '###FOOTER###' => array()
283 )
284 )
285 ),
286 '',
287 FALSE,
288 FALSE,
289 'Value 1
290
291
292 Value 2.1
293
294 Value 2.2
295
296
297 '
298 )
299 );
300 }
301
302 /**
303 * @test
304 * @dataProvider substituteMarkerAndSubpartArrayRecursiveResolvesMarkersAndSubpartsArrayDataProvider
305 */
306 public function substituteMarkerAndSubpartArrayRecursiveResolvesMarkersAndSubpartsArray($template, $markersAndSubparts, $wrap, $uppercase, $deleteUnused, $expected) {
307 $this->assertSame($expected, HtmlParser::substituteMarkerAndSubpartArrayRecursive($template, $markersAndSubparts, $wrap, $uppercase, $deleteUnused));
308 }
309
310 /**
311 * @return array
312 */
313 public function cDataWillRemainUnmodifiedDataProvider() {
314 return array(
315 'single-line CDATA' => array(
316 '/*<![CDATA[*/ <hello world> /*]]>*/',
317 '/*<![CDATA[*/ <hello world> /*]]>*/',
318 ),
319 'multi-line CDATA #1' => array(
320 '/*<![CDATA[*/' . LF . '<hello world> /*]]>*/',
321 '/*<![CDATA[*/' . LF . '<hello world> /*]]>*/',
322 ),
323 'multi-line CDATA #2' => array(
324 '/*<![CDATA[*/ <hello world>' . LF . '/*]]>*/',
325 '/*<![CDATA[*/ <hello world>' . LF . '/*]]>*/',
326 ),
327 'multi-line CDATA #3' => array(
328 '/*<![CDATA[*/' . LF . '<hello world>' . LF . '/*]]>*/',
329 '/*<![CDATA[*/' . LF . '<hello world>' . LF . '/*]]>*/',
330 ),
331 );
332 }
333
334 /**
335 * @test
336 * @param string $source
337 * @param string $expected
338 * @dataProvider cDataWillRemainUnmodifiedDataProvider
339 */
340 public function xHtmlCleaningDoesNotModifyCDATA($source, $expected) {
341 $result = $this->subject->XHTML_clean($source);
342 $this->assertSame($expected, $result);
343 }
344
345 /**
346 * Data provider for spanTagCorrectlyRemovedWhenRmTagIfNoAttribIsConfigured
347 */
348 public static function spanTagCorrectlyRemovedWhenRmTagIfNoAttribIsConfiguredDataProvider() {
349 return array(
350 'Span tag with no attrib' => array(
351 '<span>text</span>',
352 'text'
353 ),
354 'Span tag with allowed id attrib' => array(
355 '<span id="id">text</span>',
356 '<span id="id">text</span>'
357 ),
358 'Span tag with disallowed style attrib' => array(
359 '<span style="line-height: 12px;">text</span>',
360 'text'
361 )
362 );
363 }
364
365 /**
366 * @test
367 * @dataProvider spanTagCorrectlyRemovedWhenRmTagIfNoAttribIsConfiguredDataProvider
368 */
369 public function tagCorrectlyRemovedWhenRmTagIfNoAttribIsConfigured($content, $expectedResult) {
370 $tsConfig = array(
371 'allowTags' => 'span',
372 'tags.' => array(
373 'span.' => array(
374 'allowedAttribs' => 'id',
375 'rmTagIfNoAttrib' => 1
376 )
377 )
378 );
379 $this->assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
380 }
381
382 /**
383 * @test
384 */
385 public function rmTagIfNoAttribIsConfiguredDoesNotChangeNestingType() {
386 $tsConfig = array(
387 'allowTags' => 'div,span',
388 'rmTagIfNoAttrib' => 'span',
389 'globalNesting' => 'div,span'
390 );
391 $content = '<span></span><span id="test"><div></span></div>';
392 $expectedResult = '<span id="test"></span>';
393 $this->assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
394 }
395
396 /**
397 * Data provider for localNestingCorrectlyRemovesInvalidTags
398 */
399 public static function localNestingCorrectlyRemovesInvalidTagsDataProvider() {
400 return array(
401 'Valid nesting is untouched' => array(
402 '<B><I></B></I>',
403 '<B><I></B></I>'
404 ),
405 'Valid nesting with content is untouched' => array(
406 'testa<B>test1<I>test2</B>test3</I>testb',
407 'testa<B>test1<I>test2</B>test3</I>testb'
408 ),
409 'Superflous tags are removed' => array(
410 '</B><B><I></B></I></B>',
411 '<B><I></B></I>'
412 ),
413 'Superflous tags with content are removed' => array(
414 'test1</B>test2<B>test3<I>test4</B>test5</I>test6</B>test7',
415 'test1test2<B>test3<I>test4</B>test5</I>test6test7'
416 ),
417 'Another valid nesting test' => array(
418 '<span><div></span></div>',
419 '<span><div></span></div>',
420 ),
421 );
422 }
423
424 /**
425 * @test
426 * @dataProvider localNestingCorrectlyRemovesInvalidTagsDataProvider
427 * @param string $content
428 * @param string $expectedResult
429 */
430 public function localNestingCorrectlyRemovesInvalidTags($content, $expectedResult) {
431 $tsConfig = array(
432 'allowTags' => 'div,span,b,i',
433 'localNesting' => 'div,span,b,i',
434 );
435 $this->assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
436 }
437
438 /**
439 * Data provider for globalNestingCorrectlyRemovesInvalidTags
440 */
441 public static function globalNestingCorrectlyRemovesInvalidTagsDataProvider() {
442 return array(
443 'Valid nesting is untouched' => array(
444 '<B><I></I></B>',
445 '<B><I></I></B>'
446 ),
447 'Valid nesting with content is untouched' => array(
448 'testa<B>test1<I>test2</I>test3</B>testb',
449 'testa<B>test1<I>test2</I>test3</B>testb'
450 ),
451 'Invalid nesting is cleaned' => array(
452 '</B><B><I></B></I></B>',
453 '<B></B>'
454 ),
455 'Invalid nesting with content is cleaned' => array(
456 'test1</B>test2<B>test3<I>test4</B>test5</I>test6</B>test7',
457 'test1test2<B>test3test4</B>test5test6test7'
458 ),
459 'Another invalid nesting test' => array(
460 '<span><div></span></div>',
461 '<span></span>',
462 ),
463 );
464 }
465
466 /**
467 * @test
468 * @dataProvider globalNestingCorrectlyRemovesInvalidTagsDataProvider
469 * @param string $content
470 * @param string $expectedResult
471 */
472 public function globalNestingCorrectlyRemovesInvalidTags($content, $expectedResult) {
473 $tsConfig = array(
474 'allowTags' => 'span,div,b,i',
475 'globalNesting' => 'span,div,b,i',
476 );
477 $this->assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
478 }
479
480 /**
481 * @return array
482 */
483 public function emptyTagsDataProvider() {
484 return array(
485 array(0 , NULL, FALSE, '<h1></h1>', '<h1></h1>'),
486 array(1 , NULL, FALSE, '<h1></h1>', ''),
487 array(1 , NULL, FALSE, '<h1>hallo</h1>', '<h1>hallo</h1>'),
488 array(1 , NULL, FALSE, '<h1 class="something"></h1>', ''),
489 array(1 , NULL, FALSE, '<h1 class="something"></h1><h2></h2>', ''),
490 array(1 , 'h2', FALSE, '<h1 class="something"></h1><h2></h2>', '<h1 class="something"></h1>'),
491 array(1 , 'h2, h1', FALSE, '<h1 class="something"></h1><h2></h2>', ''),
492 array(1 , NULL, FALSE, '<div><p></p></div>', ''),
493 array(1 , NULL, FALSE, '<div><p>&nbsp;</p></div>', '<div><p>&nbsp;</p></div>'),
494 array(1 , NULL, TRUE, '<div><p>&nbsp;&nbsp;</p></div>', ''),
495 array(1 , NULL, TRUE, '<div>&nbsp;&nbsp;<p></p></div>', ''),
496 array(1 , NULL, FALSE, '<div>Some content<p></p></div>', '<div>Some content</div>'),
497 array(1 , NULL, TRUE, '<div>Some content<p></p></div>', '<div>Some content</div>'),
498 array(1 , NULL, FALSE, '<div>Some content</div>', '<div>Some content</div>'),
499 array(1 , NULL, TRUE, '<div>Some content</div>', '<div>Some content</div>'),
500 array(1 , NULL, FALSE, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
501 array(1 , NULL, TRUE, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'),
502 );
503 }
504
505 /**
506 * @test
507 * @dataProvider emptyTagsDataProvider
508 * @param bool $stripOn TRUE if stripping should be activated.
509 * @param string $tagList Comma seperated list of tags that should be stripped.
510 * @param bool $treatNonBreakingSpaceAsEmpty If TRUE &nbsp; will be considered empty.
511 * @param string $content The HTML code that should be modified.
512 * @param string $expectedResult The expected HTML code result.
513 */
514 public function stripEmptyTags($stripOn, $tagList, $treatNonBreakingSpaceAsEmpty, $content, $expectedResult) {
515 $tsConfig = array(
516 'keepNonMatchedTags' => 1,
517 'stripEmptyTags' => $stripOn,
518 'stripEmptyTags.' => array(
519 'tags' => $tagList,
520 'treatNonBreakingSpaceAsEmpty' => $treatNonBreakingSpaceAsEmpty
521 ),
522 );
523
524 $result = $this->parseConfigAndCleanHtml($tsConfig, $content);
525 $this->assertEquals($expectedResult, $result);
526 }
527
528 /**
529 * Calls HTMLparserConfig() and passes the generated config to the HTMLcleaner() method on the current subject.
530 *
531 * @param array $tsConfig The TypoScript that should be used to generate the HTML parser config.
532 * @param string $content The content that should be parsed by the HTMLcleaner.
533 * @return string The parsed content.
534 */
535 protected function parseConfigAndCleanHtml(array $tsConfig, $content) {
536 $config = $this->subject->HTMLparserConfig($tsConfig);
537 return $this->subject->HTMLcleaner($content, $config[0], $config[1], $config[2], $config[3]);
538 }
539 }