Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\EnginePhpTemplateTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests theme functions with PHPTemplate.
*
* @group Theme
*/
class EnginePhpTemplateTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme_phptemplate'));
}
/**
* Ensures a theme's template is overridable based on the 'template' filename.
*/
function testTemplateOverride() {
$this->config('system.theme')
->set('default', 'test_theme_phptemplate')
->save();
$this->drupalGet('theme-test/template-test');
$this->assertText('Success: Template overridden with PHPTemplate theme.', 'Template overridden by PHPTemplate file.');
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\EngineTwigTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Tests Twig-specific theme functionality.
*
* @group Theme
*/
class EngineTwigTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test', 'twig_theme_test');
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme'));
}
/**
* Tests that the Twig engine handles PHP data correctly.
*/
function testTwigVariableDataTypes() {
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('twig-theme-test/php-variables');
foreach (_test_theme_twig_php_values() as $type => $value) {
$this->assertRaw('<li>' . $type . ': ' . $value['expected'] . '</li>');
}
}
/**
* Tests the url and url_generate Twig functions.
*/
public function testTwigUrlGenerator() {
$this->drupalGet('twig-theme-test/url-generator');
// Find the absolute URL of the current site.
$url_generator = $this->container->get('url_generator');
$expected = array(
'path (as route) not absolute: ' . $url_generator->generateFromRoute('user.register'),
'url (as route) absolute: ' . $url_generator->generateFromRoute('user.register', array(), array('absolute' => TRUE)),
'path (as route) not absolute with fragment: ' . $url_generator->generateFromRoute('user.register', array(), array('fragment' => 'bottom')),
'url (as route) absolute despite option: ' . $url_generator->generateFromRoute('user.register', array(), array('absolute' => TRUE)),
'url (as route) absolute with fragment: ' . $url_generator->generateFromRoute('user.register', array(), array('absolute' => TRUE, 'fragment' => 'bottom')),
);
// Verify that url() has the ability to bubble cacheability metadata:
// absolute URLs should bubble the 'url.site' cache context. (This only
// needs to test that cacheability metadata is bubbled *at all*; detailed
// tests for *which* cacheability metadata is bubbled live elsewhere.)
$this->assertCacheContext('url.site');
// Make sure we got something.
$content = $this->getRawContent();
$this->assertFalse(empty($content), 'Page content is not empty');
foreach ($expected as $string) {
$this->assertRaw('<div>' . $string . '</div>');
}
}
/**
* Tests the link_generator Twig functions.
*/
public function testTwigLinkGenerator() {
$this->drupalGet('twig-theme-test/link-generator');
/** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
$link_generator = $this->container->get('link_generator');
$expected = [
'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE])),
'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE, 'attributes' => ['foo' => 'bar']])),
'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['foo' => 'bar', 'id' => 'kitten']])),
'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['id' => 'kitten']])),
];
// Verify that link() has the ability to bubble cacheability metadata:
// absolute URLs should bubble the 'url.site' cache context. (This only
// needs to test that cacheability metadata is bubbled *at all*; detailed
// tests for *which* cacheability metadata is bubbled live elsewhere.)
$this->assertCacheContext('url.site');
$content = $this->getRawContent();
$this->assertFalse(empty($content), 'Page content is not empty');
foreach ($expected as $string) {
$this->assertRaw('<div>' . $string . '</div>');
}
}
/**
* Tests the magic url to string Twig functions.
*
* @see \Drupal\Core\Url
*/
public function testTwigUrlToString() {
$this->drupalGet('twig-theme-test/url-to-string');
$expected = [
'rendered url: ' . Url::fromRoute('user.register')->toString(),
];
$content = $this->getRawContent();
$this->assertFalse(empty($content), 'Page content is not empty');
foreach ($expected as $string) {
$this->assertRaw('<div>' . $string . '</div>');
}
}
/**
* Tests the automatic/magic calling of toString() on objects, if exists.
*/
public function testTwigFileUrls() {
$this->drupalGet('/twig-theme-test/file-url');
$filepath = file_create_url('core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js');
$this->assertRaw('<div>file_url: ' . $filepath . '</div>');
}
/**
* Tests the attach of asset libraries.
*/
public function testTwigAttachLibrary() {
$this->drupalGet('/twig-theme-test/attach-library');
$this->assertRaw('ckeditor.js');
}
}

View file

