3 namespace Drupal\Tests\Component\Utility;
5 use Drupal\Component\Render\MarkupInterface;
6 use Drupal\Component\Render\MarkupTrait;
7 use Drupal\Component\Utility\Html;
8 use Drupal\Tests\UnitTestCase;
11 * Tests \Drupal\Component\Utility\Html.
15 * @coversDefaultClass \Drupal\Component\Utility\Html
17 class HtmlTest extends UnitTestCase {
22 protected function setUp() {
25 $property = new \ReflectionProperty('Drupal\Component\Utility\Html', 'seenIdsInit');
26 $property->setAccessible(TRUE);
27 $property->setValue(NULL);
31 * Tests the Html::cleanCssIdentifier() method.
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.
41 * @dataProvider providerTestCleanCssIdentifier
43 * @covers ::cleanCssIdentifier
45 public function testCleanCssIdentifier($expected, $source, $filter = NULL) {
46 if ($filter !== NULL) {
47 $this->assertSame($expected, Html::cleanCssIdentifier($source, $filter));
50 $this->assertSame($expected, Html::cleanCssIdentifier($source));
55 * Provides test data for testCleanCssIdentifier().
60 public function providerTestCleanCssIdentifier() {
61 $id1 = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
63 $id3 = 'css__identifier__with__double__underscores';
65 // Verify that no valid ASCII characters are stripped from the identifier.
67 // Verify that valid UTF-8 characters are not stripped from the identifier.
69 // Verify that invalid characters (including non-breaking space) are stripped from the identifier.
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
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', ['__' => '_']],
86 * Tests that Html::getClass() cleans the class name properly.
88 * @coversDefaultClass ::getClass
90 public function testHtmlClass() {
91 // Verify Drupal coding standards are enforced.
92 $this->assertSame('class-name--ü', Html::getClass('CLASS NAME_[Ü]'), 'Enforce Drupal coding standards.');
94 // Test Html::getClass() handles Drupal\Component\Render\MarkupInterface
96 $markup = HtmlTestMarkup::create('CLASS_FROM_OBJECT');
97 $this->assertSame('class-from-object', Html::getClass($markup), 'Markup object is converted to CSS class.');
101 * Tests the Html::getUniqueId() method.
103 * @param string $expected
104 * The expected result.
105 * @param string $source
106 * The string being transformed to an ID.
108 * (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE.
110 * @dataProvider providerTestHtmlGetUniqueId
112 * @covers ::getUniqueId
114 public function testHtmlGetUniqueId($expected, $source, $reset = FALSE) {
116 Html::resetSeenIds();
118 $this->assertSame($expected, Html::getUniqueId($source));
122 * Provides test data for testHtmlGetId().
127 public function providerTestHtmlGetUniqueId() {
128 $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
130 // Verify that letters, digits, and hyphens are not stripped from the 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'],
144 * Tests the Html::getUniqueId() method.
146 * @param string $expected
147 * The expected result.
148 * @param string $source
149 * The string being transformed to an ID.
151 * @dataProvider providerTestHtmlGetUniqueIdWithAjaxIds
153 * @covers ::getUniqueId
155 public function testHtmlGetUniqueIdWithAjaxIds($expected, $source) {
156 Html::setIsAjax(TRUE);
157 $id = Html::getUniqueId($source);
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);
165 $random_suffix = substr($id, strlen($source) + 2);
167 $expected = $expected . $random_suffix;
168 $this->assertSame($expected, $id);
172 * Provides test data for testHtmlGetId().
177 public function providerTestHtmlGetUniqueIdWithAjaxIds() {
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'],
188 * Tests the Html::getUniqueId() method.
190 * @param string $expected
191 * The expected result.
192 * @param string $source
193 * The string being transformed to an ID.
195 * @dataProvider providerTestHtmlGetId
199 public function testHtmlGetId($expected, $source) {
200 Html::setIsAjax(FALSE);
201 $this->assertSame($expected, Html::getId($source));
205 * Provides test data for testHtmlGetId().
210 public function providerTestHtmlGetId() {
211 $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
213 // Verify that letters, digits, and hyphens are not stripped from the 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'],
226 * Tests Html::decodeEntities().
228 * @dataProvider providerDecodeEntities
229 * @covers ::decodeEntities
231 public function testDecodeEntities($text, $expected) {
232 $this->assertEquals($expected, Html::decodeEntities($text));
236 * Data provider for testDecodeEntities().
238 * @see testDecodeEntities()
240 public function providerDecodeEntities() {
242 ['Drupal', 'Drupal'],
243 ['<script>', '<script>'],
244 ['<script>', '<script>'],
245 ['<script>', '<script>'],
246 ['&lt;script&gt;', '<script>'],
249 ['&#34;', '"'],
251 ['&quot;', '"'],
254 ['&#39;', '''],
267 * Tests Html::escape().
269 * @dataProvider providerEscape
272 public function testEscape($expected, $text) {
273 $this->assertEquals($expected, Html::escape($text));
277 * Data provider for testEscape().
281 public function providerEscape() {
283 ['Drupal', 'Drupal'],
284 ['<script>', '<script>'],
285 ['&lt;script&gt;', '<script>'],
286 ['&#34;', '"'],
288 ['&quot;', '"'],
290 ['&#039;', '''],
295 ['Drup�al', "Drup\x80al"],
300 * Tests relationship between escaping and decoding HTML entities.
302 * @covers ::decodeEntities
305 public function testDecodeEntitiesAndEscape() {
306 $string = "<em>répété</em>";
307 $escaped = Html::escape($string);
308 $this->assertSame('<em>répét&eacute;</em>', $escaped);
309 $decoded = Html::decodeEntities($escaped);
310 $this->assertSame('<em>répété</em>', $decoded);
311 $decoded = Html::decodeEntities($decoded);
312 $this->assertSame('<em>répété</em>', $decoded);
313 $escaped = Html::escape($decoded);
314 $this->assertSame('<em>répété</em>', $escaped);
318 * Tests Html::serialize().
320 * Resolves an issue by where an empty DOMDocument object sent to serialization would
321 * cause errors in getElementsByTagName() in the serialization function.
323 * @covers ::serialize
325 public function testSerialize() {
326 $document = new \DOMDocument();
327 $result = Html::serialize($document);
328 $this->assertSame('', $result);
332 * @covers ::transformRootRelativeUrlsToAbsolute
333 * @dataProvider providerTestTransformRootRelativeUrlsToAbsolute
335 public function testTransformRootRelativeUrlsToAbsolute($html, $scheme_and_host, $expected_html) {
336 $this->assertSame($expected_html ?: $html, Html::transformRootRelativeUrlsToAbsolute($html, $scheme_and_host));
340 * @covers ::transformRootRelativeUrlsToAbsolute
341 * @dataProvider providerTestTransformRootRelativeUrlsToAbsoluteAssertion
343 public function testTransformRootRelativeUrlsToAbsoluteAssertion($scheme_and_host) {
344 $this->setExpectedException(\AssertionError::class);
345 Html::transformRootRelativeUrlsToAbsolute('', $scheme_and_host);
349 * Provides test data for testTransformRootRelativeUrlsToAbsolute().
354 public function providerTestTransformRootRelativeUrlsToAbsolute() {
357 // One random tag name.
358 $tag_name = strtolower($this->randomMachineName());
360 // A site installed either in the root of a domain or a subdirectory.
361 $base_paths = ['/', '/subdir/' . $this->randomMachineName() . '/'];
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.
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],
372 foreach (['href', 'poster', 'src', 'cite', 'data', 'action', 'formaction', 'about'] as $attribute) {
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],
385 * Provides test data for testTransformRootRelativeUrlsToAbsoluteAssertion().
390 public function providerTestTransformRootRelativeUrlsToAbsoluteAssertion() {
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'],
402 * Marks an object's __toString() method as returning markup.
404 class HtmlTestMarkup implements MarkupInterface {