f5e1f1a4bd9b64ebf05eaeae00a01699fa29fe02
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 namespace Pelago\Tests\Unit\Emogrifier\HtmlProcessor;
4
5 use Pelago\Emogrifier\HtmlProcessor\AbstractHtmlProcessor;
6 use Pelago\Tests\Unit\Emogrifier\HtmlProcessor\Fixtures\TestingHtmlProcessor;
7
8 /**
9 * Test case.
10 *
11 * @author Oliver Klee <github@oliverklee.de>
12 */
13 class AbstractHtmlProcessorTest extends \PHPUnit_Framework_TestCase
14 {
15 /**
16 * @test
17 */
18 public function fixtureIsAbstractHtmlProcessor()
19 {
20 static::assertInstanceOf(AbstractHtmlProcessor::class, new TestingHtmlProcessor('<html></html>'));
21 }
22
23 /**
24 * @test
25 */
26 public function reformatsHtml()
27 {
28 $rawHtml = '<!DOCTYPE HTML>' .
29 '<html>' .
30 '<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>' .
31 '<body></body>' .
32 '</html>';
33 $formattedHtml = "<!DOCTYPE HTML>\n" .
34 "<html>\n" .
35 '<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>' . "\n" .
36 "<body></body>\n" .
37 "</html>\n";
38
39 $subject = new TestingHtmlProcessor($rawHtml);
40
41 static::assertSame($formattedHtml, $subject->render());
42 }
43
44 /**
45 * @return array[]
46 */
47 public function nonHtmlDataProvider()
48 {
49 return [
50 'empty string' => [''],
51 'null' => [null],
52 'integer' => [2],
53 'float' => [3.14159],
54 'object' => [new \stdClass()],
55 ];
56 }
57
58 /**
59 * @test
60 * @expectedException \InvalidArgumentException
61 *
62 * @param mixed $html
63 *
64 * @dataProvider nonHtmlDataProvider
65 */
66 public function constructorWithNoHtmlDataThrowsException($html)
67 {
68 new TestingHtmlProcessor($html);
69 }
70
71 /**
72 * @return string[][]
73 */
74 public function invalidHtmlDataProvider()
75 {
76 return [
77 'broken nesting gets nested' => ['<b><i></b></i>', '<b><i></i></b>'],
78 'partial opening tag gets closed' => ['<b', '<b></b>'],
79 'only opening tag gets closed' => ['<b>', '<b></b>'],
80 'only closing tag gets removed' => ['foo</b> bar', 'foo bar'],
81 ];
82 }
83
84 /**
85 * @test
86 *
87 * @param string $input
88 * @param string $expectedHtml
89 *
90 * @dataProvider invalidHtmlDataProvider
91 */
92 public function renderRepairsBrokenHtml($input, $expectedHtml)
93 {
94 $subject = new TestingHtmlProcessor($input);
95 $result = $subject->render();
96
97 static::assertContains($expectedHtml, $result);
98 }
99
100 /**
101 * @return string[][]
102 */
103 public function contentWithoutHtmlTagDataProvider()
104 {
105 return [
106 'doctype only' => ['<!DOCTYPE html>'],
107 'body content only' => ['<p>Hello</p>'],
108 'HEAD element' => ['<head></head>'],
109 'BODY element' => ['<body></body>'],
110 'HEAD AND BODY element' => ['<head></head><body></body>'],
111 ];
112 }
113
114 /**
115 * @test
116 *
117 * @param string $html
118 *
119 * @dataProvider contentWithoutHtmlTagDataProvider
120 */
121 public function addsMissingHtmlTag($html)
122 {
123 $subject = new TestingHtmlProcessor($html);
124
125 $result = $subject->render();
126
127 static::assertContains('<html>', $result);
128 }
129
130 /**
131 * @return string[][]
132 */
133 public function contentWithoutHeadTagDataProvider()
134 {
135 return [
136 'doctype only' => ['<!DOCTYPE html>'],
137 'body content only' => ['<p>Hello</p>'],
138 'BODY element' => ['<body></body>'],
139 ];
140 }
141
142 /**
143 * @test
144 *
145 * @param string $html
146 *
147 * @dataProvider contentWithoutHeadTagDataProvider
148 */
149 public function addsMissingHeadTag($html)
150 {
151 $subject = new TestingHtmlProcessor($html);
152
153 $result = $subject->render();
154
155 static::assertContains('<head>', $result);
156 }
157
158 /**
159 * @return string[][]
160 */
161 public function contentWithoutBodyTagDataProvider()
162 {
163 return [
164 'doctype only' => ['<!DOCTYPE html>'],
165 'HEAD element' => ['<head></head>'],
166 'body content only' => ['<p>Hello</p>'],
167 ];
168 }
169
170 /**
171 * @test
172 *
173 * @param string $html
174 *
175 * @dataProvider contentWithoutBodyTagDataProvider
176 */
177 public function addsMissingBodyTag($html)
178 {
179 $subject = new TestingHtmlProcessor($html);
180
181 $result = $subject->render();
182
183 static::assertContains('<body>', $result);
184 }
185
186 /**
187 * @test
188 */
189 public function putsMissingBodyElementAroundBodyContent()
190 {
191 $subject = new TestingHtmlProcessor('<p>Hello</p>');
192
193 $result = $subject->render();
194
195 static::assertContains('<body><p>Hello</p></body>', $result);
196 }
197
198 /**
199 * @return string[][]
200 */
201 public function specialCharactersDataProvider()
202 {
203 return [
204 'template markers with dollar signs & square brackets' => ['$[USER:NAME]$'],
205 'UTF-8 umlauts' => ['Küss die Hand, schöne Frau.'],
206 'HTML entities' => ['a &amp; b &gt; c'],
207 ];
208 }
209
210 /**
211 * @test
212 *
213 * @param string $codeNotToBeChanged
214 *
215 * @dataProvider specialCharactersDataProvider
216 */
217 public function keepsSpecialCharacters($codeNotToBeChanged)
218 {
219 $html = '<html><p>' . $codeNotToBeChanged . '</p></html>';
220 $subject = new TestingHtmlProcessor($html);
221
222 $result = $subject->render();
223
224 static::assertContains($codeNotToBeChanged, $result);
225 }
226
227 /**
228 * @test
229 */
230 public function addsMissingHtml5DocumentType()
231 {
232 $subject = new TestingHtmlProcessor('<html></html>');
233
234 $result = $subject->render();
235
236 static::assertContains('<!DOCTYPE html>', $result);
237 }
238
239 /**
240 * @return string[][]
241 */
242 public function documentTypeDataProvider()
243 {
244 return [
245 'HTML5' => ['<!DOCTYPE html>'],
246 'XHTML 1.0 strict' => [
247 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' .
248 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
249 ],
250 'XHTML 1.0 transitional' => [
251 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' .
252 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
253 ],
254 'HTML 4 transitional' => [
255 '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ' .
256 '"http://www.w3.org/TR/REC-html40/loose.dtd">',
257 ],
258 ];
259 }
260
261 /**
262 * @test
263 *
264 * @param string $documentType
265 *
266 * @dataProvider documentTypeDataProvider
267 */
268 public function keepsExistingDocumentType($documentType)
269 {
270 $html = $documentType . '<html></html>';
271 $subject = new TestingHtmlProcessor($html);
272
273 $result = $subject->render();
274
275 static::assertContains($documentType, $result);
276 }
277
278 /**
279 * @test
280 */
281 public function addsMissingContentTypeMetaTag()
282 {
283 $subject = new TestingHtmlProcessor('<p>Hello</p>');
284
285 $result = $subject->render();
286
287 static::assertContains('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">', $result);
288 }
289
290 /**
291 * @test
292 */
293 public function notAddsSecondContentTypeMetaTag()
294 {
295 $html = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>';
296 $subject = new TestingHtmlProcessor($html);
297
298 $result = $subject->render();
299
300 $numberOfContentTypeMetaTags = \substr_count($result, 'Content-Type');
301 static::assertSame(1, $numberOfContentTypeMetaTags);
302 }
303
304 /**
305 * @test
306 *
307 * @param string $documentType
308 *
309 * @dataProvider documentTypeDataProvider
310 */
311 public function convertsXmlSelfClosingTagsToNonXmlSelfClosingTag($documentType)
312 {
313 $subject = new TestingHtmlProcessor($documentType . '<html><body><br/></body></html>');
314
315 $result = $subject->render();
316
317 static::assertContains('<body><br></body>', $result);
318 }
319
320 /**
321 * @test
322 *
323 * @param string $documentType
324 *
325 * @dataProvider documentTypeDataProvider
326 */
327 public function keepsNonXmlSelfClosingTags($documentType)
328 {
329 $subject = new TestingHtmlProcessor($documentType . '<html><body><br></body></html>');
330
331 $result = $subject->render();
332
333 static::assertContains('<body><br></body>', $result);
334 }
335
336 /**
337 * @test
338 */
339 public function renderBodyContentForEmptyBodyReturnsEmptyString()
340 {
341 $subject = new TestingHtmlProcessor('<html><body></body></html>');
342
343 $result = $subject->renderBodyContent();
344
345 static::assertSame('', $result);
346 }
347
348 /**
349 * @test
350 */
351 public function renderBodyContentReturnsBodyContent()
352 {
353 $bodyContent = '<p>Hello world</p>';
354 $subject = new TestingHtmlProcessor('<html><body>' . $bodyContent . '</body></html>');
355
356 $result = $subject->renderBodyContent();
357
358 static::assertSame($bodyContent, $result);
359 }
360
361 /**
362 * @test
363 */
364 public function getDomDocumentReturnsDomDocument()
365 {
366 $subject = new TestingHtmlProcessor('<html></html>');
367
368 static::assertInstanceOf(\DOMDocument::class, $subject->getDomDocument());
369 }
370
371 /**
372 * @test
373 */
374 public function getDomDocumentWithNormalizedHtmlRepresentsTheGivenHtml()
375 {
376 $html = "<!DOCTYPE html>\n<html>\n<head>" .
377 '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' .
378 "</head>\n<body>\n<br>\n</body>\n</html>\n";
379 $subject = new TestingHtmlProcessor($html);
380
381 $domDocument = $subject->getDomDocument();
382
383 self::assertSame($html, $domDocument->saveHTML());
384 }
385 }