3 namespace Drupal\system\Tests\Theme;
5 use Drupal\Component\Serialization\Json;
6 use Drupal\Component\Utility\Html;
7 use Drupal\Component\Utility\SafeMarkup;
8 use Drupal\Core\Session\UserSession;
10 use Drupal\simpletest\WebTestBase;
13 * Tests for common theme functions.
17 class FunctionsTest extends WebTestBase {
24 public static $modules = ['router_test'];
27 * Tests item-list.html.twig.
29 public function testItemList() {
30 // Verify that empty items produce no output.
33 $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates no output.');
35 // Verify that empty items with title produce no output.
37 $variables['title'] = 'Some title';
39 $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback with title generates no output.');
41 // Verify that empty items produce the empty string.
43 $variables['empty'] = 'No items found.';
44 $expected = '<div class="item-list">No items found.</div>';
45 $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates empty string.');
47 // Verify that empty items produce the empty string with title.
49 $variables['title'] = 'Some title';
50 $variables['empty'] = 'No items found.';
51 $expected = '<div class="item-list"><h3>Some title</h3>No items found.</div>';
52 $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates empty string with title.');
54 // Verify that title set to 0 is output.
56 $variables['title'] = 0;
57 $variables['empty'] = 'No items found.';
58 $expected = '<div class="item-list"><h3>0</h3>No items found.</div>';
59 $this->assertThemeOutput('item_list', $variables, $expected, '%callback with title set to 0 generates a title.');
61 // Verify that title set to a render array is output.
63 $variables['title'] = [
64 '#markup' => '<span>Render array</span>',
66 $variables['empty'] = 'No items found.';
67 $expected = '<div class="item-list"><h3><span>Render array</span></h3>No items found.</div>';
68 $this->assertThemeOutput('item_list', $variables, $expected, '%callback with title set to a render array generates a title.');
70 // Verify that empty text is not displayed when there are list items.
72 $variables['title'] = 'Some title';
73 $variables['empty'] = 'No items found.';
74 $variables['items'] = ['Un', 'Deux', 'Trois'];
75 $expected = '<div class="item-list"><h3>Some title</h3><ul><li>Un</li><li>Deux</li><li>Trois</li></ul></div>';
76 $this->assertThemeOutput('item_list', $variables, $expected, '%callback does not print empty text when there are list items.');
78 // Verify nested item lists.
80 $variables['title'] = 'Some title';
81 $variables['attributes'] = [
84 $variables['items'] = [
85 // A plain string value forms an own item.
87 // Items can be fully-fledged render arrays with their own attributes.
89 '#wrapper_attributes' => [
94 '#theme' => 'item_list',
95 '#attributes' => ['id' => 'blist'],
101 '#wrapper_attributes' => ['class' => ['item-class-bb']],
106 // However, items can also be child #items.
110 '#attributes' => ['id' => 'clist'],
114 '#wrapper_attributes' => ['class' => ['item-class-cb']],
123 // Use #markup to be able to specify #wrapper_attributes.
126 '#wrapper_attributes' => ['id' => 'item-id-d'],
128 // An empty item with attributes.
130 '#wrapper_attributes' => ['id' => 'item-id-e'],
132 // Lastly, another plain string item.
136 $inner_b = '<div class="item-list"><ol id="blist">';
137 $inner_b .= '<li>ba</li>';
138 $inner_b .= '<li class="item-class-bb">bb</li>';
139 $inner_b .= '</ol></div>';
141 $inner_cb = '<div class="item-list"><ul>';
142 $inner_cb .= '<li>cba</li>';
143 $inner_cb .= '<li>cbb</li>';
144 $inner_cb .= '</ul></div>';
146 $inner_c = '<div class="item-list"><ul id="clist">';
147 $inner_c .= '<li>ca</li>';
148 $inner_c .= '<li class="item-class-cb">cb' . $inner_cb . '</li>';
149 $inner_c .= '<li>cc</li>';
150 $inner_c .= '</ul></div>';
152 $expected = '<div class="item-list">';
153 $expected .= '<h3>Some title</h3>';
154 $expected .= '<ul id="parentlist">';
155 $expected .= '<li>a</li>';
156 $expected .= '<li id="item-id-b">b' . $inner_b . '</li>';
157 $expected .= '<li>c' . $inner_c . '</li>';
158 $expected .= '<li id="item-id-d">d</li>';
159 $expected .= '<li id="item-id-e"></li>';
160 $expected .= '<li>f</li>';
161 $expected .= '</ul></div>';
163 $this->assertThemeOutput('item_list', $variables, $expected);
167 * Tests links.html.twig.
169 public function testLinks() {
170 // Turn off the query for the
171 // \Drupal\Core\Utility\LinkGeneratorInterface::generate() method to compare
172 // the active link correctly.
173 $original_query = \Drupal::request()->query->all();
174 \Drupal::request()->query->replace([]);
175 // Verify that empty variables produce no output.
178 $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback generates no output.');
181 $variables['heading'] = 'Some title';
183 $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback with heading generates no output.');
185 // Verify that a list of links is properly rendered.
187 $variables['attributes'] = ['id' => 'somelinks'];
188 $variables['links'] = [
190 'title' => 'A <link>',
191 'url' => Url::fromUri('base:a/link'),
194 'title' => 'Plain "text"',
197 'title' => SafeMarkup::format('<span class="unescaped">@text</span>', ['@text' => 'potentially unsafe text that <should> be escaped']),
200 'title' => 'Front page',
201 'url' => Url::fromRoute('<front>'),
204 'title' => 'Test route',
205 'url' => Url::fromRoute('router_test.1'),
208 'title' => 'Query test route',
209 'url' => Url::fromRoute('router_test.1'),
216 $expected_links = '';
217 $expected_links .= '<ul id="somelinks">';
218 $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base:a/link')->toString() . '">' . Html::escape('A <link>') . '</a></li>';
219 $expected_links .= '<li class="plain-text">' . Html::escape('Plain "text"') . '</li>';
220 $expected_links .= '<li class="html-text"><span class="unescaped">' . Html::escape('potentially unsafe text that <should> be escaped') . '</span></li>';
221 $expected_links .= '<li class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '">' . Html::escape('Front page') . '</a></li>';
222 $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . Html::escape('Test route') . '</a></li>';
223 $query = ['key' => 'value'];
224 $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . Html::escape('Query test route') . '</a></li>';
225 $expected_links .= '</ul>';
227 // Verify that passing a string as heading works.
228 $variables['heading'] = 'Links heading';
229 $expected_heading = '<h2>Links heading</h2>';
230 $expected = $expected_heading . $expected_links;
231 $this->assertThemeOutput('links', $variables, $expected);
233 // Restore the original request's query.
234 \Drupal::request()->query->replace($original_query);
236 // Verify that passing an array as heading works (core support).
237 $variables['heading'] = [
238 'text' => 'Links heading',
240 'attributes' => ['class' => ['heading']],
242 $expected_heading = '<h3 class="heading">Links heading</h3>';
243 $expected = $expected_heading . $expected_links;
244 $this->assertThemeOutput('links', $variables, $expected);
246 // Verify that passing attributes for the heading works.
247 $variables['heading'] = ['text' => 'Links heading', 'level' => 'h3', 'attributes' => ['id' => 'heading']];
248 $expected_heading = '<h3 id="heading">Links heading</h3>';
249 $expected = $expected_heading . $expected_links;
250 $this->assertThemeOutput('links', $variables, $expected);
252 // Verify that passing attributes for the links work.
253 $variables['links']['plain text']['attributes'] = [
254 'class' => ['a/class'],
256 $expected_links = '';
257 $expected_links .= '<ul id="somelinks">';
258 $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base:a/link')->toString() . '">' . Html::escape('A <link>') . '</a></li>';
259 $expected_links .= '<li class="plain-text"><span class="a/class">' . Html::escape('Plain "text"') . '</span></li>';
260 $expected_links .= '<li class="html-text"><span class="unescaped">' . Html::escape('potentially unsafe text that <should> be escaped') . '</span></li>';
261 $expected_links .= '<li class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '">' . Html::escape('Front page') . '</a></li>';
262 $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . Html::escape('Test route') . '</a></li>';
263 $query = ['key' => 'value'];
264 $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . Html::escape('Query test route') . '</a></li>';
265 $expected_links .= '</ul>';
266 $expected = $expected_heading . $expected_links;
267 $this->assertThemeOutput('links', $variables, $expected);
269 // Verify the data- attributes for setting the "active" class on links.
270 \Drupal::currentUser()->setAccount(new UserSession(['uid' => 1]));
271 $variables['set_active_class'] = TRUE;
272 $expected_links = '';
273 $expected_links .= '<ul id="somelinks">';
274 $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base:a/link')->toString() . '">' . Html::escape('A <link>') . '</a></li>';
275 $expected_links .= '<li class="plain-text"><span class="a/class">' . Html::escape('Plain "text"') . '</span></li>';
276 $expected_links .= '<li class="html-text"><span class="unescaped">' . Html::escape('potentially unsafe text that <should> be escaped') . '</span></li>';
277 $expected_links .= '<li data-drupal-link-system-path="<front>" class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '" data-drupal-link-system-path="<front>">' . Html::escape('Front page') . '</a></li>';
278 $expected_links .= '<li data-drupal-link-system-path="router_test/test1" class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '" data-drupal-link-system-path="router_test/test1">' . Html::escape('Test route') . '</a></li>';
279 $query = ['key' => 'value'];
280 $encoded_query = Html::escape(Json::encode($query));
281 $expected_links .= '<li data-drupal-link-query="' . $encoded_query . '" data-drupal-link-system-path="router_test/test1" class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '" data-drupal-link-query="' . $encoded_query . '" data-drupal-link-system-path="router_test/test1">' . Html::escape('Query test route') . '</a></li>';
282 $expected_links .= '</ul>';
283 $expected = $expected_heading . $expected_links;
284 $this->assertThemeOutput('links', $variables, $expected);
288 * Tests links.html.twig using links with indexed keys.
290 public function testIndexedKeyedLinks() {
291 // Turn off the query for the
292 // \Drupal\Core\Utility\LinkGeneratorInterface::generate() method to compare
293 // the active link correctly.
294 $original_query = \Drupal::request()->query->all();
295 \Drupal::request()->query->replace([]);
296 // Verify that empty variables produce no output.
299 $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback generates no output.');
302 $variables['heading'] = 'Some title';
304 $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback with heading generates no output.');
306 // Verify that a list of links is properly rendered.
308 $variables['attributes'] = ['id' => 'somelinks'];
309 $variables['links'] = [
311 'title' => 'A <link>',
312 'url' => Url::fromUri('base:a/link'),
315 'title' => 'Plain "text"',
318 'title' => SafeMarkup::format('<span class="unescaped">@text</span>', ['@text' => 'potentially unsafe text that <should> be escaped']),
321 'title' => 'Front page',
322 'url' => Url::fromRoute('<front>'),
325 'title' => 'Test route',
326 'url' => Url::fromRoute('router_test.1'),
329 'title' => 'Query test route',
330 'url' => Url::fromRoute('router_test.1'),
337 $expected_links = '';
338 $expected_links .= '<ul id="somelinks">';
339 $expected_links .= '<li><a href="' . Url::fromUri('base:a/link')->toString() . '">' . Html::escape('A <link>') . '</a></li>';
340 $expected_links .= '<li>' . Html::escape('Plain "text"') . '</li>';
341 $expected_links .= '<li><span class="unescaped">' . Html::escape('potentially unsafe text that <should> be escaped') . '</span></li>';
342 $expected_links .= '<li><a href="' . Url::fromRoute('<front>')->toString() . '">' . Html::escape('Front page') . '</a></li>';
343 $expected_links .= '<li><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . Html::escape('Test route') . '</a></li>';
344 $query = ['key' => 'value'];
345 $expected_links .= '<li><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . Html::escape('Query test route') . '</a></li>';
346 $expected_links .= '</ul>';
348 // Verify that passing a string as heading works.
349 $variables['heading'] = 'Links heading';
350 $expected_heading = '<h2>Links heading</h2>';
351 $expected = $expected_heading . $expected_links;
352 $this->assertThemeOutput('links', $variables, $expected);
354 // Restore the original request's query.
355 \Drupal::request()->query->replace($original_query);
357 // Verify that passing an array as heading works (core support).
358 $variables['heading'] = [
359 'text' => 'Links heading',
361 'attributes' => ['class' => ['heading']],
363 $expected_heading = '<h3 class="heading">Links heading</h3>';
364 $expected = $expected_heading . $expected_links;
365 $this->assertThemeOutput('links', $variables, $expected);
367 // Verify that passing attributes for the heading works.
368 $variables['heading'] = ['text' => 'Links heading', 'level' => 'h3', 'attributes' => ['id' => 'heading']];
369 $expected_heading = '<h3 id="heading">Links heading</h3>';
370 $expected = $expected_heading . $expected_links;
371 $this->assertThemeOutput('links', $variables, $expected);
373 // Verify that passing attributes for the links work.
374 $variables['links'][1]['attributes'] = [
375 'class' => ['a/class'],
377 $expected_links = '';
378 $expected_links .= '<ul id="somelinks">';
379 $expected_links .= '<li><a href="' . Url::fromUri('base:a/link')->toString() . '">' . Html::escape('A <link>') . '</a></li>';
380 $expected_links .= '<li><span class="a/class">' . Html::escape('Plain "text"') . '</span></li>';
381 $expected_links .= '<li><span class="unescaped">' . Html::escape('potentially unsafe text that <should> be escaped') . '</span></li>';
382 $expected_links .= '<li><a href="' . Url::fromRoute('<front>')->toString() . '">' . Html::escape('Front page') . '</a></li>';
383 $expected_links .= '<li><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . Html::escape('Test route') . '</a></li>';
384 $query = ['key' => 'value'];
385 $expected_links .= '<li><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . Html::escape('Query test route') . '</a></li>';
386 $expected_links .= '</ul>';
387 $expected = $expected_heading . $expected_links;
388 $this->assertThemeOutput('links', $variables, $expected);
390 // Verify the data- attributes for setting the "active" class on links.
391 \Drupal::currentUser()->setAccount(new UserSession(['uid' => 1]));
392 $variables['set_active_class'] = TRUE;
393 $expected_links = '';
394 $expected_links .= '<ul id="somelinks">';
395 $expected_links .= '<li><a href="' . Url::fromUri('base:a/link')->toString() . '">' . Html::escape('A <link>') . '</a></li>';
396 $expected_links .= '<li><span class="a/class">' . Html::escape('Plain "text"') . '</span></li>';
397 $expected_links .= '<li><span class="unescaped">' . Html::escape('potentially unsafe text that <should> be escaped') . '</span></li>';
398 $expected_links .= '<li data-drupal-link-system-path="<front>"><a href="' . Url::fromRoute('<front>')->toString() . '" data-drupal-link-system-path="<front>">' . Html::escape('Front page') . '</a></li>';
399 $expected_links .= '<li data-drupal-link-system-path="router_test/test1"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '" data-drupal-link-system-path="router_test/test1">' . Html::escape('Test route') . '</a></li>';
400 $query = ['key' => 'value'];
401 $encoded_query = Html::escape(Json::encode($query));
402 $expected_links .= '<li data-drupal-link-query="' . $encoded_query . '" data-drupal-link-system-path="router_test/test1"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '" data-drupal-link-query="' . $encoded_query . '" data-drupal-link-system-path="router_test/test1">' . Html::escape('Query test route') . '</a></li>';
403 $expected_links .= '</ul>';
404 $expected = $expected_heading . $expected_links;
405 $this->assertThemeOutput('links', $variables, $expected);
409 * Test the use of drupal_pre_render_links() on a nested array of links.
411 public function testDrupalPreRenderLinks() {
412 // Define the base array to be rendered, containing a variety of different
416 '#pre_render' => ['drupal_pre_render_links'],
419 'title' => 'Parent link original',
420 'url' => Url::fromRoute('router_test.1'),
426 // This should be rendered if 'first_child' is rendered separately,
427 // but ignored if the parent is being rendered (since it duplicates
428 // one of the parent's links).
430 'title' => 'Parent link copy',
431 'url' => Url::fromRoute('router_test.6'),
433 // This should always be rendered.
434 'first_child_link' => [
435 'title' => 'First child link',
436 'url' => Url::fromRoute('router_test.7'),
440 // This should always be rendered as part of the parent.
444 'second_child_link' => [
445 'title' => 'Second child link',
446 'url' => Url::fromRoute('router_test.8'),
450 // This should never be rendered, since the user does not have access to
455 'third_child_link' => [
456 'title' => 'Third child link',
457 'url' => Url::fromRoute('router_test.9'),
464 // Start with a fresh copy of the base array, and try rendering the entire
465 // thing. We expect a single <ul> with appropriate links contained within
467 $render_array = $base_array;
468 $html = \Drupal::service('renderer')->renderRoot($render_array);
469 $dom = new \DOMDocument();
470 $dom->loadHTML($html);
471 $this->assertEqual($dom->getElementsByTagName('ul')->length, 1, 'One "ul" tag found in the rendered HTML.');
472 $list_elements = $dom->getElementsByTagName('li');
473 $this->assertEqual($list_elements->length, 3, 'Three "li" tags found in the rendered HTML.');
474 $this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link original', 'First expected link found.');
475 $this->assertEqual($list_elements->item(1)->nodeValue, 'First child link', 'Second expected link found.');
476 $this->assertEqual($list_elements->item(2)->nodeValue, 'Second child link', 'Third expected link found.');
477 $this->assertIdentical(strpos($html, 'Parent link copy'), FALSE, '"Parent link copy" link not found.');
478 $this->assertIdentical(strpos($html, 'Third child link'), FALSE, '"Third child link" link not found.');
480 // Now render 'first_child', followed by the rest of the links, and make
481 // sure we get two separate <ul>'s with the appropriate links contained
483 $render_array = $base_array;
484 $child_html = \Drupal::service('renderer')->renderRoot($render_array['first_child']);
485 $parent_html = \Drupal::service('renderer')->renderRoot($render_array);
486 // First check the child HTML.
487 $dom = new \DOMDocument();
488 $dom->loadHTML($child_html);
489 $this->assertEqual($dom->getElementsByTagName('ul')->length, 1, 'One "ul" tag found in the rendered child HTML.');
490 $list_elements = $dom->getElementsByTagName('li');
491 $this->assertEqual($list_elements->length, 2, 'Two "li" tags found in the rendered child HTML.');
492 $this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link copy', 'First expected link found.');
493 $this->assertEqual($list_elements->item(1)->nodeValue, 'First child link', 'Second expected link found.');
494 // Then check the parent HTML.
495 $dom = new \DOMDocument();
496 $dom->loadHTML($parent_html);
497 $this->assertEqual($dom->getElementsByTagName('ul')->length, 1, 'One "ul" tag found in the rendered parent HTML.');
498 $list_elements = $dom->getElementsByTagName('li');
499 $this->assertEqual($list_elements->length, 2, 'Two "li" tags found in the rendered parent HTML.');
500 $this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link original', 'First expected link found.');
501 $this->assertEqual($list_elements->item(1)->nodeValue, 'Second child link', 'Second expected link found.');
502 $this->assertIdentical(strpos($parent_html, 'First child link'), FALSE, '"First child link" link not found.');
503 $this->assertIdentical(strpos($parent_html, 'Third child link'), FALSE, '"Third child link" link not found.');
507 * Tests theme_image().
509 public function testImage() {
510 // Test that data URIs work with theme_image().
512 $variables['uri'] = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
513 $variables['alt'] = 'Data URI image of a red dot';
514 $expected = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Data URI image of a red dot" />' . "\n";
515 $this->assertThemeOutput('image', $variables, $expected);