1860e041050b337beb1bc81d46db38652aeea965
[yaffs-website] / web / core / tests / Drupal / Tests / Component / Utility / HtmlTest.php
1 <?php
2
3 namespace Drupal\Tests\Component\Utility;
4
5 use Drupal\Component\Render\MarkupInterface;
6 use Drupal\Component\Render\MarkupTrait;
7 use Drupal\Component\Utility\Html;
8 use Drupal\Component\Utility\Random;
9 use PHPUnit\Framework\TestCase;
10
11 /**
12  * Tests \Drupal\Component\Utility\Html.
13  *
14  * @group Common
15  *
16  * @coversDefaultClass \Drupal\Component\Utility\Html
17  */
18 class HtmlTest extends TestCase {
19
20   /**
21    * {@inheritdoc}
22    */
23   protected function setUp() {
24     parent::setUp();
25
26     $property = new \ReflectionProperty('Drupal\Component\Utility\Html', 'seenIdsInit');
27     $property->setAccessible(TRUE);
28     $property->setValue(NULL);
29   }
30
31   /**
32    * Tests the Html::cleanCssIdentifier() method.
33    *
34    * @param string $expected
35    *   The expected result.
36    * @param string $source
37    *   The string being transformed to an ID.
38    * @param array|null $filter
39    *   (optional) An array of string replacements to use on the identifier. If
40    *   NULL, no filter will be passed and a default will be used.
41    *
42    * @dataProvider providerTestCleanCssIdentifier
43    *
44    * @covers ::cleanCssIdentifier
45    */
46   public function testCleanCssIdentifier($expected, $source, $filter = NULL) {
47     if ($filter !== NULL) {
48       $this->assertSame($expected, Html::cleanCssIdentifier($source, $filter));
49     }
50     else {
51       $this->assertSame($expected, Html::cleanCssIdentifier($source));
52     }
53   }
54
55   /**
56    * Provides test data for testCleanCssIdentifier().
57    *
58    * @return array
59    *   Test data.
60    */
61   public function providerTestCleanCssIdentifier() {
62     $id1 = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
63     $id2 = '¡¢£¤¥';
64     $id3 = 'css__identifier__with__double__underscores';
65     return [
66       // Verify that no valid ASCII characters are stripped from the identifier.
67       [$id1, $id1, []],
68       // Verify that valid UTF-8 characters are not stripped from the identifier.
69       [$id2, $id2, []],
70       // Verify that double underscores are not stripped from the identifier.
71       [$id3, $id3],
72       // Verify that invalid characters (including non-breaking space) are
73       // stripped from the identifier.
74       ['invalididentifier', 'invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', []],
75       // Verify that an identifier starting with a digit is replaced.
76       ['_cssidentifier', '1cssidentifier', []],
77       // Verify that an identifier starting with a hyphen followed by a digit is
78       // replaced.
79       ['__cssidentifier', '-1cssidentifier', []],
80       // Verify that an identifier starting with two hyphens is replaced.
81       ['__cssidentifier', '--cssidentifier', []],
82       // Verify that passing double underscores as a filter is processed.
83       ['_cssidentifier', '__cssidentifier', ['__' => '_']],
84     ];
85   }
86
87   /**
88    * Tests that Html::getClass() cleans the class name properly.
89    *
90    * @coversDefaultClass ::getClass
91    */
92   public function testHtmlClass() {
93     // Verify Drupal coding standards are enforced.
94     $this->assertSame('class-name--ü', Html::getClass('CLASS NAME_[Ü]'), 'Enforce Drupal coding standards.');
95
96     // Test Html::getClass() handles Drupal\Component\Render\MarkupInterface
97     // input.
98     $markup = HtmlTestMarkup::create('CLASS_FROM_OBJECT');
99     $this->assertSame('class-from-object', Html::getClass($markup), 'Markup object is converted to CSS class.');
100   }
101
102   /**
103    * Tests the Html::getUniqueId() method.
104    *
105    * @param string $expected
106    *   The expected result.
107    * @param string $source
108    *   The string being transformed to an ID.
109    * @param bool $reset
110    *   (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE.
111    *
112    * @dataProvider providerTestHtmlGetUniqueId
113    *
114    * @covers ::getUniqueId
115    */
116   public function testHtmlGetUniqueId($expected, $source, $reset = FALSE) {
117     if ($reset) {
118       Html::resetSeenIds();
119     }
120     $this->assertSame($expected, Html::getUniqueId($source));
121   }
122
123   /**
124    * Provides test data for testHtmlGetId().
125    *
126    * @return array
127    *   Test data.
128    */
129   public function providerTestHtmlGetUniqueId() {
130     $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
131     return [
132       // Verify that letters, digits, and hyphens are not stripped from the ID.
133       [$id, $id],
134       // Verify that invalid characters are stripped from the ID.
135       ['invalididentifier', 'invalid,./:@\\^`{Üidentifier'],
136       // Verify Drupal coding standards are enforced.
137       ['id-name-1', 'ID NAME_[1]'],
138       // Verify that a repeated ID is made unique.
139       ['test-unique-id', 'test-unique-id', TRUE],
140       ['test-unique-id--2', 'test-unique-id'],
141       ['test-unique-id--3', 'test-unique-id'],
142     ];
143   }
144
145   /**
146    * Tests the Html::getUniqueId() method.
147    *
148    * @param string $expected
149    *   The expected result.
150    * @param string $source
151    *   The string being transformed to an ID.
152    *
153    * @dataProvider providerTestHtmlGetUniqueIdWithAjaxIds
154    *
155    * @covers ::getUniqueId
156    */
157   public function testHtmlGetUniqueIdWithAjaxIds($expected, $source) {
158     Html::setIsAjax(TRUE);
159     $id = Html::getUniqueId($source);
160
161     // Note, we truncate two hyphens at the end.
162     // @see \Drupal\Component\Utility\Html::getId()
163     if (strpos($source, '--') !== FALSE) {
164       $random_suffix = substr($id, strlen($source) + 1);
165     }
166     else {
167       $random_suffix = substr($id, strlen($source) + 2);
168     }
169     $expected = $expected . $random_suffix;
170     $this->assertSame($expected, $id);
171   }
172
173   /**
174    * Provides test data for testHtmlGetId().
175    *
176    * @return array
177    *   Test data.
178    */
179   public function providerTestHtmlGetUniqueIdWithAjaxIds() {
180     return [
181       ['test-unique-id1--', 'test-unique-id1'],
182       // Note, we truncate two hyphens at the end.
183       // @see \Drupal\Component\Utility\Html::getId()
184       ['test-unique-id1---', 'test-unique-id1--'],
185       ['test-unique-id2--', 'test-unique-id2'],
186     ];
187   }
188
189   /**
190    * Tests the Html::getUniqueId() method.
191    *
192    * @param string $expected
193    *   The expected result.
194    * @param string $source
195    *   The string being transformed to an ID.
196    *
197    * @dataProvider providerTestHtmlGetId
198    *
199    * @covers ::getId
200    */
201   public function testHtmlGetId($expected, $source) {
202     Html::setIsAjax(FALSE);
203     $this->assertSame($expected, Html::getId($source));
204   }
205
206   /**
207    * Provides test data for testHtmlGetId().
208    *
209    * @return array
210    *   Test data.
211    */
212   public function providerTestHtmlGetId() {
213     $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
214     return [
215       // Verify that letters, digits, and hyphens are not stripped from the ID.
216       [$id, $id],
217       // Verify that invalid characters are stripped from the ID.
218       ['invalididentifier', 'invalid,./:@\\^`{Üidentifier'],
219       // Verify Drupal coding standards are enforced.
220       ['id-name-1', 'ID NAME_[1]'],
221       // Verify that a repeated ID is made unique.
222       ['test-unique-id', 'test-unique-id'],
223       ['test-unique-id', 'test-unique-id'],
224     ];
225   }
226
227   /**
228    * Tests Html::decodeEntities().
229    *
230    * @dataProvider providerDecodeEntities
231    * @covers ::decodeEntities
232    */
233   public function testDecodeEntities($text, $expected) {
234     $this->assertEquals($expected, Html::decodeEntities($text));
235   }
236
237   /**
238    * Data provider for testDecodeEntities().
239    *
240    * @see testDecodeEntities()
241    */
242   public function providerDecodeEntities() {
243     return [
244       ['Drupal', 'Drupal'],
245       ['<script>', '<script>'],
246       ['&lt;script&gt;', '<script>'],
247       ['&#60;script&#62;', '<script>'],
248       ['&amp;lt;script&amp;gt;', '&lt;script&gt;'],
249       ['"', '"'],
250       ['&#34;', '"'],
251       ['&amp;#34;', '&#34;'],
252       ['&quot;', '"'],
253       ['&amp;quot;', '&quot;'],
254       ["'", "'"],
255       ['&#39;', "'"],
256       ['&amp;#39;', '&#39;'],
257       ['©', '©'],
258       ['&copy;', '©'],
259       ['&#169;', '©'],
260       ['→', '→'],
261       ['&#8594;', '→'],
262       ['➼', '➼'],
263       ['&#10172;', '➼'],
264       ['&euro;', '€'],
265     ];
266   }
267
268   /**
269    * Tests Html::escape().
270    *
271    * @dataProvider providerEscape
272    * @covers ::escape
273    */
274   public function testEscape($expected, $text) {
275     $this->assertEquals($expected, Html::escape($text));
276   }
277
278   /**
279    * Data provider for testEscape().
280    *
281    * @see testEscape()
282    */
283   public function providerEscape() {
284     return [
285       ['Drupal', 'Drupal'],
286       ['&lt;script&gt;', '<script>'],
287       ['&amp;lt;script&amp;gt;', '&lt;script&gt;'],
288       ['&amp;#34;', '&#34;'],
289       ['&quot;', '"'],
290       ['&amp;quot;', '&quot;'],
291       ['&#039;', "'"],
292       ['&amp;#039;', '&#039;'],
293       ['©', '©'],
294       ['→', '→'],
295       ['➼', '➼'],
296       ['€', '€'],
297       ['Drup�al', "Drup\x80al"],
298     ];
299   }
300
301   /**
302    * Tests relationship between escaping and decoding HTML entities.
303    *
304    * @covers ::decodeEntities
305    * @covers ::escape
306    */
307   public function testDecodeEntitiesAndEscape() {
308     $string = "<em>répét&eacute;</em>";
309     $escaped = Html::escape($string);
310     $this->assertSame('&lt;em&gt;répét&amp;eacute;&lt;/em&gt;', $escaped);
311     $decoded = Html::decodeEntities($escaped);
312     $this->assertSame('<em>répét&eacute;</em>', $decoded);
313     $decoded = Html::decodeEntities($decoded);
314     $this->assertSame('<em>répété</em>', $decoded);
315     $escaped = Html::escape($decoded);
316     $this->assertSame('&lt;em&gt;répété&lt;/em&gt;', $escaped);
317   }
318
319   /**
320    * Tests Html::serialize().
321    *
322    * Resolves an issue by where an empty DOMDocument object sent to serialization would
323    * cause errors in getElementsByTagName() in the serialization function.
324    *
325    * @covers ::serialize
326    */
327   public function testSerialize() {
328     $document = new \DOMDocument();
329     $result = Html::serialize($document);
330     $this->assertSame('', $result);
331   }
332
333   /**
334    * @covers ::transformRootRelativeUrlsToAbsolute
335    * @dataProvider providerTestTransformRootRelativeUrlsToAbsolute
336    */
337   public function testTransformRootRelativeUrlsToAbsolute($html, $scheme_and_host, $expected_html) {
338     $this->assertSame($expected_html ?: $html, Html::transformRootRelativeUrlsToAbsolute($html, $scheme_and_host));
339   }
340
341   /**
342    * @covers ::transformRootRelativeUrlsToAbsolute
343    * @dataProvider providerTestTransformRootRelativeUrlsToAbsoluteAssertion
344    */
345   public function testTransformRootRelativeUrlsToAbsoluteAssertion($scheme_and_host) {
346     $this->setExpectedException(\AssertionError::class);
347     Html::transformRootRelativeUrlsToAbsolute('', $scheme_and_host);
348   }
349
350   /**
351    * Provides test data for testTransformRootRelativeUrlsToAbsolute().
352    *
353    * @return array
354    *   Test data.
355    */
356   public function providerTestTransformRootRelativeUrlsToAbsolute() {
357     $data = [];
358
359     // Random generator.
360     $random = new Random();
361
362     // One random tag name.
363     $tag_name = strtolower($random->name(8, TRUE));
364
365     // A site installed either in the root of a domain or a subdirectory.
366     $base_paths = ['/', '/subdir/' . $random->name(8, TRUE) . '/'];
367
368     foreach ($base_paths as $base_path) {
369       // The only attribute that has more than just a URL as its value, is
370       // 'srcset', so special-case it.
371       $data += [
372         "$tag_name, srcset, $base_path: root-relative" => ["<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, {$base_path}root-relative 300w\">root-relative test</$tag_name>", 'http://example.com', "<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, http://example.com{$base_path}root-relative 300w\">root-relative test</$tag_name>"],
373         "$tag_name, srcset, $base_path: protocol-relative" => ["<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, //example.com{$base_path}protocol-relative 300w\">protocol-relative test</$tag_name>", 'http://example.com', FALSE],
374         "$tag_name, srcset, $base_path: absolute" => ["<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, http://example.com{$base_path}absolute 300w\">absolute test</$tag_name>", 'http://example.com', FALSE],
375       ];
376
377       foreach (['href', 'poster', 'src', 'cite', 'data', 'action', 'formaction', 'about'] as $attribute) {
378         $data += [
379           "$tag_name, $attribute, $base_path: root-relative" => ["<$tag_name $attribute=\"{$base_path}root-relative\">root-relative test</$tag_name>", 'http://example.com', "<$tag_name $attribute=\"http://example.com{$base_path}root-relative\">root-relative test</$tag_name>"],
380           "$tag_name, $attribute, $base_path: protocol-relative" => ["<$tag_name $attribute=\"//example.com{$base_path}protocol-relative\">protocol-relative test</$tag_name>", 'http://example.com', FALSE],
381           "$tag_name, $attribute, $base_path: absolute" => ["<$tag_name $attribute=\"http://example.com{$base_path}absolute\">absolute test</$tag_name>", 'http://example.com', FALSE],
382         ];
383       }
384     }
385
386     return $data;
387   }
388
389   /**
390    * Provides test data for testTransformRootRelativeUrlsToAbsoluteAssertion().
391    *
392    * @return array
393    *   Test data.
394    */
395   public function providerTestTransformRootRelativeUrlsToAbsoluteAssertion() {
396     return [
397       'only relative path' => ['llama'],
398       'only root-relative path' => ['/llama'],
399       'host and path' => ['example.com/llama'],
400       'scheme, host and path' => ['http://example.com/llama'],
401     ];
402   }
403
404 }
405
406 /**
407  * Marks an object's __toString() method as returning markup.
408  */
409 class HtmlTestMarkup implements MarkupInterface {
410   use MarkupTrait;
411
412 }