HtmlParserTest.php 26.2 KB
Newer Older
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
9
10
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
11
 *
12
13
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
16
 * The TYPO3 project - inspiring people to share!
 */
17

18
19
namespace TYPO3\CMS\Core\Tests\Unit\Html;

20
use TYPO3\CMS\Core\Html\HtmlParser;
21
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
22

23
24
25
/**
 * Testcase for \TYPO3\CMS\Core\Html\HtmlParser
 */
26
class HtmlParserTest extends UnitTestCase
27
28
29
30
{
    /**
     * @var \TYPO3\CMS\Core\Html\HtmlParser
     */
31
    protected $subject;
32

33
    protected function setUp(): void
34
    {
35
        parent::setUp();
36
37
        $this->subject = new HtmlParser();
    }
38

39
40
41
    /**
     * @return array
     */
42
    public function cDataWillRemainUnmodifiedDataProvider(): array
43
    {
44
45
        return [
            'single-line CDATA' => [
46
47
                '/*<![CDATA[*/ <hello world> /*]]>*/',
                '/*<![CDATA[*/ <hello world> /*]]>*/',
48
49
            ],
            'multi-line CDATA #1' => [
50
51
                '/*<![CDATA[*/' . LF . '<hello world> /*]]>*/',
                '/*<![CDATA[*/' . LF . '<hello world> /*]]>*/',
52
53
            ],
            'multi-line CDATA #2' => [
54
55
                '/*<![CDATA[*/ <hello world>' . LF . '/*]]>*/',
                '/*<![CDATA[*/ <hello world>' . LF . '/*]]>*/',
56
57
            ],
            'multi-line CDATA #3' => [
58
59
                '/*<![CDATA[*/' . LF . '<hello world>' . LF . '/*]]>*/',
                '/*<![CDATA[*/' . LF . '<hello world>' . LF . '/*]]>*/',
60
61
            ],
        ];
62
    }
63

64
65
66
67
68
    /**
     * Data provider for splitIntoBlock
     *
     * @return array
     */
69
    public function splitIntoBlockDataProvider(): array
70
    {
71
72
        return [
            'splitBlock' => [
73
74
75
                'h1,span',
                '<body><h1>Title</h1><span>Note</span></body>',
                false,
76
77
                [
                    '<body>',
78
79
80
                    '<h1>Title</h1>',
                    '',
                    '<span>Note</span>',
81
82
                    '</body>'
                ]
83
84
            ],
            'splitBlock br' => [
85
86
87
                'h1,span',
                '<body><h1>Title</h1><br /><span>Note</span><br /></body>',
                false,
88
89
                [
                    '<body>',
90
91
92
                    '<h1>Title</h1>',
                    '<br />',
                    '<span>Note</span>',
93
94
                    '<br /></body>'
                ]
95
96
            ],
            'splitBlock with attribute' => [
97
98
99
                'h1,span',
                '<body><h1 class="title">Title</h1><span>Note</span></body>',
                false,
100
101
                [
                    '<body>',
102
103
104
                    '<h1 class="title">Title</h1>',
                    '',
                    '<span>Note</span>',
105
106
                    '</body>'
                ]
107
108
            ],
            'splitBlock span with attribute' => [
109
110
111
                'span',
                '<body><h1>Title</h1><span class="title">Note</span></body>',
                false,
112
113
                [
                    '<body><h1>Title</h1>',
114
                    '<span class="title">Note</span>',
115
116
                    '</body>'
                ]
117
118
            ],
            'splitBlock without extra end tags' => [
119
120
121
                'h1,span,div',
                '<body><h1>Title</h1><span>Note</span></body></div>',
                true,
122
123
                [
                    '<body>',
124
125
126
                    '<h1>Title</h1>',
                    '',
                    '<span>Note</span>',
127
128
                    '</body>'
                ]
129
130
            ],
        ];
131
    }
132

133
134
135
136
137
138
139
140
    /**
     * @test
     * @param string $tag List of tags, comma separated.
     * @param string $content HTML-content
     * @param bool $eliminateExtraEndTags If set, excessive end tags are ignored - you should probably set this in most cases.
     * @param array $expected The expected result
     * @dataProvider splitIntoBlockDataProvider
     */
141
    public function splitIntoBlock(string $tag, string $content, bool $eliminateExtraEndTags, array $expected): void
142
    {
143
        self::assertSame($expected, $this->subject->splitIntoBlock($tag, $content, $eliminateExtraEndTags));
144
    }
145

146
147
148
149
150
151
    /**
     * @test
     * @param string $source
     * @param string $expected
     * @dataProvider cDataWillRemainUnmodifiedDataProvider
     */
152
    public function xHtmlCleaningDoesNotModifyCDATA(string $source, string $expected): void
153
    {
154
        $result = $this->subject->HTMLcleaner($source, [], 1);
155
        self::assertSame($expected, $result);
156
    }
157

158
159
160
    /**
     * Data provider for spanTagCorrectlyRemovedWhenRmTagIfNoAttribIsConfigured
     */
161
    public static function spanTagCorrectlyRemovedWhenRmTagIfNoAttribIsConfiguredDataProvider(): array
162
    {
163
164
        return [
            'Span tag with no attrib' => [
165
166
                '<span>text</span>',
                'text'
167
168
            ],
            'Span tag with allowed id attrib' => [
169
170
                '<span id="id">text</span>',
                '<span id="id">text</span>'
171
172
            ],
            'Span tag with disallowed style attrib' => [
173
174
                '<span style="line-height: 12px;">text</span>',
                'text'
175
176
            ]
        ];
177
    }
178

179
180
181
182
183
184
    /**
     * @test
     * @param string $content
     * @param string $expectedResult
     * @dataProvider spanTagCorrectlyRemovedWhenRmTagIfNoAttribIsConfiguredDataProvider
     */
185
    public function tagCorrectlyRemovedWhenRmTagIfNoAttribIsConfigured(string $content, string $expectedResult): void
186
    {
187
        $tsConfig = [
188
            'allowTags' => 'span',
189
190
            'tags.' => [
                'span.' => [
191
192
                    'allowedAttribs' => 'id',
                    'rmTagIfNoAttrib' => 1
193
194
195
                ]
            ]
        ];
196
        self::assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
197
    }
198

199
200
201
    /**
     * @test
     */
202
    public function rmTagIfNoAttribIsConfiguredDoesNotChangeNestingType(): void
203
    {
204
        $tsConfig = [
205
206
207
            'allowTags' => 'div,span',
            'rmTagIfNoAttrib' => 'span',
            'globalNesting' => 'div,span'
208
        ];
209
210
        $content = '<span></span><span id="test"><div></span></div>';
        $expectedResult = '<span id="test"></span>';
211
        self::assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
212
    }
213

214
215
216
217
218
    /**
     * Data provider for localNestingCorrectlyRemovesInvalidTags
     *
     * @return array
     */
219
    public static function localNestingCorrectlyRemovesInvalidTagsDataProvider(): array
220
    {
221
222
        return [
            'Valid nesting is untouched' => [
223
224
                '<B><I></B></I>',
                '<B><I></B></I>'
225
226
            ],
            'Valid nesting with content is untouched' => [
227
228
                'testa<B>test1<I>test2</B>test3</I>testb',
                'testa<B>test1<I>test2</B>test3</I>testb'
229
            ],
230
            'Superfluous tags are removed' => [
231
232
                '</B><B><I></B></I></B>',
                '<B><I></B></I>'
233
            ],
234
            'Superfluous tags with content are removed' => [
235
236
                'test1</B>test2<B>test3<I>test4</B>test5</I>test6</B>test7',
                'test1test2<B>test3<I>test4</B>test5</I>test6test7'
237
238
            ],
            'Another valid nesting test' => [
239
240
                '<span><div></span></div>',
                '<span><div></span></div>',
241
242
            ],
        ];
243
    }
244

245
246
247
248
249
250
    /**
     * @test
     * @dataProvider localNestingCorrectlyRemovesInvalidTagsDataProvider
     * @param string $content
     * @param string $expectedResult
     */
251
    public function localNestingCorrectlyRemovesInvalidTags(string $content, string $expectedResult): void
252
    {
253
        $tsConfig = [
254
255
            'allowTags' => 'div,span,b,i',
            'localNesting' => 'div,span,b,i',
256
        ];
257
        self::assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
258
    }
259

260
261
262
263
264
    /**
     * Data provider for globalNestingCorrectlyRemovesInvalidTags
     *
     * @return array
     */
265
    public static function globalNestingCorrectlyRemovesInvalidTagsDataProvider(): array
266
    {
267
268
        return [
            'Valid nesting is untouched' => [
269
270
                '<B><I></I></B>',
                '<B><I></I></B>'
271
272
            ],
            'Valid nesting with content is untouched' => [
273
274
                'testa<B>test1<I>test2</I>test3</B>testb',
                'testa<B>test1<I>test2</I>test3</B>testb'
275
276
            ],
            'Invalid nesting is cleaned' => [
277
278
                '</B><B><I></B></I></B>',
                '<B></B>'
279
280
            ],
            'Invalid nesting with content is cleaned' => [
281
282
                'test1</B>test2<B>test3<I>test4</B>test5</I>test6</B>test7',
                'test1test2<B>test3test4</B>test5test6test7'
283
284
            ],
            'Another invalid nesting test' => [
285
286
                '<span><div></span></div>',
                '<span></span>',
287
288
            ],
        ];
289
    }
290

291
292
293
294
295
296
    /**
     * @test
     * @dataProvider globalNestingCorrectlyRemovesInvalidTagsDataProvider
     * @param string $content
     * @param string $expectedResult
     */
297
    public function globalNestingCorrectlyRemovesInvalidTags(string $content, string $expectedResult): void
298
    {
299
        $tsConfig = [
300
301
            'allowTags' => 'span,div,b,i',
            'globalNesting' => 'span,div,b,i',
302
        ];
303
        self::assertEquals($expectedResult, $this->parseConfigAndCleanHtml($tsConfig, $content));
304
    }
305

306
307
308
    /**
     * @return array
     */
309
    public function emptyTagsDataProvider(): array
310
    {
311
        return [
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
            [false, null, false, '<h1></h1>', '<h1></h1>'],
            [true, null, false, '<h1></h1>', ''],
            [true, null, false, '<h1>hallo</h1>', '<h1>hallo</h1>'],
            [true, null, false, '<h1 class="something"></h1>', ''],
            [true, null, false, '<h1 class="something"></h1><h2></h2>', ''],
            [true, 'h2', false, '<h1 class="something"></h1><h2></h2>', '<h1 class="something"></h1>'],
            [true, 'h2, h1', false, '<h1 class="something"></h1><h2></h2>', ''],
            [true, null, false, '<div><p></p></div>', ''],
            [true, null, false, '<div><p>&nbsp;</p></div>', '<div><p>&nbsp;</p></div>'],
            [true, null, true, '<div><p>&nbsp;&nbsp;</p></div>', ''],
            [true, null, true, '<div>&nbsp;&nbsp;<p></p></div>', ''],
            [true, null, false, '<div>Some content<p></p></div>', '<div>Some content</div>'],
            [true, null, true, '<div>Some content<p></p></div>', '<div>Some content</div>'],
            [true, null, false, '<div>Some content</div>', '<div>Some content</div>'],
            [true, null, true, '<div>Some content</div>', '<div>Some content</div>'],
            [true, null, false, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'],
            [true, null, true, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'],
            [false, '', false, '<h1></h1>', '<h1></h1>'],
            [true, '', false, '<h1></h1>', ''],
            [true, '', false, '<h1>hallo</h1>', '<h1>hallo</h1>'],
            [true, '', false, '<h1 class="something"></h1>', ''],
            [true, '', false, '<h1 class="something"></h1><h2></h2>', ''],
            [true, '', false, '<div><p></p></div>', ''],
            [true, '', false, '<div><p>&nbsp;</p></div>', '<div><p>&nbsp;</p></div>'],
            [true, '', true, '<div><p>&nbsp;&nbsp;</p></div>', ''],
            [true, '', true, '<div>&nbsp;&nbsp;<p></p></div>', ''],
            [true, '', false, '<div>Some content<p></p></div>', '<div>Some content</div>'],
            [true, '', true, '<div>Some content<p></p></div>', '<div>Some content</div>'],
            [true, '', false, '<div>Some content</div>', '<div>Some content</div>'],
            [true, '', true, '<div>Some content</div>', '<div>Some content</div>'],
            [true, '', false, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'],
            [true, '', true, '<a href="#skiplinks">Skiplinks </a><b></b>', '<a href="#skiplinks">Skiplinks </a>'],
344
        ];
345
    }
346

347
348
349
350
    /**
     * @test
     * @dataProvider emptyTagsDataProvider
     * @param bool $stripOn TRUE if stripping should be activated.
351
     * @param string|bool $tagList Comma separated list of tags that should be stripped.
352
353
354
355
     * @param bool $treatNonBreakingSpaceAsEmpty If TRUE &nbsp; will be considered empty.
     * @param string $content The HTML code that should be modified.
     * @param string $expectedResult The expected HTML code result.
     */
356
357
358
359
360
361
362
    public function stripEmptyTags(
        bool $stripOn,
        $tagList,
        bool $treatNonBreakingSpaceAsEmpty,
        string $content,
        string $expectedResult
    ): void {
363
        $tsConfig = [
364
365
            'keepNonMatchedTags' => 1,
            'stripEmptyTags' => $stripOn,
366
            'stripEmptyTags.' => [
367
368
                'tags' => $tagList,
                'treatNonBreakingSpaceAsEmpty' => $treatNonBreakingSpaceAsEmpty
369
370
            ],
        ];
371

372
        $result = $this->parseConfigAndCleanHtml($tsConfig, $content);
373
        self::assertEquals($expectedResult, $result);
374
    }
375

376
377
378
    /**
     * @return array
     */
379
    public function stripEmptyTagsKeepsConfiguredTagsDataProvider(): array
380
    {
381
        return [
382
            [
383
384
385
386
                'tr,td',
                false,
                '<div><p><tr><td></td></tr></p></div><div class="test"></div><tr></tr><p></p><td></td><i></i>',
                '<div><p><tr><td></td></tr></p></div><tr></tr><td></td>'
387
388
            ],
            [
389
390
391
392
                'tr,td',
                true,
                '<div><p><tr><td></td></tr></p></div><p class="test"> &nbsp; </p><tr></tr><p></p><td></td><i></i>',
                '<div><p><tr><td></td></tr></p></div><tr></tr><td></td>'
393
            ],
394
395
396
397
398
399
400
401
402
403
404
        ];
    }

    /**
     * @test
     * @dataProvider stripEmptyTagsKeepsConfiguredTagsDataProvider
     * @param string $tagList List of tags that should be kept, event if they are empty.
     * @param bool $treatNonBreakingSpaceAsEmpty If true &nbsp; will be considered empty.
     * @param string $content The HTML content that should be parsed.
     * @param string $expectedResult The expected HTML code result.
     */
405
406
407
408
409
410
    public function stripEmptyTagsKeepsConfiguredTags(
        string $tagList,
        bool $treatNonBreakingSpaceAsEmpty,
        string $content,
        string $expectedResult
    ): void {
411
        $tsConfig = [
412
413
            'keepNonMatchedTags' => 1,
            'stripEmptyTags' => 1,
414
            'stripEmptyTags.' => [
415
416
                'keepTags' => $tagList,
                'treatNonBreakingSpaceAsEmpty' => $treatNonBreakingSpaceAsEmpty
417
418
            ],
        ];
419
420

        $result = $this->parseConfigAndCleanHtml($tsConfig, $content);
421
        self::assertEquals($expectedResult, $result);
422
423
    }

424
425
426
427
428
429
430
    /**
     * Calls HTMLparserConfig() and passes the generated config to the HTMLcleaner() method on the current subject.
     *
     * @param array $tsConfig The TypoScript that should be used to generate the HTML parser config.
     * @param string $content The content that should be parsed by the HTMLcleaner.
     * @return string The parsed content.
     */
431
    protected function parseConfigAndCleanHtml(array $tsConfig, string $content): string
432
433
434
435
    {
        $config = $this->subject->HTMLparserConfig($tsConfig);
        return $this->subject->HTMLcleaner($content, $config[0], $config[1], $config[2], $config[3]);
    }
436

437
438
439
440
441
    /**
     * Data provider for getFirstTag
     *
     * @return array
     */
442
    public function getFirstTagDataProvider(): array
443
    {
444
445
446
447
448
449
        return [
            ['<body><span></span></body>', '<body>'],
            ['<span>Wrapper<div>Some content</div></span>', '<span>'],
            ['Something before<span>Wrapper<div>Some content</div></span>Something after', 'Something before<span>'],
            ['Something without tag', '']
        ];
450
    }
451

452
453
454
455
456
457
458
459
460
461
    /**
     * Returns the first tag in $str
     * Actually everything from the beginning of the $str is returned, so you better make sure the tag is the first thing...
     *
     * @test
     * @dataProvider getFirstTagDataProvider
     *
     * @param string $str HTML string with tags
     * @param string $expected The expected result.
     */
462
    public function getFirstTag(string $str, string $expected): void
463
    {
464
        self::assertEquals($expected, $this->subject->getFirstTag($str));
465
    }
466

467
468
469
470
471
    /**
     * Data provider for getFirstTagName
     *
     * @return array
     */
472
    public function getFirstTagNameDataProvider(): array
473
    {
474
        return [
475
476
            [
                '<body><span></span></body>',
477
                false,
478
479
480
481
                'BODY'
            ],
            [
                '<body><span></span></body>',
482
                true,
483
484
485
486
                'body'
            ],
            [
                '<div class="test"><span></span></div>',
487
                false,
488
489
490
491
                'DIV'
            ],
            [
                '<div><span class="test"></span></div>',
492
                false,
493
494
495
496
                'DIV'
            ],
            [
                '<br /><span class="test"></span>',
497
                false,
498
499
500
501
                'BR'
            ],
            [
                '<img src="test.jpg" />',
502
                false,
503
504
                'IMG'
            ],
505
        ];
506
    }
507

508
509
510
511
512
513
514
515
516
517
    /**
     * Returns the NAME of the first tag in $str
     *
     * @test
     * @dataProvider getFirstTagNameDataProvider
     *
     * @param string $str HTML tag (The element name MUST be separated from the attributes by a space character! Just *whitespace* will not do)
     * @param bool $preserveCase If set, then the tag is NOT converted to uppercase by case is preserved.
     * @param string $expected The expected result.
     */
518
    public function getFirstTagName(string $str, bool $preserveCase, string $expected): void
519
    {
520
        self::assertEquals($expected, $this->subject->getFirstTagName($str, $preserveCase));
521
    }
522

523
524
525
    /**
     * @return array
     */
526
    public function removeFirstAndLastTagDataProvider(): array
527
    {
528
529
530
        return [
            ['<span>Wrapper<div>Some content</div></span>', 'Wrapper<div>Some content</div>'],
            ['<td><tr>Some content</tr></td>', '<tr>Some content</tr>'],
531
532
533
534
            [
                'Something before<span>Wrapper<div>Some content</div></span>Something after',
                'Wrapper<div>Some content</div>'
            ],
535
            ['<span class="hidden">Wrapper<div>Some content</div></span>', 'Wrapper<div>Some content</div>'],
536
537
538
539
540
541
542
543
            [
                '<span>Wrapper<div class="hidden">Some content</div></span>',
                'Wrapper<div class="hidden">Some content</div>'
            ],
            [
                'Some stuff before <span>Wrapper<div class="hidden">Some content</div></span> and after',
                'Wrapper<div class="hidden">Some content</div>'
            ],
544
        ];
545
    }
546

547
548
549
550
551
552
553
554
555
    /**
     * Removes the first and last tag in the string
     * Anything before the first and after the last tags respectively is also removed
     *
     * @test
     * @dataProvider removeFirstAndLastTagDataProvider
     * @param string $str String to process
     * @param string $expectedResult
     */
556
    public function removeFirstAndLastTag(string $str, string $expectedResult): void
557
    {
558
        self::assertEquals($expectedResult, $this->subject->removeFirstAndLastTag($str));
559
    }
560
561
562
563

    /**
     * @return array
     */
564
    public function getTagAttributesDataProvider(): array
565
566
567
568
569
570
    {
        return [
            [
                '<a href="" data-shortCut="DXB" required>',
                [
                    ['href' => '', 'data-shortcut' => 'DXB', 'required' => ''],
571
572
573
574
575
                    [
                        'href' => ['origTag' => 'href', 'dashType' => '"'],
                        'data-shortcut' => ['origTag' => 'data-shortCut', 'dashType' => '"'],
                        'required' => ['origTag' => 'required']
                    ]
576
577
578
579
580
581
                ]
            ],
            [
                '<ul STYLE=\'background-image: (url: "fra.png")\' data-shortcut=FRA>',
                [
                    ['style' => 'background-image: (url: "fra.png")', 'data-shortcut' => 'FRA'],
582
583
584
585
                    [
                        'style' => ['origTag' => 'STYLE', 'dashType' => '\''],
                        'data-shortcut' => ['origTag' => 'data-shortcut', 'dashType' => '']
                    ]
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
                ]
            ]

        ];
    }

    /**
     * Returns an array with all attributes and its meta information from a tag.
     * Removes tag-name if found
     *
     * @test
     * @dataProvider getTagAttributesDataProvider
     * @param string $tag String to process
     * @param array $expectedResult
     */
601
    public function getTagAttributes(string $tag, array $expectedResult): void
602
    {
603
        self::assertEquals($expectedResult, $this->subject->get_tag_attributes($tag));
604
605
606
607
608
    }

    /**
     * @return array
     */
609
    public function stripEmptyTagsDataProvider(): array
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
    {
        return [
            // Testing wrongly encapsulated and upper/lowercase tags
            [
                '<div>Denpassar</div><p> Bali</P><p></p><P></p><ul><li></li></ul>',
                '',
                false,
                '<div>Denpassar</div><p> Bali</P>'
            ],
            // Testing incomplete tags
            [
                '<p><div>Klungklung</div></p><p> Semarapura<p></p><p></p><ul><li></li></ul>',
                '',
                false,
                '<p><div>Klungklung</div></p><p> Semarapura'
            ],
            // Testing third parameter (break spaces
            [
                '<p><div>Badung</div></p><ul> Mangupura<p></p><p></p><ul><li>&nbsp;</li><li>Uluwatu</li></ul>',
                '',
                true,
                '<p><div>Badung</div></p><ul> Mangupura<ul><li>Uluwatu</li></ul>'
            ],
            // Testing fourth parameter (keeping empty other tags, keeping defined used tags)
            [
                '<p><div>Badung</div></p><ul> Mangupura<p></p><p></p><ul><li></li></ul>',
                'p,div',
                true,
                '<p><div>Badung</div></p><ul> Mangupura<ul><li></li></ul>'
            ],

        ];
    }

    /**
     * Strips empty tags from HTML.
     *
     * @test
     * @dataProvider stripEmptyTagsDataProvider
     * @param string $content The content to be stripped of empty tags
     * @param string $tagList The comma separated list of tags to be stripped.
     *                        If empty, all empty tags will be stripped
     * @param bool $treatNonBreakingSpaceAsEmpty If TRUE tags containing only &nbsp; entities will be treated as empty.
     * @param string $expectedResult
     */
655
656
657
658
659
660
    public function rawStripEmptyTagsTest(
        string $content,
        string $tagList,
        bool $treatNonBreakingSpaceAsEmpty,
        string $expectedResult
    ): void {
661
        self::assertEquals(
662
663
664
            $expectedResult,
            $this->subject->stripEmptyTags($content, $tagList, $treatNonBreakingSpaceAsEmpty)
        );
665
    }
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749

    public function prefixResourcePathDataProvider(): array
    {
        return [
            '<td background="test.png">' => [
                '<table><tr><td background="test.png">Test</td></tr></table>',
                '/prefix/',
                '<table><tr><td background="/prefix/test.png">Test</td></tr></table>',

            ],
            '<table background="test.png">' => [
                '<table background="test.png"><tr><td>Test</td></tr></table>',
                '/prefix/',
                '<table background="/prefix/test.png"><tr><td>Test</td></tr></table>',
            ],
            '<body background="test.png">' => [
                '<body background="test.png">',
                '/prefix/',
                '<body background="/prefix/test.png">',
            ],
            '<img src="test.png">' => [
                '<img src="test.png">',
                '/prefix/',
                '<img src="/prefix/test.png">',
            ],
            '<input src="test.png">' => [
                '<input type="image" src="test.png"/>',
                '/prefix/',
                '<input type="image" src="/prefix/test.png" />',
            ],
            '<script src="test.js">' => [
                '<script src="test.js"/>',
                '/assets/',
                '<script src="/assets/test.js" />',
            ],
            '<embed src="test.swf">' => [
                '<embed src="test.swf"></embed>',
                '/media/',
                '<embed src="/media/test.swf"></embed>',
            ],
            '<a href="test.pdf">' => [
                '<a href="something/test.pdf">Test PDF</a>',
                '/',
                '<a href="/something/test.pdf">Test PDF</a>',
            ],
            '<link href="test.css">' => [
                '<link rel="stylesheet" type="text/css" href="theme.css">',
                '/css/',
                '<link rel="stylesheet" type="text/css" href="/css/theme.css">',
            ],
            '<form action="test/">' => [
                '<form action="test/"></form>',
                '/',
                '<form action="/test/"></form>',
            ],
            '<param name="movie" value="test.mp4">' => [
                '<param name="movie" value="test.mp4" />',
                '/test/',
                '<param name="movie" value="/test/test.mp4" />'
            ],
            '<source srcset="large.jpg">' => [
                '<source srcset="large.jpg">',
                '/assets/',
                '<source srcset="/assets/large.jpg">',
            ],
            '<source media="(min-width: 56.25em)" srcset="large.jpg 1x, large@2x.jpg 2x">' => [
                '<source media="(min-width: 56.25em)" srcset="large.jpg 1x, large@2x.jpg 2x">',
                '/assets/',
                '<source media="(min-width: 56.25em)" srcset="/assets/large.jpg 1x, /assets/large@2x.jpg 2x">',
            ],
        ];
    }

    /**
     * @test
     * @dataProvider prefixResourcePathDataProvider
     */
    public function prefixResourcePathTest(string $content, string $prefix, string $expectedResult): void
    {
        self::assertSame(
            $expectedResult,
            $this->subject->prefixResourcePath($prefix, $content)
        );
    }
750
}