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\Component\Utility\Random;
9 use PHPUnit\Framework\TestCase;
12 * Tests \Drupal\Component\Utility\Html.
16 * @coversDefaultClass \Drupal\Component\Utility\Html
18 class HtmlTest extends TestCase {
23 protected function setUp() {
26 $property = new \ReflectionProperty('Drupal\Component\Utility\Html', 'seenIdsInit');
27 $property->setAccessible(TRUE);
28 $property->setValue(NULL);
32 * Tests the Html::cleanCssIdentifier() method.
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.
42 * @dataProvider providerTestCleanCssIdentifier
44 * @covers ::cleanCssIdentifier
46 public function testCleanCssIdentifier($expected, $source, $filter = NULL) {
47 if ($filter !== NULL) {
48 $this->assertSame($expected, Html::cleanCssIdentifier($source, $filter));
51 $this->assertSame($expected, Html::cleanCssIdentifier($source));
56 * Provides test data for testCleanCssIdentifier().
61 public function providerTestCleanCssIdentifier() {
62 $id1 = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
64 $id3 = 'css__identifier__with__double__underscores';
66 // Verify that no valid ASCII characters are stripped from the identifier.
68 // Verify that valid UTF-8 characters are not stripped from the identifier.
70 // Verify that double underscores are not stripped from the identifier.
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
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', ['__' => '_']],
88 * Tests that Html::getClass() cleans the class name properly.
90 * @coversDefaultClass ::getClass
92 public function testHtmlClass() {
93 // Verify Drupal coding standards are enforced.
94 $this->assertSame('class-name--ü', Html::getClass('CLASS NAME_[Ü]'), 'Enforce Drupal coding standards.');
96 // Test Html::getClass() handles Drupal\Component\Render\MarkupInterface
98 $markup = HtmlTestMarkup::create('CLASS_FROM_OBJECT');
99 $this->assertSame('class-from-object', Html::getClass($markup), 'Markup object is converted to CSS class.');
103 * Tests the Html::getUniqueId() method.
105 * @param string $expected
106 * The expected result.
107 * @param string $source
108 * The string being transformed to an ID.
110 * (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE.
112 * @dataProvider providerTestHtmlGetUniqueId
114 * @covers ::getUniqueId
116 public function testHtmlGetUniqueId($expected, $source, $reset = FALSE) {
118 Html::resetSeenIds();
120 $this->assertSame($expected, Html::getUniqueId($source));
124 * Provides test data for testHtmlGetId().
129 public function providerTestHtmlGetUniqueId() {
130 $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
132 // Verify that letters, digits, and hyphens are not stripped from the 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'],
146 * Tests the Html::getUniqueId() method.
148 * @param string $expected
149 * The expected result.
150 * @param string $source
151 * The string being transformed to an ID.
153 * @dataProvider providerTestHtmlGetUniqueIdWithAjaxIds
155 * @covers ::getUniqueId
157 public function testHtmlGetUniqueIdWithAjaxIds($expected, $source) {
158 Html::setIsAjax(TRUE);
159 $id = Html::getUniqueId($source);
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);
167 $random_suffix = substr($id, strlen($source) + 2);
169 $expected = $expected . $random_suffix;
170 $this->assertSame($expected, $id);
174 * Provides test data for testHtmlGetId().
179 public function providerTestHtmlGetUniqueIdWithAjaxIds() {
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'],
190 * Tests the Html::getUniqueId() method.
192 * @param string $expected
193 * The expected result.
194 * @param string $source
195 * The string being transformed to an ID.
197 * @dataProvider providerTestHtmlGetId
201 public function testHtmlGetId($expected, $source) {
202 Html::setIsAjax(FALSE);
203 $this->assertSame($expected, Html::getId($source));
207 * Provides test data for testHtmlGetId().
212 public function providerTestHtmlGetId() {
213 $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
215 // Verify that letters, digits, and hyphens are not stripped from the 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'],
228 * Tests Html::decodeEntities().
230 * @dataProvider providerDecodeEntities
231 * @covers ::decodeEntities
233 public function testDecodeEntities($text, $expected) {
234 $this->assertEquals($expected, Html::decodeEntities($text));
238 * Data provider for testDecodeEntities().
240 * @see testDecodeEntities()
242 public function providerDecodeEntities() {
244 ['Drupal', 'Drupal'],
245 ['<script>', '<script>'],
246 ['<script>', '<script>'],
247 ['<script>', '<script>'],
248 ['&lt;script&gt;', '<script>'],
251 ['&#34;', '"'],
253 ['&quot;', '"'],
256 ['&#39;', '''],
269 * Tests Html::escape().
271 * @dataProvider providerEscape
274 public function testEscape($expected, $text) {
275 $this->assertEquals($expected, Html::escape($text));
279 * Data provider for testEscape().
283 public function providerEscape() {
285 ['Drupal', 'Drupal'],
286 ['<script>', '<script>'],
287 ['&lt;script&gt;', '<script>'],
288 ['&#34;', '"'],
290 ['&quot;', '"'],
292 ['&#039;', '''],
297 ['Drup�al', "Drup\x80al"],
302 * Tests relationship between escaping and decoding HTML entities.
304 * @covers ::decodeEntities
307 public function testDecodeEntitiesAndEscape() {
308 $string = "<em>répété</em>";
309 $escaped = Html::escape($string);
310 $this->assertSame('<em>répét&eacute;</em>', $escaped);
311 $decoded = Html::decodeEntities($escaped);
312 $this->assertSame('<em>répété</em>', $decoded);
313 $decoded = Html::decodeEntities($decoded);
314 $this->assertSame('<em>répété</em>', $decoded);
315 $escaped = Html::escape($decoded);
316 $this->assertSame('<em>répété</em>', $escaped);
320 * Tests Html::serialize().
322 * Resolves an issue by where an empty DOMDocument object sent to serialization would
323 * cause errors in getElementsByTagName() in the serialization function.
325 * @covers ::serialize
327 public function testSerialize() {
328 $document = new \DOMDocument();
329 $result = Html::serialize($document);
330 $this->assertSame('', $result);
334 * @covers ::transformRootRelativeUrlsToAbsolute
335 * @dataProvider providerTestTransformRootRelativeUrlsToAbsolute
337 public function testTransformRootRelativeUrlsToAbsolute($html, $scheme_and_host, $expected_html) {
338 $this->assertSame($expected_html ?: $html, Html::transformRootRelativeUrlsToAbsolute($html, $scheme_and_host));
342 * @covers ::transformRootRelativeUrlsToAbsolute
343 * @dataProvider providerTestTransformRootRelativeUrlsToAbsoluteAssertion
345 public function testTransformRootRelativeUrlsToAbsoluteAssertion($scheme_and_host) {
346 if (method_exists($this, 'expectException')) {
347 $this->expectException(\AssertionError::class);
350 $this->setExpectedException(\AssertionError::class);
352 Html::transformRootRelativeUrlsToAbsolute('', $scheme_and_host);
356 * Provides test data for testTransformRootRelativeUrlsToAbsolute().
361 public function providerTestTransformRootRelativeUrlsToAbsolute() {
365 $random = new Random();
367 // One random tag name.
368 $tag_name = strtolower($random->name(8, TRUE));
370 // A site installed either in the root of a domain or a subdirectory.
371 $base_paths = ['/', '/subdir/' . $random->name(8, TRUE) . '/'];
373 foreach ($base_paths as $base_path) {
374 // The only attribute that has more than just a URL as its value, is
375 // 'srcset', so special-case it.
377 "$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>"],
378 "$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],
379 "$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],
382 foreach (['href', 'poster', 'src', 'cite', 'data', 'action', 'formaction', 'about'] as $attribute) {
384 "$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>"],
385 "$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],
386 "$tag_name, $attribute, $base_path: absolute" => ["<$tag_name $attribute=\"http://example.com{$base_path}absolute\">absolute test</$tag_name>", 'http://example.com', FALSE],
395 * Provides test data for testTransformRootRelativeUrlsToAbsoluteAssertion().
400 public function providerTestTransformRootRelativeUrlsToAbsoluteAssertion() {
402 'only relative path' => ['llama'],
403 'only root-relative path' => ['/llama'],
404 'host and path' => ['example.com/llama'],
405 'scheme, host and path' => ['http://example.com/llama'],
412 * Marks an object's __toString() method as returning markup.
414 class HtmlTestMarkup implements MarkupInterface {