@ -0,0 +1,149 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\EntityFilteringThemeTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\comment\CommentInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\simpletest\WebTestBase;
/**
* Tests themed output for each entity type in all available themes to ensure
* entity labels are filtered for XSS.
*
* @group Theme
*/
class EntityFilteringThemeTest extends WebTestBase {
use CommentTestTrait;
/**
* Use the standard profile.
*
* We test entity theming with the default node, user, comment, and taxonomy
* configurations at several paths in the standard profile.
*
* @var string
*/
protected $profile = 'standard';
/**
* A list of all available themes.
*
* @var \Drupal\Core\Extension\Extension[]
*/
protected $themes;
/**
* A test user.
*
* @var \Drupal\user\User
*/
protected $user;
/**
* A test node.
*
* @var \Drupal\node\Node
*/
protected $node;
/**
* A test taxonomy term.
*
* @var \Drupal\taxonomy\Term
*/
protected $term;
/**
* A test comment.
*
* @var \Drupal\comment\Comment
*/
protected $comment;
/**
* A string containing markup and JS.
*
* @string
*/
protected $xssLabel = "string with <em>HTML</em> and <script>alert('JS');</script>";
protected function setUp() {
parent::setUp();
// Install all available non-testing themes.
$listing = new ExtensionDiscovery(\Drupal::root());
$this->themes = $listing->scan('theme', FALSE);
\Drupal::service('theme_handler')->install(array_keys($this->themes));
// Create a test user.
$this->user = $this->drupalCreateUser(array('access content', 'access user profiles'));
$this->user->name = $this->xssLabel;
$this->user->save();
$this->drupalLogin($this->user);
// Create a test term.
$this->term = entity_create('taxonomy_term', array(
'name' => $this->xssLabel,
'vid' => 1,
));
$this->term->save();
// Add a comment field.
$this->addDefaultCommentField('node', 'article', 'comment', CommentItemInterface::OPEN);
// Create a test node tagged with the test term.
$this->node = $this->drupalCreateNode(array(
'title' => $this->xssLabel,
'type' => 'article',
'promote' => NODE_PROMOTED,
'field_tags' => array(array('target_id' => $this->term->id())),
));
// Create a test comment on the test node.
$this->comment = entity_create('comment', array(
'entity_id' => $this->node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'status' => CommentInterface::PUBLISHED,
'subject' => $this->xssLabel,
'comment_body' => array($this->randomMachineName()),
));
$this->comment->save();
}
/**
* Checks each themed entity for XSS filtering in available themes.
*/
function testThemedEntity() {
// Check paths where various view modes of the entities are rendered.
$paths = array(
'user',
'node',
'node/' . $this->node->id(),
'taxonomy/term/' . $this->term->id(),
);
// Check each path in all available themes.
foreach ($this->themes as $name => $theme) {
$this->config('system.theme')
->set('default', $name)
->save();
foreach ($paths as $path) {
$this->drupalGet($path);
$this->assertResponse(200);
$this->assertNoRaw($this->xssLabel);
}
}
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\FastTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests autocompletion not loading registry.
*
* @group Theme
*/
class FastTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
protected function setUp() {
parent::setUp();
$this->account = $this->drupalCreateUser(array('access user profiles'));
}
/**
* Tests access to user autocompletion and verify the correct results.
*/
function testUserAutocomplete() {
$this->drupalLogin($this->account);
$this->drupalGet('user/autocomplete', array('query' => array('q' => $this->account->getUsername())));
$this->assertRaw($this->account->getUsername());
$this->assertNoText('registry initialized', 'The registry was not initialized');
}
}

View file

@ -0,0 +1,400 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\FunctionsTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Tests for common theme functions.
*
* @group Theme
*/
class FunctionsTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('router_test');
/**
* Tests item-list.html.twig.
*/
function testItemList() {
// Verify that empty items produce no output.
$variables = array();
$expected = '';
$this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates no output.');
// Verify that empty items with title produce no output.
$variables = array();
$variables['title'] = 'Some title';
$expected = '';
$this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback with title generates no output.');
// Verify that empty items produce the empty string.
$variables = array();
$variables['empty'] = 'No items found.';
$expected = '<div class="item-list">No items found.</div>';
$this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates empty string.');
// Verify that empty items produce the empty string with title.
$variables = array();
$variables['title'] = 'Some title';
$variables['empty'] = 'No items found.';
$expected = '<div class="item-list"><h3>Some title</h3>No items found.</div>';
$this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates empty string with title.');
// Verify that title set to 0 is output.
$variables = array();
$variables['title'] = 0;
$variables['empty'] = 'No items found.';
$expected = '<div class="item-list"><h3>0</h3>No items found.</div>';
$this->assertThemeOutput('item_list', $variables, $expected, '%callback with title set to 0 generates a title.');
// Verify that title set to a render array is output.
$variables = array();
$variables['title'] = array(
'#markup' => '<span>Render array</span>',
);
$variables['empty'] = 'No items found.';
$expected = '<div class="item-list"><h3><span>Render array</span></h3>No items found.</div>';
$this->assertThemeOutput('item_list', $variables, $expected, '%callback with title set to a render array generates a title.');
// Verify that empty text is not displayed when there are list items.
$variables = array();
$variables['title'] = 'Some title';
$variables['empty'] = 'No items found.';
$variables['items'] = array('Un', 'Deux', 'Trois');
$expected = '<div class="item-list"><h3>Some title</h3><ul><li>Un</li><li>Deux</li><li>Trois</li></ul></div>';
$this->assertThemeOutput('item_list', $variables, $expected, '%callback does not print empty text when there are list items.');
// Verify nested item lists.
$variables = array();
$variables['title'] = 'Some title';
$variables['attributes'] = array(
'id' => 'parentlist',
);
$variables['items'] = array(
// A plain string value forms an own item.
'a',
// Items can be fully-fledged render arrays with their own attributes.
array(
'#wrapper_attributes' => array(
'id' => 'item-id-b',
),
'#markup' => 'b',
'childlist' => array(
'#theme' => 'item_list',
'#attributes' => array('id' => 'blist'),
'#list_type' => 'ol',
'#items' => array(
'ba',
array(
'#markup' => 'bb',
'#wrapper_attributes' => array('class' => array('item-class-bb')),
),
),
),
),
// However, items can also be child #items.
array(
'#markup' => 'c',
'childlist' => array(
'#attributes' => array('id' => 'clist'),
'ca',
array(
'#markup' => 'cb',
'#wrapper_attributes' => array('class' => array('item-class-cb')),
'children' => array(
'cba',
'cbb',
),
),
'cc',
),
),
// Use #markup to be able to specify #wrapper_attributes.
array(
'#markup' => 'd',
'#wrapper_attributes' => array('id' => 'item-id-d'),
),
// An empty item with attributes.
array(
'#wrapper_attributes' => array('id' => 'item-id-e'),
),
// Lastly, another plain string item.
'f',
);
$inner_b = '<div class="item-list"><ol id="blist">';
$inner_b .= '<li>ba</li>';
$inner_b .= '<li class="item-class-bb">bb</li>';
$inner_b .= '</ol></div>';
$inner_cb = '<div class="item-list"><ul>';
$inner_cb .= '<li>cba</li>';
$inner_cb .= '<li>cbb</li>';
$inner_cb .= '</ul></div>';
$inner_c = '<div class="item-list"><ul id="clist">';
$inner_c .= '<li>ca</li>';
$inner_c .= '<li class="item-class-cb">cb' . $inner_cb . '</li>';
$inner_c .= '<li>cc</li>';
$inner_c .= '</ul></div>';
$expected = '<div class="item-list">';
$expected .= '<h3>Some title</h3>';
$expected .= '<ul id="parentlist">';
$expected .= '<li>a</li>';
$expected .= '<li id="item-id-b">b' . $inner_b . '</li>';
$expected .= '<li>c' . $inner_c . '</li>';
$expected .= '<li id="item-id-d">d</li>';
$expected .= '<li id="item-id-e"></li>';
$expected .= '<li>f</li>';
$expected .= '</ul></div>';
$this->assertThemeOutput('item_list', $variables, $expected);
}
/**
* Tests links.html.twig.
*/
function testLinks() {
// Turn off the query for the _l() function to compare the active
// link correctly.
$original_query = \Drupal::request()->query->all();
\Drupal::request()->query->replace(array());
// Verify that empty variables produce no output.
$variables = array();
$expected = '';
$this->assertThemeOutput('links', $variables, $expected, 'Empty %callback generates no output.');
$variables = array();
$variables['heading'] = 'Some title';
$expected = '';
$this->assertThemeOutput('links', $variables, $expected, 'Empty %callback with heading generates no output.');
// Verify that a list of links is properly rendered.
$variables = array();
$variables['attributes'] = array('id' => 'somelinks');
$variables['links'] = array(
'a link' => array(
'title' => 'A <link>',
'url' => Url::fromUri('base:a/link'),
),
'plain text' => array(
'title' => 'Plain "text"',
),
'html text' => array(
'title' => SafeMarkup::format('<span class="unescaped">@text</span>', array('@text' => 'potentially unsafe text that <should> be escaped')),
),
'front page' => array(
'title' => 'Front page',
'url' => Url::fromRoute('<front>'),
),
'router-test' => array(
'title' => 'Test route',
'url' => Url::fromRoute('router_test.1'),
),
'query-test' => array(
'title' => 'Query test route',
'url' => Url::fromRoute('router_test.1'),
'query' => array(
'key' => 'value',
)
),
);
$expected_links = '';
$expected_links .= '<ul id="somelinks">';
$expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base:a/link')->toString() . '">' . SafeMarkup::checkPlain('A <link>') . '</a></li>';
$expected_links .= '<li class="plain-text">' . SafeMarkup::checkPlain('Plain "text"') . '</li>';
$expected_links .= '<li class="html-text"><span class="unescaped">' . SafeMarkup::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
$expected_links .= '<li class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '">' . SafeMarkup::checkPlain('Front page') . '</a></li>';
$expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . SafeMarkup::checkPlain('Test route') . '</a></li>';
$query = array('key' => 'value');
$expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . SafeMarkup::checkPlain('Query test route') . '</a></li>';
$expected_links .= '</ul>';
// Verify that passing a string as heading works.
$variables['heading'] = 'Links heading';
$expected_heading = '<h2>Links heading</h2>';
$expected = $expected_heading . $expected_links;
$this->assertThemeOutput('links', $variables, $expected);
// Restore the original request's query.
\Drupal::request()->query->replace($original_query);
// Verify that passing an array as heading works (core support).
$variables['heading'] = array(
'text' => 'Links heading',
'level' => 'h3',
'attributes' => array('class' => array('heading')),
);
$expected_heading = '<h3 class="heading">Links heading</h3>';
$expected = $expected_heading . $expected_links;
$this->assertThemeOutput('links', $variables, $expected);
// Verify that passing attributes for the heading works.
$variables['heading'] = array('text' => 'Links heading', 'level' => 'h3', 'attributes' => array('id' => 'heading'));
$expected_heading = '<h3 id="heading">Links heading</h3>';
$expected = $expected_heading . $expected_links;
$this->assertThemeOutput('links', $variables, $expected);
// Verify that passing attributes for the links work.
$variables['links']['plain text']['attributes'] = array(
'class' => array('a/class'),
);
$expected_links = '';
$expected_links .= '<ul id="somelinks">';
$expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base:a/link')->toString() . '">' . SafeMarkup::checkPlain('A <link>') . '</a></li>';
$expected_links .= '<li class="plain-text"><span class="a/class">' . SafeMarkup::checkPlain('Plain "text"') . '</span></li>';
$expected_links .= '<li class="html-text"><span class="unescaped">' . SafeMarkup::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
$expected_links .= '<li class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '">' . SafeMarkup::checkPlain('Front page') . '</a></li>';
$expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . SafeMarkup::checkPlain('Test route') . '</a></li>';
$query = array('key' => 'value');
$expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . SafeMarkup::checkPlain('Query test route') . '</a></li>';
$expected_links .= '</ul>';
$expected = $expected_heading . $expected_links;
$this->assertThemeOutput('links', $variables, $expected);
// Verify the data- attributes for setting the "active" class on links.
\Drupal::currentUser()->setAccount(new UserSession(array('uid' => 1)));
$variables['set_active_class'] = TRUE;
$expected_links = '';
$expected_links .= '<ul id="somelinks">';
$expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base:a/link')->toString() . '">' . SafeMarkup::checkPlain('A <link>') . '</a></li>';
$expected_links .= '<li class="plain-text"><span class="a/class">' . SafeMarkup::checkPlain('Plain "text"') . '</span></li>';
$expected_links .= '<li class="html-text"><span class="unescaped">' . SafeMarkup::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
$expected_links .= '<li data-drupal-link-system-path="&lt;front&gt;" class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '" data-drupal-link-system-path="&lt;front&gt;">' . SafeMarkup::checkPlain('Front page') . '</a></li>';
$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">' . SafeMarkup::checkPlain('Test route') . '</a></li>';
$query = array('key' => 'value');
$encoded_query = SafeMarkup::checkPlain(Json::encode($query));
$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">' . SafeMarkup::checkPlain('Query test route') . '</a></li>';
$expected_links .= '</ul>';
$expected = $expected_heading . $expected_links;
$this->assertThemeOutput('links', $variables, $expected);
}
/**
* Test the use of drupal_pre_render_links() on a nested array of links.
*/
function testDrupalPreRenderLinks() {
// Define the base array to be rendered, containing a variety of different
// kinds of links.
$base_array = array(
'#theme' => 'links',
'#pre_render' => array('drupal_pre_render_links'),
'#links' => array(
'parent_link' => array(
'title' => 'Parent link original',
'url' => Url::fromRoute('router_test.1'),
),
),
'first_child' => array(
'#theme' => 'links',
'#links' => array(
// This should be rendered if 'first_child' is rendered separately,
// but ignored if the parent is being rendered (since it duplicates
// one of the parent's links).
'parent_link' => array(
'title' => 'Parent link copy',
'url' => Url::fromRoute('router_test.6'),
),
// This should always be rendered.
'first_child_link' => array(
'title' => 'First child link',
'url' => Url::fromRoute('router_test.7'),
),
),
),
// This should always be rendered as part of the parent.
'second_child' => array(
'#theme' => 'links',
'#links' => array(
'second_child_link' => array(
'title' => 'Second child link',
'url' => Url::fromRoute('router_test.8'),
),
),
),
// This should never be rendered, since the user does not have access to
// it.
'third_child' => array(
'#theme' => 'links',
'#links' => array(
'third_child_link' => array(
'title' => 'Third child link',
'url' => Url::fromRoute('router_test.9'),
),
),
'#access' => FALSE,
),
);
// Start with a fresh copy of the base array, and try rendering the entire
// thing. We expect a single <ul> with appropriate links contained within
// it.
$render_array = $base_array;
$html = \Drupal::service('renderer')->renderRoot($render_array);
$dom = new \DOMDocument();
$dom->loadHTML($html);
$this->assertEqual($dom->getElementsByTagName('ul')->length, 1, 'One "ul" tag found in the rendered HTML.');
$list_elements = $dom->getElementsByTagName('li');
$this->assertEqual($list_elements->length, 3, 'Three "li" tags found in the rendered HTML.');
$this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link original', 'First expected link found.');
$this->assertEqual($list_elements->item(1)->nodeValue, 'First child link', 'Second expected link found.');
$this->assertEqual($list_elements->item(2)->nodeValue, 'Second child link', 'Third expected link found.');
$this->assertIdentical(strpos($html, 'Parent link copy'), FALSE, '"Parent link copy" link not found.');
$this->assertIdentical(strpos($html, 'Third child link'), FALSE, '"Third child link" link not found.');
// Now render 'first_child', followed by the rest of the links, and make
// sure we get two separate <ul>'s with the appropriate links contained
// within each.
$render_array = $base_array;
$child_html = \Drupal::service('renderer')->renderRoot($render_array['first_child']);
$parent_html = \Drupal::service('renderer')->renderRoot($render_array);
// First check the child HTML.
$dom = new \DOMDocument();
$dom->loadHTML($child_html);
$this->assertEqual($dom->getElementsByTagName('ul')->length, 1, 'One "ul" tag found in the rendered child HTML.');
$list_elements = $dom->getElementsByTagName('li');
$this->assertEqual($list_elements->length, 2, 'Two "li" tags found in the rendered child HTML.');
$this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link copy', 'First expected link found.');
$this->assertEqual($list_elements->item(1)->nodeValue, 'First child link', 'Second expected link found.');
// Then check the parent HTML.
$dom = new \DOMDocument();
$dom->loadHTML($parent_html);
$this->assertEqual($dom->getElementsByTagName('ul')->length, 1, 'One "ul" tag found in the rendered parent HTML.');
$list_elements = $dom->getElementsByTagName('li');
$this->assertEqual($list_elements->length, 2, 'Two "li" tags found in the rendered parent HTML.');
$this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link original', 'First expected link found.');
$this->assertEqual($list_elements->item(1)->nodeValue, 'Second child link', 'Second expected link found.');
$this->assertIdentical(strpos($parent_html, 'First child link'), FALSE, '"First child link" link not found.');
$this->assertIdentical(strpos($parent_html, 'Third child link'), FALSE, '"Third child link" link not found.');
}
/**
* Tests theme_image().
*/
function testImage() {
// Test that data URIs work with theme_image().
$variables = array();
$variables['uri'] = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
$variables['alt'] = 'Data URI image of a red dot';
$expected = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Data URI image of a red dot" />' . "\n";
$this->assertThemeOutput('image', $variables, $expected);
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\HtmlAttributesTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests attributes inserted in the 'html' and 'body' elements on the page.
*
* @group Theme
*/
class HtmlAttributesTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
/**
* Tests that attributes in the 'html' and 'body' elements can be altered.
*/
function testThemeHtmlAttributes() {
$this->drupalGet('');
$attributes = $this->xpath('/html[@theme_test_html_attribute="theme test html attribute value"]');
$this->assertTrue(count($attributes) == 1, "Attribute set in the 'html' element via hook_preprocess_HOOK() found.");
$attributes = $this->xpath('/html/body[@theme_test_body_attribute="theme test body attribute value"]');
$this->assertTrue(count($attributes) == 1, "Attribute set in the 'body' element via hook_preprocess_HOOK() found.");
}
}

View file

@ -0,0 +1,141 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\ImageTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\KernelTestBase;
/**
* Tests built-in image theme functions.
*
* @group Theme
*/
class ImageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/*
* The images to test with.
*
* @var array
*/
protected $testImages;
protected function setUp() {
parent::setUp();
$this->testImages = array(
'/core/misc/druplicon.png',
'/core/misc/loading.gif',
);
}
/**
* Tests that an image with the sizes attribute is output correctly.
*/
function testThemeImageWithSizes() {
// Test with multipliers.
$sizes = '(max-width: ' . rand(10, 30) . 'em) 100vw, (max-width: ' . rand(30, 50) . 'em) 50vw, 30vw';
$image = array(
'#theme' => 'image',
'#sizes' => $sizes,
'#uri' => reset($this->testImages),
'#width' => rand(0, 1000) . 'px',
'#height' => rand(0, 500) . 'px',
'#alt' => $this->randomMachineName(),
'#title' => $this->randomMachineName(),
);
$this->render($image);
// Make sure sizes is set.
$this->assertRaw($sizes, 'Sizes is set correctly.');
}
/**
* Tests that an image with the src attribute is output correctly.
*/
function testThemeImageWithSrc() {
$image = array(
'#theme' => 'image',
'#uri' => reset($this->testImages),
'#width' => rand(0, 1000) . 'px',
'#height' => rand(0, 500) . 'px',
'#alt' => $this->randomMachineName(),
'#title' => $this->randomMachineName(),
);
$this->render($image);
// Make sure the src attribute has the correct value.
$this->assertRaw(file_create_url($image['#uri']), 'Correct output for an image with the src attribute.');
}
/**
* Tests that an image with the srcset and multipliers is output correctly.
*/
function testThemeImageWithSrcsetMultiplier() {
// Test with multipliers.
$image = array(
'#theme' => 'image',
'#srcset' => array(
array(
'uri' => $this->testImages[0],
'multiplier' => '1x',
),
array(
'uri' => $this->testImages[1],
'multiplier' => '2x',
),
),
'#width' => rand(0, 1000) . 'px',
'#height' => rand(0, 500) . 'px',
'#alt' => $this->randomMachineName(),
'#title' => $this->randomMachineName(),
);
$this->render($image);
// Make sure the srcset attribute has the correct value.
$this->assertRaw(file_create_url($this->testImages[0]) . ' 1x, ' . file_create_url($this->testImages[1]) . ' 2x', 'Correct output for image with srcset attribute and multipliers.');
}
/**
* Tests that an image with the srcset and widths is output correctly.
*/
function testThemeImageWithSrcsetWidth() {
// Test with multipliers.
$widths = array(
rand(0, 500) . 'w',
rand(500, 1000) . 'w',
);
$image = array(
'#theme' => 'image',
'#srcset' => array(
array(
'uri' => $this->testImages[0],
'width' => $widths[0],
),
array(
'uri' => $this->testImages[1],
'width' => $widths[1],
),
),
'#width' => rand(0, 1000) . 'px',
'#height' => rand(0, 500) . 'px',
'#alt' => $this->randomMachineName(),
'#title' => $this->randomMachineName(),
);
$this->render($image);
// Make sure the srcset attribute has the correct value.
$this->assertRaw(file_create_url($this->testImages[0]) . ' ' . $widths[0] . ', ' . file_create_url($this->testImages[1]) . ' ' . $widths[1], 'Correct output for image with srcset attribute and width descriptors.');
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\MessageTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\KernelTestBase;
/**
* Tests built-in message theme functions.
*
* @group Theme
*/
class MessageTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('system');
/**
* Tests setting messages output.
*/
function testMessages() {
// Enable the Classy theme.
\Drupal::service('theme_handler')->install(['classy']);
$this->config('system.theme')->set('default', 'classy')->save();
drupal_set_message('An error occurred', 'error');
drupal_set_message('But then something nice happened');
$messages = array(
'#type' => 'status_messages',
);
$this->render($messages);
$this->assertRaw('messages messages--error');
$this->assertRaw('messages messages--status');
}
}

View file

@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\RegistryTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Core\Theme\Registry;
use Drupal\simpletest\KernelTestBase;
use Drupal\Core\Utility\ThemeRegistry;
/**
* Tests the behavior of the ThemeRegistry class.
*
* @group Theme
*/
class RegistryTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test', 'system');
protected $profile = 'testing';
/**
* Tests the behavior of the theme registry class.
*/
function testRaceCondition() {
// The theme registry is not marked as persistable in case we don't have a
// proper request.
\Drupal::request()->setMethod('GET');
$cid = 'test_theme_registry';
// Directly instantiate the theme registry, this will cause a base cache
// entry to be written in __construct().
$cache = \Drupal::cache();
$lock_backend = \Drupal::lock();
$registry = new ThemeRegistry($cid, $cache, $lock_backend, array('theme_registry'), $this->container->get('module_handler')->isLoaded());
$this->assertTrue(\Drupal::cache()->get($cid), 'Cache entry was created.');
// Trigger a cache miss for an offset.
$this->assertTrue($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry.');
// This will cause the ThemeRegistry class to write an updated version of
// the cache entry when it is destroyed, usually at the end of the request.
// Before that happens, manually delete the cache entry we created earlier
// so that the new entry is written from scratch.
\Drupal::cache()->delete($cid);
// Destroy the class so that it triggers a cache write for the offset.
$registry->destruct();
$this->assertTrue(\Drupal::cache()->get($cid), 'Cache entry was created.');
// Create a new instance of the class. Confirm that both the offset
// requested previously, and one that has not yet been requested are both
// available.
$registry = new ThemeRegistry($cid, $cache, $lock_backend, array('theme_registry'), $this->container->get('module_handler')->isLoaded());
$this->assertTrue($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry');
$this->assertTrue($registry->get('theme_test_template_test_2'), 'Offset was returned correctly from the theme registry');
}
/**
* Tests the theme registry with multiple subthemes.
*/
public function testMultipleSubThemes() {
$theme_handler = \Drupal::service('theme_handler');
$theme_handler->install(['test_basetheme', 'test_subtheme', 'test_subsubtheme']);
$registry_subsub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subsubtheme');
$registry_subsub_theme->setThemeManager(\Drupal::theme());
$registry_sub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subtheme');
$registry_sub_theme->setThemeManager(\Drupal::theme());
$registry_base_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_basetheme');
$registry_base_theme->setThemeManager(\Drupal::theme());
$preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions'];
$this->assertIdentical([
'template_preprocess',
'test_basetheme_preprocess_theme_test_template_test',
'test_subtheme_preprocess_theme_test_template_test',
'test_subsubtheme_preprocess_theme_test_template_test',
], $preprocess_functions);
$preprocess_functions = $registry_sub_theme->get()['theme_test_template_test']['preprocess functions'];
$this->assertIdentical([
'template_preprocess',
'test_basetheme_preprocess_theme_test_template_test',
'test_subtheme_preprocess_theme_test_template_test',
], $preprocess_functions);
$preprocess_functions = $registry_base_theme->get()['theme_test_template_test']['preprocess functions'];
$this->assertIdentical([
'template_preprocess',
'test_basetheme_preprocess_theme_test_template_test',
], $preprocess_functions);
}
/**
* Tests that the theme registry can be altered by themes.
*/
public function testThemeRegistryAlterByTheme() {
/** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
$theme_handler = \Drupal::service('theme_handler');
$theme_handler->install(['test_theme']);
$theme_handler->setDefault('test_theme');
$registry = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_theme');
$registry->setThemeManager(\Drupal::theme());
$this->assertEqual('value', $registry->get()['theme_test_template_test']['variables']['additional']);
}
}

View file

@ -0,0 +1,311 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TableTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\KernelTestBase;
/**
* Tests built-in table theme functions.
*
* @group Theme
*/
class TableTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'router');
\Drupal::service('router.builder')->rebuild();
}
/**
* Tableheader.js provides 'sticky' table headers, and is included by default.
*/
function testThemeTableStickyHeaders() {
$header = array('one', 'two', 'three');
$rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#sticky' => TRUE,
);
$this->render($table);
$this->assertTrue(in_array('core/drupal.tableheader', $table['#attached']['library']), 'tableheader asset library found.');
$this->assertRaw('sticky-enabled');
}
/**
* If $sticky is FALSE, no tableheader.js should be included.
*/
function testThemeTableNoStickyHeaders() {
$header = array('one', 'two', 'three');
$rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
$attributes = array();
$caption = NULL;
$colgroups = array();
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#attributes' => $attributes,
'#caption' => $caption,
'#colgroups' => $colgroups,
'#sticky' => FALSE,
);
$this->render($table);
$this->assertFalse(in_array('core/drupal.tableheader', $table['#attached']['library']), 'tableheader asset library not found.');
$this->assertNoRaw('sticky-enabled');
}
/**
* Tests that the table header is printed correctly even if there are no rows,
* and that the empty text is displayed correctly.
*/
function testThemeTableWithEmptyMessage() {
$header = array(
'Header 1',
array(
'data' => 'Header 2',
'colspan' => 2,
),
);
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => array(),
'#empty' => 'Empty row.',
);
// Enable the Classy theme.
\Drupal::service('theme_handler')->install(['classy']);
$this->config('system.theme')->set('default', 'classy')->save();
$this->render($table);
$this->removeWhiteSpace();
$this->assertRaw('<thead><tr><th>Header 1</th><th colspan="2">Header 2</th></tr>', 'Table header found.');
$this->assertRaw('<tr class="odd"><td colspan="3" class="empty message">Empty row.</td>', 'Colspan on #empty row found.');
}
/**
* Tests that the 'no_striping' option works correctly.
*/
function testThemeTableWithNoStriping() {
$rows = array(
array(
'data' => array(1),
'no_striping' => TRUE,
),
);
$table = array(
'#type' => 'table',
'#rows' => $rows,
);
$this->render($table);
$this->assertNoRaw('class="odd"', 'Odd/even classes were not added because $no_striping = TRUE.');
$this->assertNoRaw('no_striping', 'No invalid no_striping HTML attribute was printed.');
}
/**
* Test that the 'footer' option works correctly.
*/
function testThemeTableFooter() {
$footer = array(
array(
'data' => array(1),
),
array('Foo'),
);
$table = array(
'#type' => 'table',
'#rows' => array(),
'#footer' => $footer,
);
$this->render($table);
$this->removeWhiteSpace();
$this->assertRaw('<tfoot><tr><td>1</td></tr><tr><td>Foo</td></tr></tfoot>', 'Table footer found.');
}
/**
* Tests that the 'header' option in cells works correctly.
*/
function testThemeTableHeaderCellOption() {
$rows = array(
array(
array('data' => 1, 'header' => TRUE),
array('data' => 1, 'header' => FALSE),
array('data' => 1),
),
);
$table = array(
'#type' => 'table',
'#rows' => $rows,
);
$this->render($table);
$this->removeWhiteSpace();
$this->assertRaw('<th>1</th><td>1</td><td>1</td>', 'The th and td tags was printed correctly.');
}
/**
* Tests that the 'responsive-table' class is applied correctly.
*/
public function testThemeTableResponsive() {
$header = array('one', 'two', 'three');
$rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#responsive' => TRUE,
);
$this->render($table);
$this->assertRaw('responsive-enabled', 'The responsive-enabled class was printed correctly.');
}
/**
* Tests that the 'responsive-table' class is not applied without headers.
*/
public function testThemeTableNotResponsiveHeaders() {
$rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
$table = array(
'#type' => 'table',
'#rows' => $rows,
'#responsive' => TRUE,
);
$this->render($table);
$this->assertNoRaw('responsive-enabled', 'The responsive-enabled class is not applied without table headers.');
}
/**
* Tests that 'responsive-table' class only applied when responsive is TRUE.
*/
public function testThemeTableNotResponsiveProperty() {
$header = array('one', 'two', 'three');
$rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#responsive' => FALSE,
);
$this->render($table);
$this->assertNoRaw('responsive-enabled', 'The responsive-enabled class is not applied without the "responsive" property set to TRUE.');
}
/**
* Tests 'priority-medium' and 'priority-low' classes.
*/
public function testThemeTableResponsivePriority() {
$header = array(
// Test associative header indices.
'associative_key' => array('data' => 1, 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)),
// Test non-associative header indices.
array('data' => 2, 'class' => array(RESPONSIVE_PRIORITY_LOW)),
// Test no responsive priorities.
array('data' => 3),
);
$rows = array(array(4, 5, 6));
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#responsive' => TRUE,
);
$this->render($table);
$this->assertRaw('<th class="priority-medium">1</th>', 'Header 1: the priority-medium class was applied correctly.');
$this->assertRaw('<th class="priority-low">2</th>', 'Header 2: the priority-low class was applied correctly.');
$this->assertRaw('<th>3</th>', 'Header 3: no priority classes were applied.');
$this->assertRaw('<td class="priority-medium">4</td>', 'Cell 1: the priority-medium class was applied correctly.');
$this->assertRaw('<td class="priority-low">5</td>', 'Cell 2: the priority-low class was applied correctly.');
$this->assertRaw('<td>6</td>', 'Cell 3: no priority classes were applied.');
}
/**
* Tests header elements with a mix of string and render array values.
*/
public function testThemeTableHeaderRenderArray() {
$header = array(
array (
'data' => array(
'#markup' => 'one',
),
),
'two',
array (
'data' => array(
'#type' => 'html_tag',
'#tag' => 'b',
'#value' => 'three',
),
),
);
$rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#responsive' => FALSE,
);
$this->render($table);
$this->removeWhiteSpace();
$this->assertRaw('<thead><tr><th>one</th><th>two</th><th><b>three</b></th></tr>', 'Table header found.');
}
/**
* Tests row elements with a mix of string and render array values.
*/
public function testThemeTableRowRenderArray() {
$header = array('one', 'two', 'three');
$rows = array(
array(
'1-one',
array(
'data' => '1-two'
),
'1-three',
),
array(
array (
'data' => array(
'#markup' => '2-one',
),
),
'2-two',
array (
'data' => array(
'#type' => 'html_tag',
'#tag' => 'b',
'#value' => '2-three',
),
),
),
);
$table = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#responsive' => FALSE,
);
$this->render($table);
$this->removeWhiteSpace();
$this->assertRaw('<tbody><tr><td>1-one</td><td>1-two</td><td>1-three</td></tr>', 'Table row 1 found.');
$this->assertRaw('<tr><td>2-one</td><td>2-two</td><td><b>2-three</b></td></tr></tbody>', 'Table row 2 found.');
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\ThemeEarlyInitializationTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests that the theme system can be correctly initialized early in the page
* request.
*
* @group Theme
*/
class ThemeEarlyInitializationTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
/**
* Test that the theme system can generate output in a request listener.
*/
function testRequestListener() {
$this->drupalGet('theme-test/request-listener');
// Verify that themed output generated in the request listener appears.
$this->assertRaw('Themed output generated in a KernelEvents::REQUEST listener');
// Verify that the default theme's CSS still appears even though the theme
// system was initialized early.
$this->assertRaw('classy/css/layout.css');
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\ThemeInfoTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests processing of theme .info.yml properties.
*
* @group Theme
*/
class ThemeInfoTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
/**
* The theme handler used in this test for enabling themes.
*
* @var \Drupal\Core\Extension\ThemeHandler
*/
protected $themeHandler;
/**
* The theme manager used in this test.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* The state service used in this test.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->themeHandler = $this->container->get('theme_handler');
$this->themeManager = $this->container->get('theme.manager');
$this->state = $this->container->get('state');
}
/**
* Tests stylesheets-remove.
*/
function testStylesheets() {
$this->themeHandler->install(array('test_basetheme', 'test_subtheme'));
$this->config('system.theme')
->set('default', 'test_subtheme')
->save();
$base = drupal_get_path('theme', 'test_basetheme');
$sub = drupal_get_path('theme', 'test_subtheme') . '/css';
// All removals are expected to be based on a file's path and name and
// should work nevertheless.
$this->drupalGet('theme-test/info/stylesheets');
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$base/base-add.css')]")), "$base/base-add.css found");
$this->assertIdentical(0, count($this->xpath("//link[contains(@href, 'base-remove.css')]")), "base-remove.css not found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$sub/sub-add.css')]")), "$sub/sub-add.css found");
$this->assertIdentical(0, count($this->xpath("//link[contains(@href, 'sub-remove.css')]")), "sub-remove.css not found");
$this->assertIdentical(0, count($this->xpath("//link[contains(@href, 'base-add.sub-remove.css')]")), "base-add.sub-remove.css not found");
// Verify that CSS files with the same name are loaded from both the base theme and subtheme.
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$base/samename.css')]")), "$base/samename.css found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$sub/samename.css')]")), "$sub/samename.css found");
}
/**
* Tests that changes to the info file are picked up.
*/
public function testChanges() {
$this->themeHandler->install(array('test_theme'));
$this->themeHandler->setDefault('test_theme');
$this->themeManager->resetActiveTheme();
$active_theme = $this->themeManager->getActiveTheme();
// Make sure we are not testing the wrong theme.
$this->assertEqual('test_theme', $active_theme->getName());
$this->assertEqual(['classy/base', 'test_theme/global-styling'], $active_theme->getLibraries());
// @see theme_test_system_info_alter()
$this->state->set('theme_test.modify_info_files', TRUE);
drupal_flush_all_caches();
$active_theme = $this->themeManager->getActiveTheme();
$this->assertEqual(['classy/base', 'test_theme/global-styling', 'core/backbone'], $active_theme->getLibraries());
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\ThemeSettingsTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\simpletest\KernelTestBase;
/**
* Tests theme settings functionality.
*
* @group Theme
*/
class ThemeSettingsTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* List of discovered themes.
*
* @var array
*/
protected $availableThemes;
protected function setUp() {
parent::setUp();
// Theme settings rely on System module's system.theme.global configuration.
$this->installConfig(array('system'));
if (!isset($this->availableThemes)) {
$discovery = new ExtensionDiscovery(\Drupal::root());
$this->availableThemes = $discovery->scan('theme');
}
}
/**
* Tests that $theme.settings are imported and used as default theme settings.
*/
function testDefaultConfig() {
$name = 'test_basetheme';
$path = $this->availableThemes[$name]->getPath();
$this->assertTrue(file_exists("$path/" . InstallStorage::CONFIG_INSTALL_DIRECTORY . "/$name.settings.yml"));
$this->container->get('theme_handler')->install(array($name));
$this->assertIdentical(theme_get_setting('base', $name), 'only');
}
/**
* Tests that the $theme.settings default config file is optional.
*/
function testNoDefaultConfig() {
$name = 'stark';
$path = $this->availableThemes[$name]->getPath();
$this->assertFalse(file_exists("$path/" . InstallStorage::CONFIG_INSTALL_DIRECTORY . "/$name.settings.yml"));
$this->container->get('theme_handler')->install(array($name));
$this->assertNotNull(theme_get_setting('features.favicon', $name));
}
}

