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