View file

@ -0,0 +1,190 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\ThemeSuggestionsAlterTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Component\Utility\Xss;
use Drupal\simpletest\WebTestBase;
/**
* Tests theme suggestion alter hooks.
*
* @group Theme
*/
class ThemeSuggestionsAlterTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme'));
}
/**
* Tests that hooks to provide theme suggestions work.
*/
function testTemplateSuggestions() {
$this->drupalGet('theme-test/suggestion-provided');
$this->assertText('Template for testing suggestions provided by the module declaring the theme hook.');
// Install test_theme, it contains a template suggested by theme_test.module
// in theme_test_theme_suggestions_theme_test_suggestion_provided().
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('theme-test/suggestion-provided');
$this->assertText('Template overridden based on suggestion provided by the module declaring the theme hook.');
}
/**
* Tests hook_theme_suggestions_alter().
*/
function testGeneralSuggestionsAlter() {
$this->drupalGet('theme-test/general-suggestion-alter');
$this->assertText('Original template for testing hook_theme_suggestions_alter().');
// Install test_theme and test that themes can alter template suggestions.
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('theme-test/general-suggestion-alter');
$this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_alter().');
// Enable the theme_suggestions_test module to test modules implementing
// suggestions alter hooks.
\Drupal::service('module_installer')->install(array('theme_suggestions_test'));
$this->resetAll();
$this->drupalGet('theme-test/general-suggestion-alter');
$this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_alter().');
}
/**
* Tests that theme suggestion alter hooks work for templates.
*/
function testTemplateSuggestionsAlter() {
$this->drupalGet('theme-test/suggestion-alter');
$this->assertText('Original template for testing hook_theme_suggestions_HOOK_alter().');
// Install test_theme and test that themes can alter template suggestions.
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('theme-test/suggestion-alter');
$this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_HOOK_alter().');
// Enable the theme_suggestions_test module to test modules implementing
// suggestions alter hooks.
\Drupal::service('module_installer')->install(array('theme_suggestions_test'));
$this->resetAll();
$this->drupalGet('theme-test/suggestion-alter');
$this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK_alter().');
}
/**
* Tests that theme suggestion alter hooks work for specific theme calls.
*/
function testSpecificSuggestionsAlter() {
// Test that the default template is rendered.
$this->drupalGet('theme-test/specific-suggestion-alter');
$this->assertText('Template for testing specific theme calls.');
$this->config('system.theme')
->set('default', 'test_theme')
->save();
// Test a specific theme call similar to '#theme' => 'node__article'.
$this->drupalGet('theme-test/specific-suggestion-alter');
$this->assertText('Template matching the specific theme call.');
$this->assertText('theme_test_specific_suggestions__variant', 'Specific theme call is added to the suggestions array.');
// Ensure that the base hook is used to determine the suggestion alter hook.
\Drupal::service('module_installer')->install(array('theme_suggestions_test'));
$this->resetAll();
$this->drupalGet('theme-test/specific-suggestion-alter');
$this->assertText('Template overridden based on suggestion alter hook determined by the base hook.');
$this->assertTrue(strpos($this->getRawContent(), 'theme_test_specific_suggestions__variant') < strpos($this->getRawContent(), 'theme_test_specific_suggestions__variant__foo'), 'Specific theme call is added to the suggestions array before the suggestions alter hook.');
}
/**
* Tests that theme suggestion alter hooks work for theme functions.
*/
function testThemeFunctionSuggestionsAlter() {
$this->drupalGet('theme-test/function-suggestion-alter');
$this->assertText('Original theme function.');
// Install test_theme and test that themes can alter theme suggestions.
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('theme-test/function-suggestion-alter');
$this->assertText('Theme function overridden based on new theme suggestion provided by the test_theme theme.');
// Enable the theme_suggestions_test module to test modules implementing
// suggestions alter hooks.
\Drupal::service('module_installer')->install(array('theme_suggestions_test'));
$this->resetAll();
$this->drupalGet('theme-test/function-suggestion-alter');
$this->assertText('Theme function overridden based on new theme suggestion provided by a module.');
}
/**
* Tests that theme suggestion alter hooks work with theme hook includes.
*/
public function testSuggestionsAlterInclude() {
// Check the original theme output.
$this->drupalGet('theme-test/suggestion-alter-include');
$this->assertText('Original function before altering theme suggestions.');
// Enable theme_suggestions_test module and make two requests to make sure
// the include file is always loaded. The file will always be included for
// the first request because the theme registry is being rebuilt.
\Drupal::service('module_installer')->install(array('theme_suggestions_test'));
$this->resetAll();
$this->drupalGet('theme-test/suggestion-alter-include');
$this->assertText('Function suggested via suggestion alter hook found in include file.', 'Include file loaded for initial request.');
$this->drupalGet('theme-test/suggestion-alter-include');
$this->assertText('Function suggested via suggestion alter hook found in include file.', 'Include file loaded for second request.');
}
/**
* Tests execution order of theme suggestion alter hooks.
*
* hook_theme_suggestions_alter() should fire before
* hook_theme_suggestions_HOOK_alter() within an extension (module or theme).
*/
function testExecutionOrder() {
// Install our test theme and module.
$this->config('system.theme')
->set('default', 'test_theme')
->save();
\Drupal::service('module_installer')->install(array('theme_suggestions_test'));
$this->resetAll();
// Send two requests so that we get all the messages we've set via
// drupal_set_message().
$this->drupalGet('theme-test/suggestion-alter');
// Ensure that the order is first by extension, then for a given extension,
// the hook-specific one after the generic one.
$expected = array(
'theme_suggestions_test_theme_suggestions_alter() executed.',
'theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter() executed.',
'theme_test_theme_suggestions_alter() executed.',
'theme_test_theme_suggestions_theme_test_suggestions_alter() executed.',
'test_theme_theme_suggestions_alter() executed.',
'test_theme_theme_suggestions_theme_test_suggestions_alter() executed.',
);
$content = preg_replace('/\s+/', ' ', Xss::filter($this->content, array()));
$this->assert(strpos($content, implode(' ', $expected)) !== FALSE, 'Suggestion alter hooks executed in the expected order.');
}
}

View file

@ -0,0 +1,290 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\ThemeTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Component\Serialization\Json;
use Drupal\simpletest\WebTestBase;
use Drupal\test_theme\ThemeClass;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Tests low-level theme functions.
*
* @group Theme
*/
class ThemeTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test', 'node');
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme'));
}
/**
* Test attribute merging.
*
* Render arrays that use a render element and templates (and hence call
* template_preprocess()) must ensure the attributes at different occasions
* are all merged correctly:
* - $variables['attributes'] as passed in to _theme()
* - the render element's #attributes
* - any attributes set in the template's preprocessing function
*/
function testAttributeMerging() {
$theme_test_render_element = array(
'elements' => array(
'#attributes' => array('data-foo' => 'bar'),
),
'attributes' => array(
'id' => 'bazinga',
),
);
$this->assertThemeOutput('theme_test_render_element', $theme_test_render_element, '<div id="bazinga" data-foo="bar" data-variables-are-preprocessed></div>' . "\n");
}
/**
* Test that _theme() returns expected data types.
*/
function testThemeDataTypes() {
// theme_test_false is an implemented theme hook so \Drupal::theme() service should
// return a string, even though the theme function itself can return anything.
$foos = array('null' => NULL, 'false' => FALSE, 'integer' => 1, 'string' => 'foo');
foreach ($foos as $type => $example) {
$output = \Drupal::theme()->render('theme_test_foo', array('foo' => $example));
$this->assertTrue(is_string($output), format_string('\Drupal::theme() returns a string for data type !type.', array('!type' => $type)));
}
// suggestionnotimplemented is not an implemented theme hook so \Drupal::theme() service
// should return FALSE instead of a string.
$output = \Drupal::theme()->render(array('suggestionnotimplemented'), array());
$this->assertIdentical($output, FALSE, '\Drupal::theme() returns FALSE when a hook suggestion is not implemented.');
}
/**
* Test function theme_get_suggestions() for SA-CORE-2009-003.
*/
function testThemeSuggestions() {
// Set the front page as something random otherwise the CLI
// test runner fails.
$this->config('system.site')->set('page.front', '/nobody-home')->save();
$args = array('node', '1', 'edit');
$suggestions = theme_get_suggestions($args, 'page');
$this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1', 'page__node__edit'), 'Found expected node edit page suggestions');
// Check attack vectors.
$args = array('node', '\\1');
$suggestions = theme_get_suggestions($args, 'page');
$this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), 'Removed invalid \\ from suggestions');
$args = array('node', '1/');
$suggestions = theme_get_suggestions($args, 'page');
$this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), 'Removed invalid / from suggestions');
$args = array('node', "1\0");
$suggestions = theme_get_suggestions($args, 'page');
$this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), 'Removed invalid \\0 from suggestions');
// Define path with hyphens to be used to generate suggestions.
$args = array('node', '1', 'hyphen-path');
$result = array('page__node', 'page__node__%', 'page__node__1', 'page__node__hyphen_path');
$suggestions = theme_get_suggestions($args, 'page');
$this->assertEqual($suggestions, $result, 'Found expected page suggestions for paths containing hyphens.');
}
/**
* Ensures preprocess functions run even for suggestion implementations.
*
* The theme hook used by this test has its base preprocess function in a
* separate file, so this test also ensures that that file is correctly loaded
* when needed.
*/
function testPreprocessForSuggestions() {
// Test with both an unprimed and primed theme registry.
drupal_theme_rebuild();
for ($i = 0; $i < 2; $i++) {
$this->drupalGet('theme-test/suggestion');
$this->assertText('Theme hook implementor=test_theme_theme_test__suggestion(). Foo=template_preprocess_theme_test', 'Theme hook suggestion ran with data available from a preprocess function for the base hook.');
}
}
/**
* Tests the priority of some theme negotiators.
*/
public function testNegotiatorPriorities() {
$this->drupalGet('theme-test/priority');
// Ensure that the custom theme negotiator was not able to set the theme.
$this->assertNoText('Theme hook implementor=test_theme_theme_test__suggestion(). Foo=template_preprocess_theme_test', 'Theme hook suggestion ran with data available from a preprocess function for the base hook.');
}
/**
* Ensures that non-HTML requests never initialize themes.
*/
public function testThemeOnNonHtmlRequest() {
$this->drupalGet('theme-test/non-html');
$json = Json::decode($this->getRawContent());
$this->assertFalse($json['theme_initialized']);
}
/**
* Ensure page-front template suggestion is added when on front page.
*/
function testFrontPageThemeSuggestion() {
// Set the current route to user.login because theme_get_suggestions() will
// query it to see if we are on the front page.
$request = Request::create('/user/login');
$request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'user.login');
$request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/user/login'));
\Drupal::requestStack()->push($request);
$this->config('system.site')->set('page.front', '/user/login')->save();
$suggestions = theme_get_suggestions(array('user', 'login'), 'page');
// Set it back to not annoy the batch runner.
\Drupal::requestStack()->pop();
$this->assertTrue(in_array('page__front', $suggestions), 'Front page template was suggested.');
}
/**
* Ensures a theme's .info.yml file is able to override a module CSS file from being added to the page.
*
* @see test_theme.info.yml
*/
function testCSSOverride() {
// Reuse the same page as in testPreprocessForSuggestions(). We're testing
// what is output to the HTML HEAD based on what is in a theme's .info.yml
// file, so it doesn't matter what page we get, as long as it is themed with
// the test theme. First we test with CSS aggregation disabled.
$config = $this->config('system.performance');
$config->set('css.preprocess', 0);
$config->save();
$this->drupalGet('theme-test/suggestion');
$this->assertNoText('system.module.css', 'The theme\'s .info.yml file is able to override a module CSS file from being added to the page.');
// Also test with aggregation enabled, simply ensuring no PHP errors are
// triggered during drupal_build_css_cache() when a source file doesn't
// exist. Then allow remaining tests to continue with aggregation disabled
// by default.
$config->set('css.preprocess', 1);
$config->save();
$this->drupalGet('theme-test/suggestion');
$config->set('css.preprocess', 0);
$config->save();
}
/**
* Ensures a themes template is overridable based on the 'template' filename.
*/
function testTemplateOverride() {
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('theme-test/template-test');
$this->assertText('Success: Template overridden.', 'Template overridden by defined \'template\' filename.');
}
/**
* Ensures a theme template can override a theme function.
*/
function testFunctionOverride() {
$this->drupalGet('theme-test/function-template-overridden');
$this->assertText('Success: Template overrides theme function.', 'Theme function overridden by test_theme template.');
}
/**
* Test the listInfo() function.
*/
function testListThemes() {
$theme_handler = $this->container->get('theme_handler');
$theme_handler->install(array('test_subtheme'));
$themes = $theme_handler->listInfo();
// Check if ThemeHandlerInterface::listInfo() retrieves enabled themes.
$this->assertIdentical(1, $themes['test_theme']->status, 'Installed theme detected');
// Check if ThemeHandlerInterface::listInfo() returns disabled themes.
// Check for base theme and subtheme lists.
$base_theme_list = array('test_basetheme' => 'Theme test base theme');
$sub_theme_list = array('test_subsubtheme' => 'Theme test subsubtheme', 'test_subtheme' => 'Theme test subtheme');
$this->assertIdentical($themes['test_basetheme']->sub_themes, $sub_theme_list, 'Base theme\'s object includes list of subthemes.');
$this->assertIdentical($themes['test_subtheme']->base_themes, $base_theme_list, 'Subtheme\'s object includes list of base themes.');
// Check for theme engine in subtheme.
$this->assertIdentical($themes['test_subtheme']->engine, 'twig', 'Subtheme\'s object includes the theme engine.');
// Check for theme engine prefix.
$this->assertIdentical($themes['test_basetheme']->prefix, 'twig', 'Base theme\'s object includes the theme engine prefix.');
$this->assertIdentical($themes['test_subtheme']->prefix, 'twig', 'Subtheme\'s object includes the theme engine prefix.');
}
/**
* Tests child element rendering for 'render element' theme hooks.
*/
function testDrupalRenderChildren() {
$element = array(
'#theme' => 'theme_test_render_element_children',
'child' => array(
'#markup' => 'Foo',
),
);
$this->assertThemeOutput('theme_test_render_element_children', $element, 'Foo', 'drupal_render() avoids #theme recursion loop when rendering a render element.');
$element = array(
'#theme_wrappers' => array('theme_test_render_element_children'),
'child' => array(
'#markup' => 'Foo',
),
);
$this->assertThemeOutput('theme_test_render_element_children', $element, 'Foo', 'drupal_render() avoids #theme_wrappers recursion loop when rendering a render element.');
}
/**
* Tests theme can provide classes.
*/
function testClassLoading() {
new ThemeClass();
}
/**
* Tests drupal_find_theme_templates().
*/
public function testFindThemeTemplates() {
$registry = $this->container->get('theme.registry')->get();
$templates = drupal_find_theme_templates($registry, '.html.twig', drupal_get_path('theme', 'test_theme'));
$this->assertEqual($templates['node__1']['template'], 'node--1', 'Template node--1.tpl.twig was found in test_theme.');
}
/**
* Tests that the page variable is not prematurely flattened.
*
* Some modules check the page array in template_preprocess_html(), so we
* ensure that it has not been rendered prematurely.
*/
function testPreprocessHtml() {
$this->drupalGet('');
$attributes = $this->xpath('/html/body[@theme_test_page_variable="Page variable is an array."]');
$this->assertTrue(count($attributes) == 1, 'In template_preprocess_html(), the page variable is still an array (not rendered yet).');
$this->assertText('theme test page bottom markup', 'Modules are able to set the page bottom region.');
}
/**
* Tests that region attributes can be manipulated via preprocess functions.
*/
function testRegionClass() {
\Drupal::service('module_installer')->install(array('block', 'theme_region_test'));
// Place a block.
$this->drupalPlaceBlock('system_main_block');
$this->drupalGet('');
$elements = $this->cssSelect(".region-sidebar-first.new_class");
$this->assertEqual(count($elements), 1, 'New class found.');
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigDebugMarkupTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests for Twig debug markup.
*
* @group Theme
*/
class TwigDebugMarkupTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test', 'node');
/**
* Tests debug markup added to Twig template output.
*/
function testTwigDebugMarkup() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$extension = twig_extension();
\Drupal::service('theme_handler')->install(array('test_theme'));
$this->config('system.theme')->set('default', 'test_theme')->save();
$this->drupalCreateContentType(array('type' => 'page'));
// Enable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
$cache = $this->container->get('theme.registry')->get();
// Create array of Twig templates.
$templates = drupal_find_theme_templates($cache, $extension, drupal_get_path('theme', 'test_theme'));
$templates += drupal_find_theme_templates($cache, $extension, drupal_get_path('module', 'node'));
// Create a node and test different features of the debug markup.
$node = $this->drupalCreateNode();
$build = node_view($node);
$output = $renderer->renderRoot($build);
$this->assertTrue(strpos($output, '<!-- THEME DEBUG -->') !== FALSE, 'Twig debug markup found in theme output when debug is enabled.');
$this->setRawContent($output);
$this->assertTrue(strpos($output, "THEME HOOK: 'node'") !== FALSE, 'Theme call information found.');
$this->assertTrue(strpos($output, '* node--1--full' . $extension . PHP_EOL . ' x node--1' . $extension . PHP_EOL . ' * node--page--full' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node--full' . $extension . PHP_EOL . ' * node' . $extension) !== FALSE, 'Suggested template files found in order and node ID specific template shown as current template.');
$this->assertEscaped('node--<script type="text/javascript">alert(\'yo\');</script>');
$template_filename = $templates['node__1']['path'] . '/' . $templates['node__1']['template'] . $extension;
$this->assertTrue(strpos($output, "BEGIN OUTPUT from '$template_filename'") !== FALSE, 'Full path to current template file found.');
// Create another node and make sure the template suggestions shown in the
// debug markup are correct.
$node2 = $this->drupalCreateNode();
$build = node_view($node2);
$output = $renderer->renderRoot($build);
$this->assertTrue(strpos($output, '* node--2--full' . $extension . PHP_EOL . ' * node--2' . $extension . PHP_EOL . ' * node--page--full' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node--full' . $extension . PHP_EOL . ' x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.');
// Create another node and make sure the template suggestions shown in the
// debug markup are correct.
$node3 = $this->drupalCreateNode();
$build = array('#theme' => 'node__foo__bar');
$build += node_view($node3);
$output = $renderer->renderRoot($build);
$this->assertTrue(strpos($output, "THEME HOOK: 'node__foo__bar'") !== FALSE, 'Theme call information found.');
$this->assertTrue(strpos($output, '* node--foo--bar' . $extension . PHP_EOL . ' * node--foo' . $extension . PHP_EOL . ' * node--&lt;script type=&quot;text/javascript&quot;&gt;alert(&#039;yo&#039;);&lt;/script&gt;' . $extension . PHP_EOL . ' * node--3--full' . $extension . PHP_EOL . ' * node--3' . $extension . PHP_EOL . ' * node--page--full' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node--full' . $extension . PHP_EOL . ' x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.');
// Disable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = FALSE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
$build = node_view($node);
$output = $renderer->renderRoot($build);
$this->assertFalse(strpos($output, '<!-- THEME DEBUG -->') !== FALSE, 'Twig debug markup not found in theme output when debug is disabled.');
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigEnvironmentTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Site\Settings;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the twig environment.
*
* @see \Drupal\Core\Template\TwigEnvironment
* @group Twig
*/
class TwigEnvironmentTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* Tests inline templates.
*/
public function testInlineTemplate() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
/** @var \Drupal\Core\Template\TwigEnvironment $environment */
$environment = \Drupal::service('twig');
$this->assertEqual($environment->renderInline('test-no-context'), 'test-no-context');
$this->assertEqual($environment->renderInline('test-with-context {{ llama }}', array('llama' => 'muuh')), 'test-with-context muuh');
$element = array();
$unsafe_string = '<script>alert(\'Danger! High voltage!\');</script>';
$element['test'] = array(
'#type' => 'inline_template',
'#template' => 'test-with-context {{ unsafe_content }}',
'#context' => array('unsafe_content' => $unsafe_string),
);
$this->assertEqual($renderer->renderRoot($element), 'test-with-context ' . SafeMarkup::checkPlain($unsafe_string));
// Enable twig_auto_reload and twig_debug.
$settings = Settings::getAll();
$settings['twig_debug'] = TRUE;
$settings['twig_auto_reload'] = TRUE;
new Settings($settings);
$this->container = $this->kernel->rebuildContainer();
\Drupal::setContainer($this->container);
$element = array();
$element['test'] = array(
'#type' => 'inline_template',
'#template' => 'test-with-context {{ llama }}',
'#context' => array('llama' => 'muuh'),
);
$element_copy = $element;
// Render it twice so that twig caching is triggered.
$this->assertEqual($renderer->renderRoot($element), 'test-with-context muuh');
$this->assertEqual($renderer->renderRoot($element_copy), 'test-with-context muuh');
}
/**
* Tests that exceptions are thrown when a template is not found.
*/
public function testTemplateNotFoundException() {
/** @var \Drupal\Core\Template\TwigEnvironment $environment */
$environment = \Drupal::service('twig');
try {
$environment->loadTemplate('this-template-does-not-exist.html.twig')->render(array());
$this->fail('Did not throw an exception as expected.');
}
catch (\Twig_Error_Loader $e) {
$this->assertTrue(strpos($e->getMessage(), 'Template "this-template-does-not-exist.html.twig" is not defined') === 0);
}
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigExtensionTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests Twig extensions.
*
* @group Theme
*/
class TwigExtensionTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test', 'twig_extension_test');
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme'));
}
/**
* Tests that the provided Twig extension loads the service appropriately.
*/
function testTwigExtensionLoaded() {
$twigService = \Drupal::service('twig');
$ext = $twigService->getExtension('twig_extension_test.test_extension');
$this->assertEqual(get_class($ext), 'Drupal\twig_extension_test\TwigExtension\TestExtension', 'TestExtension loaded successfully.');
}
/**
* Tests that the Twig extension's filter produces expected output.
*/
function testTwigExtensionFilter() {
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('twig-extension-test/filter');
$this->assertText('Every plant is not a mineral.', 'Success: String filtered.');
}
/**
* Tests that the Twig extension's function produces expected output.
*/
function testTwigExtensionFunction() {
$this->config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('twig-extension-test/function');
$this->assertText('THE QUICK BROWN BOX JUMPS OVER THE LAZY DOG 123.', 'Success: Text converted to uppercase.');
$this->assertText('the quick brown box jumps over the lazy dog 123.', 'Success: Text converted to lowercase.');
$this->assertNoText('The Quick Brown Fox Jumps Over The Lazy Dog 123.', 'Success: No text left behind.');
}
/**
* Tests output of integer and double 0 values of TwigExtension::escapeFilter().
*
* @see https://www.drupal.org/node/2417733
*/
public function testsRenderEscapedZeroValue() {
/** @var \Drupal\Core\Template\TwigExtension $extension */
$extension = \Drupal::service('twig.extension');
/** @var \Drupal\Core\Template\TwigEnvironment $twig */
$twig = \Drupal::service('twig');
$this->assertIdentical($extension->escapeFilter($twig, 0), 0, 'TwigExtension::escapeFilter() returns zero correctly when provided as an integer.');
$this->assertIdentical($extension->escapeFilter($twig, 0.0), 0, 'TwigExtension::escapeFilter() returns zero correctly when provided as a double.');
}
/**
* Tests output of integer and double 0 values of TwigExtension->renderVar().
*
* @see https://www.drupal.org/node/2417733
*/
public function testsRenderZeroValue() {
/** @var \Drupal\Core\Template\TwigExtension $extension */
$extension = \Drupal::service('twig.extension');
$this->assertIdentical($extension->renderVar(0), 0, 'TwigExtension::renderVar() renders zero correctly when provided as an integer.');
$this->assertIdentical($extension->renderVar(0.0), 0, 'TwigExtension::renderVar() renders zero correctly when provided as a double.');
}
}

View file

@ -0,0 +1,129 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigFilterTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Template\Attribute;
/**
* Tests Drupal's Twig filters.
*
* @group Theme
*/
class TwigFilterTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('twig_theme_test');
/**
* Test Twig "without" filter.
*/
public function testTwigWithoutFilter() {
$filter_test = array(
'#theme' => 'twig_theme_test_filter',
'#quote' => array(
'content' => array('#markup' => 'You can only find truth with logic if you have already found truth without it.'),
'author' => array('#markup' => 'Gilbert Keith Chesterton'),
'date' => array('#markup' => '1874-1936'),
),
'#attributes' => array(
'id' => 'quotes',
'checked' => TRUE,
'class' => array('red', 'green', 'blue'),
),
);
$rendered = \Drupal::service('renderer')->renderRoot($filter_test);
$this->setRawContent($rendered);
$elements = array(
array(
'expected' => '<div><strong>No author:</strong> You can only find truth with logic if you have already found truth without it.1874-1936.</div>',
'message' => '"No author" was successfully rendered.',
),
array(
'expected' => '<div><strong>Complete quote after without:</strong> You can only find truth with logic if you have already found truth without it.Gilbert Keith Chesterton1874-1936.</div>',
'message' => '"Complete quote after without" was successfully rendered.',
),
array(
'expected' => '<div><strong>Only author:</strong> Gilbert Keith Chesterton.</div>',
'message' => '"Only author:" was successfully rendered.',
),
array(
'expected' => '<div><strong>No author or date:</strong> You can only find truth with logic if you have already found truth without it..</div>',
'message' => '"No author or date" was successfully rendered.',
),
array(
'expected' => '<div><strong>Only date:</strong> 1874-1936.</div>',
'message' => '"Only date" was successfully rendered.',
),
array(
'expected' => '<div><strong>Complete quote again for good measure:</strong> You can only find truth with logic if you have already found truth without it.Gilbert Keith Chesterton1874-1936.</div>',
'message' => '"Complete quote again for good measure" was successfully rendered.',
),
array(
'expected' => '<div><strong>Marked-up:</strong>
<blockquote>
<p>You can only find truth with logic if you have already found truth without it.</p>
<footer>
&ndash; <cite><a href="#">Gilbert Keith Chesterton</a> <em>(1874-1936)</em></cite>
</footer>
</blockquote>',
'message' => '"Marked-up quote" was successfully rendered.',
),
array(
'expected' => '<div><span id="quotes" checked class="red green blue">All attributes:</span></div>',
'message' => 'All attributes printed.',
),
array(
'expected' => '<div><span class="red green blue" id="quotes" checked>Class attributes in front, remainder at the back:</span></div>',
'message' => 'Class attributes printed in the front, the rest in the back.',
),
array(
'expected' => '<div><span id="quotes" checked data-class="red green blue">Class attributes in back, remainder at the front:</span></div>',
'message' => 'Class attributes printed in the back, the rest in the front.',
),
array(
'expected' => '<div><span class="red green blue">Class attributes only:</span></div>',
'message' => 'Class attributes only printed.',
),
array(
'expected' => '<div><span checked id="quotes" class="red green blue">Without boolean attribute.</span></div>',
'message' => 'Boolean attribute printed in the front.',
),
array(
'expected' => '<div><span data-id="quotes" checked class="red green blue">Without string attribute.</span></div>',
'message' => 'Without string attribute in the front.',
),
array(
'expected' => '<div><span checked>Without id and class attributes.</span></div>',
'message' => 'Attributes printed without id and class attributes.',
),
array(
'expected' => '<div><span id="quotes" checked class="red green blue">All attributes again.</span></div>',
'message' => 'All attributes printed again.',
),
array(
'expected' => '<div id="quotes-here"><span class="gray-like-a-bunny bem__ized--top-feature" id="quotes-here">ID and class. Having the same ID twice is not valid markup but we want to make sure the filter doesn\'t use \Drupal\Component\Utility\Html::getUniqueId().</span></div>',
'message' => 'Class and ID filtered.',
),
array(
'expected' => '<div><strong>Rendered author string length:</strong> 24.</div>',
'message' => 'Render filter string\'s length.',
),
);
foreach ($elements as $element) {
$this->assertRaw($element['expected'], $element['message']);
}
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigLoaderTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests adding Twig loaders.
*
* @group Theme
*/
class TwigLoaderTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['twig_loader_test'];
/**
* Tests adding an additional twig loader to the loader chain.
*/
public function testTwigLoaderAddition() {
$environment = \Drupal::service('twig');
$template = $environment->loadTemplate('kittens');
$this->assertEqual($template->render(array()), 'kittens', 'Passing "kittens" to the custom Twig loader returns "kittens".');
$template = $environment->loadTemplate('meow');
$this->assertEqual($template->render(array()), 'cats', 'Passing something other than "kittens" to the custom Twig loader returns "cats".');
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigNamespaceTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests Twig namespaces.
*
* @group Theme
*/
class TwigNamespaceTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('twig_theme_test', 'twig_namespace_a', 'twig_namespace_b', 'node');
/**
* @var \Drupal\Core\Template\TwigEnvironment
*/
protected $twig;
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme', 'bartik'));
$this->twig = \Drupal::service('twig');
}
/**
* Checks to see if a value is a twig template.
*/
public function assertTwigTemplate($value, $message = '', $group = 'Other') {
$this->assertTrue($value instanceof \Twig_Template, $message, $group);
}
/**
* Tests template discovery using namespaces.
*/
public function testTemplateDiscovery() {
// Tests resolving namespaced templates in modules.
$this->assertTwigTemplate($this->twig->resolveTemplate('@node/node.html.twig'), 'Found node.html.twig in node module.');
// Tests resolving namespaced templates in themes.
$this->assertTwigTemplate($this->twig->resolveTemplate('@bartik/page.html.twig'), 'Found page.html.twig in Bartik theme.');
}
/**
* Tests template extension and includes using namespaces.
*/
public function testTwigNamespaces() {
// Test twig @extends and @include in template files.
$test = array('#theme' => 'twig_namespace_test');
$this->setRawContent(\Drupal::service('renderer')->renderRoot($test));
$this->assertText('This line is from twig_namespace_a/templates/test.html.twig');
$this->assertText('This line is from twig_namespace_b/templates/test.html.twig');
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigRawTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests Twig 'raw' filter.
*
* @group Theme
*/
class TwigRawTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('twig_theme_test');
/**
* Tests the raw filter inside an autoescape tag.
*/
public function testAutoescapeRaw() {
$test = array(
'#theme' => 'twig_raw_test',
'#script' => '<script>alert("This alert is real because I will put it through the raw filter!");</script>',
);
$rendered = \Drupal::service('renderer')->renderRoot($test);
$this->setRawContent($rendered);
$this->assertRaw('<script>alert("This alert is real because I will put it through the raw filter!");</script>');
}
/**
* Tests autoescaping of unsafe content.
*
* This is one of the most important tests in Drupal itself in terms of
* security.
*/
public function testAutoescape() {
$script = '<script>alert("This alert is unreal!");</script>';
$build = [
'#theme' => 'twig_autoescape_test',
'#script' => $script,
];
$rendered = \Drupal::service('renderer')->renderRoot($build);
$this->setRawContent($rendered);
$this->assertEscaped($script);
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigRegistryLoaderTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests Twig registry loader.
*
* @group Theme
*/
class TwigRegistryLoaderTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('twig_theme_test', 'block');
/**
* @var \Drupal\Core\Template\TwigEnvironment
*/
protected $twig;
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme_twig_registry_loader', 'test_theme_twig_registry_loader_theme', 'test_theme_twig_registry_loader_subtheme'));
$this->twig = \Drupal::service('twig');
}
/**
* Checks to see if a value is a Twig template.
*/
public function assertTwigTemplate($value, $message = '', $group = 'Other') {
$this->assertTrue($value instanceof \Twig_Template, $message, $group);
}
/**
* Tests template discovery using the Drupal theme registry.
*/
public function testTemplateDiscovery() {
$this->assertTwigTemplate($this->twig->resolveTemplate('block.html.twig'), 'Found block.html.twig in block module.');
}
/**
* Tests template extension and includes using the Drupal theme registry.
*/
public function testTwigNamespaces() {
// Test the module-provided extend and insert templates.
$this->drupalGet('twig-theme-test/registry-loader');
$this->assertText('This line is from twig_theme_test/templates/twig-registry-loader-test-extend.html.twig');
$this->assertText('This line is from twig_theme_test/templates/twig-registry-loader-test-include.html.twig');
// Enable a theme that overrides the extend and insert templates to ensure
// they are picked up by the registry loader.
$this->config('system.theme')
->set('default', 'test_theme_twig_registry_loader')
->save();
$this->drupalGet('twig-theme-test/registry-loader');
$this->assertText('This line is from test_theme_twig_registry_loader/templates/twig-registry-loader-test-extend.html.twig');
$this->assertText('This line is from test_theme_twig_registry_loader/templates/twig-registry-loader-test-include.html.twig');
// Enable overriding theme that overrides the extend and insert templates
// from the base theme.
$this->config('system.theme')
->set('default', 'test_theme_twig_registry_loader_theme')
->save();
$this->drupalGet('twig-theme-test/registry-loader');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-extend.html.twig');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-include.html.twig');
// Enable a subtheme for the theme that doesn't have any overrides to make
// sure that templates are being loaded from the first parent which has the
// templates.
$this->config('system.theme')
->set('default', 'test_theme_twig_registry_loader_subtheme')
->save();
$this->drupalGet('twig-theme-test/registry-loader');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-extend.html.twig');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-include.html.twig');
}
}

View file

@ -0,0 +1,137 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigSettingsTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\PhpStorage\PhpStorageFactory;
/**
* Tests overriding Twig engine settings via settings.php.
*
* @group Theme
*/
class TwigSettingsTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
/**
* Ensures Twig template auto reload setting can be overridden.
*/
function testTwigAutoReloadOverride() {
// Enable auto reload and rebuild the service container.
$parameters = $this->container->getParameter('twig.config');
$parameters['auto_reload'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
// Check isAutoReload() via the Twig service container.
$this->assertTrue($this->container->get('twig')->isAutoReload(), 'Automatic reloading of Twig templates enabled.');
// Disable auto reload and check the service container again.
$parameters = $this->container->getParameter('twig.config');
$parameters['auto_reload'] = FALSE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->assertFalse($this->container->get('twig')->isAutoReload(), 'Automatic reloading of Twig templates disabled.');
}
/**
* Ensures Twig engine debug setting can be overridden.
*/
function testTwigDebugOverride() {
// Enable debug and rebuild the service container.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
// Check isDebug() via the Twig service container.
$this->assertTrue($this->container->get('twig')->isDebug(), 'Twig debug enabled.');
$this->assertTrue($this->container->get('twig')->isAutoReload(), 'Twig automatic reloading is enabled when debug is enabled.');
// Override auto reload when debug is enabled.
$parameters = $this->container->getParameter('twig.config');
$parameters['auto_reload'] = FALSE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->assertFalse($this->container->get('twig')->isAutoReload(), 'Twig automatic reloading can be disabled when debug is enabled.');
// Disable debug and check the service container again.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = FALSE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->assertFalse($this->container->get('twig')->isDebug(), 'Twig debug disabled.');
}
/**
* Ensures Twig template cache setting can be overridden.
*/
function testTwigCacheOverride() {
$extension = twig_extension();
$theme_handler = $this->container->get('theme_handler');
$theme_handler->install(array('test_theme'));
$theme_handler->setDefault('test_theme');
// The registry still works on theme globals, so set them here.
\Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->getActiveThemeByName('test_theme'));
// Reset the theme registry, so that the new theme is used.
$this->container->set('theme.registry', NULL);
// Load array of Twig templates.
// reset() is necessary to invalidate caches tagged with 'theme_registry'.
$registry = $this->container->get('theme.registry');
$registry->reset();
$templates = $registry->getRuntime();
// Get the template filename and the cache filename for
// theme_test.template_test.html.twig.
$info = $templates->get('theme_test_template_test');
$template_filename = $info['path'] . '/' . $info['template'] . $extension;
$cache_filename = $this->container->get('twig')->getCacheFilename($template_filename);
// Navigate to the page and make sure the template gets cached.
$this->drupalGet('theme-test/template-test');
$this->assertTrue(PhpStorageFactory::get('twig')->exists($cache_filename), 'Cached Twig template found.');
// Disable the Twig cache and rebuild the service container.
$parameters = $this->container->getParameter('twig.config');
$parameters['cache'] = FALSE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
// This should return false after rebuilding the service container.
$new_cache_filename = $this->container->get('twig')->getCacheFilename($template_filename);
$this->assertFalse($new_cache_filename, 'Twig environment does not return cache filename after caching is disabled.');
}
/**
* Tests twig inline templates with auto_reload.
*/
public function testTwigInlineWithAutoReload() {
$parameters = $this->container->getParameter('twig.config');
$parameters['auto_reload'] = TRUE;
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->drupalGet('theme-test/inline-template-test');
$this->assertResponse(200);
$this->drupalGet('theme-test/inline-template-test');
$this->assertResponse(200);
}
}

View file

@ -0,0 +1,291 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigTransTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Tests Twig "trans" tags.
*
* @group Theme
*/
class TwigTransTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'theme_test',
'twig_theme_test',
'locale',
'language'
);
/**
* An administrative user for testing.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* Custom languages.
*
* @var array
*/
protected $languages = array(
'xx' => 'Lolspeak',
'zz' => 'Lolspeak2',
);
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Setup test_theme.
\Drupal::service('theme_handler')->install(array('test_theme'));
$this->config('system.theme')->set('default', 'test_theme')->save();
// Create and log in as admin.
$this->adminUser = $this->drupalCreateUser(array(
'administer languages',
'access administration pages',
'administer site configuration',
'translate interface'
));
$this->drupalLogin($this->adminUser);
// Install languages.
$this->installLanguages();
// Assign Lolspeak (xx) to be the default language.
$this->config('system.site')->set('default_langcode', 'xx')->save();
$this->rebuildContainer();
// Check that lolspeak is the default language for the site.
$this->assertEqual(\Drupal::languageManager()->getDefaultLanguage()->getId(), 'xx', 'Lolspeak is the default language');
}
/**
* Test Twig "trans" tags.
*/
public function testTwigTransTags() {
// Run this once without and once with Twig debug because trans can work
// differently depending on that setting.
$this->drupalGet('twig-theme-test/trans', array('language' => \Drupal::languageManager()->getLanguage('xx')));
$this->assertTwigTransTags();
// Enable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
$this->drupalGet('twig-theme-test/trans', array('language' => \Drupal::languageManager()->getLanguage('xx')));
$this->assertTwigTransTags();
}
/**
* Asserts Twig trans tags.
*/
protected function assertTwigTransTags() {
$this->assertText(
'OH HAI SUNZ',
'{% trans "Hello sun." %} was successfully translated.'
);
$this->assertText(
'O HAI SUNZZZZZZZ',
'{% trans "Hello sun." with {"context": "Lolspeak"} %} was successfully translated.'
);
$this->assertText(
'O HERRO ERRRF.',
'{{ "Hello Earth."|trans }} was successfully translated.'
);
$this->assertText(
'OH HAI TEH MUUN',
'{% trans %}Hello moon.{% endtrans %} was successfully translated.'
);
$this->assertText(
'O HAI STARRRRR',
'{% trans %} with {% plural count = 1 %} was successfully translated.'
);
$this->assertText(
'O HAI 2 STARZZZZ',
'{% trans %} with {% plural count = 2 %} was successfully translated.'
);
$this->assertRaw(
'ESCAPEE: &amp;&quot;&lt;&gt;',
'{{ token }} was successfully translated and prefixed with "@".'
);
$this->assertRaw(
'PAS-THRU: &"<>',
'{{ token|passthrough }} was successfully translated and prefixed with "!".'
);
$this->assertRaw(
'PLAYSHOLDR: <em class="placeholder">&amp;&quot;&lt;&gt;</em>',
'{{ token|placeholder }} was successfully translated and prefixed with "%".'
);
$this->assertRaw(
'DIS complex token HAZ LENGTH OV: 3. IT CONTAYNZ: <em class="placeholder">12345</em> AN &amp;&quot;&lt;&gt;. LETS PAS TEH BAD TEXT THRU: &"<>.',
'{{ complex.tokens }} were successfully translated with appropriate prefixes.'
);
$this->assertText(
'I have context.',
'{% trans %} with a context only msgid was excluded from translation.'
);
$this->assertText(
'I HAZ KONTEX.',
'{% trans with {"context": "Lolspeak"} %} was successfully translated with context.'
);
$this->assertText(
'O HAI NU TXT.',
'{% trans with {"langcode": "zz"} %} was successfully translated in specified language.'
);
$this->assertText(
'O HAI NU TXTZZZZ.',
'{% trans with {"context": "Lolspeak", "langcode": "zz"} %} was successfully translated with context in specified language.'
);
// Makes sure https://www.drupal.org/node/2489024 doesn't happen without
// twig debug.
$this->assertNoText(pi(), 'Running php code inside a Twig trans is not possible.');
}
/**
* Helper function: install languages.
*/
protected function installLanguages() {
foreach ($this->languages as $langcode => $name) {
// Generate custom .po contents for the language.
$contents = $this->poFileContents($langcode);
if ($contents) {
// Add test language for translation testing.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'label' => $name,
'direction' => LanguageInterface::DIRECTION_LTR,
);
// Install the language in Drupal.
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertRaw('"edit-languages-' . $langcode . '-weight"', 'Language code found.');
// Import the custom .po contents for the language.
$filename = tempnam('temporary://', "po_") . '.po';
file_put_contents($filename, $contents);
$options = array(
'files[file]' => $filename,
'langcode' => $langcode,
'customized' => TRUE,
);
$this->drupalPostForm('admin/config/regional/translate/import', $options, t('Import'));
drupal_unlink($filename);
}
}
$this->container->get('language_manager')->reset();
}
/**
* Generate a custom .po file for a specific test language.
*
* @param string $langcode
* The langcode of the specified language.
*
* @return string|FALSE
* The .po contents for the specified language or FALSE if none exists.
*/
protected function poFileContents($langcode) {
if ($langcode === 'xx') {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "Hello sun."
msgstr "OH HAI SUNZ"
msgctxt "Lolspeak"
msgid "Hello sun."
msgstr "O HAI SUNZZZZZZZ"
msgid "Hello Earth."
msgstr "O HERRO ERRRF."
msgid "Hello moon."
msgstr "OH HAI TEH MUUN"
msgid "Hello star."
msgid_plural "Hello @count stars."
msgstr[0] "O HAI STARRRRR"
msgstr[1] "O HAI @count STARZZZZ"
msgid "Escaped: @string"
msgstr "ESCAPEE: @string"
msgid "Pass-through: !string"
msgstr "PAS-THRU: !string"
msgid "Placeholder: %string"
msgstr "PLAYSHOLDR: %string"
msgid "This @token.name has a length of: @count. It contains: %token.numbers and @token.bad_text. Lets pass the bad text through: !token.bad_text."
msgstr "DIS @token.name HAZ LENGTH OV: @count. IT CONTAYNZ: %token.numbers AN @token.bad_text. LETS PAS TEH BAD TEXT THRU: !token.bad_text."
msgctxt "Lolspeak"
msgid "I have context."
msgstr "I HAZ KONTEX."
EOF;
}
else if ($langcode === 'zz') {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "Hello new text."
msgstr "O HAI NU TXT."
msgctxt "Lolspeak"
msgid "Hello new text."
msgstr "O HAI NU TXTZZZZ."
EOF;
}
return FALSE;
}
}