diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 81423cf..709f4ca 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -109,7 +109,7 @@ public function render(&$elements, $is_root_call = FALSE) {
*/
protected function doRender(&$elements, $is_root_call = FALSE) {
if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
- if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
+ if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') !== FALSE) {
$elements['#access_callback'] = $this->controllerResolver->getControllerFromDefinition($elements['#access_callback']);
}
$elements['#access'] = call_user_func($elements['#access_callback'], $elements);
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index 6848dc9..6a0f636 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -32,148 +32,7 @@ class RenderTest extends KernelTestBase {
*/
function testDrupalRenderBasics() {
$types = array(
- array(
- 'name' => 'null',
- 'value' => NULL,
- 'expected' => '',
- ),
- array(
- 'name' => 'no value',
- 'expected' => '',
- ),
- array(
- 'name' => 'empty string',
- 'value' => '',
- 'expected' => '',
- ),
- array(
- 'name' => 'no access',
- 'value' => array(
- '#markup' => 'foo',
- '#access' => FALSE,
- ),
- 'expected' => '',
- ),
- array(
- 'name' => 'access denied via callback',
- 'value' => array(
- '#markup' => 'foo',
- '#access_callback' => 'is_bool',
- ),
- 'expected' => '',
- ),
- array(
- 'name' => 'access granted via callback',
- 'value' => array(
- '#markup' => 'foo',
- '#access_callback' => 'is_array',
- ),
- 'expected' => 'foo',
- ),
- array(
- 'name' => 'access FALSE is honored',
- 'value' => array(
- '#markup' => 'foo',
- '#access' => FALSE,
- '#access_callback' => 'is_array',
- ),
- 'expected' => '',
- ),
- array(
- 'name' => 'previously printed',
- 'value' => array(
- '#markup' => 'foo',
- '#printed' => TRUE,
- ),
- 'expected' => '',
- ),
- array(
- 'name' => 'printed in prerender',
- 'value' => array(
- '#markup' => 'foo',
- '#pre_render' => array('common_test_drupal_render_printing_pre_render'),
- ),
- 'expected' => '',
- ),
-
- // Test that #theme and #theme_wrappers can co-exist on an element.
- array(
- 'name' => '#theme and #theme_wrappers basic',
- 'value' => array(
- '#theme' => 'common_test_foo',
- '#foo' => 'foo',
- '#bar' => 'bar',
- '#theme_wrappers' => array('container'),
- '#attributes' => array('class' => array('baz')),
- ),
- 'expected' => '
foobar
' . "\n",
- ),
- // Test that #theme_wrappers can disambiguate element attributes shared
- // with rendering methods that build #children by using the alternate
- // #theme_wrappers attribute override syntax.
- array(
- 'name' => '#theme and #theme_wrappers attribute disambiguation',
- 'value' => array(
- '#type' => 'link',
- '#theme_wrappers' => array(
- 'container' => array(
- '#attributes' => array('class' => array('baz')),
- ),
- ),
- '#attributes' => array('id' => 'foo'),
- '#url' => Url::fromUri('http://drupal.org'),
- '#title' => 'bar',
- ),
- 'expected' => '' . "\n",
- ),
- // Test that #theme_wrappers can disambiguate element attributes when the
- // "base" attribute is not set for #theme.
- array(
- 'name' => '#theme_wrappers attribute disambiguation with undefined #theme attribute',
- 'value' => array(
- '#type' => 'link',
- '#url' => Url::fromUri('http://drupal.org'),
- '#title' => 'foo',
- '#theme_wrappers' => array(
- 'container' => array(
- '#attributes' => array('class' => array('baz')),
- ),
- ),
- ),
- 'expected' => '' . "\n",
- ),
- // Two 'container' #theme_wrappers, one using the "base" attributes and
- // one using an override.
- array(
- 'name' => 'Two #theme_wrappers container hooks with different attributes',
- 'value' => array(
- '#attributes' => array('class' => array('foo')),
- '#theme_wrappers' => array(
- 'container' => array(
- '#attributes' => array('class' => array('bar')),
- ),
- 'container',
- ),
- ),
- 'expected' => '' . "\n",
- ),
- // Array syntax theme hook suggestion in #theme_wrappers.
- array(
- 'name' => '#theme_wrappers implements an array style theme hook suggestion',
- 'value' => array(
- '#theme_wrappers' => array(array('container')),
- '#attributes' => array('class' => array('foo')),
- ),
- 'expected' => '' . "\n",
- ),
-
// Test handling of #markup as a fallback for #theme hooks.
- // Simple #markup with no theme.
- array(
- 'name' => 'basic #markup based renderable array',
- 'value' => array('#markup' => 'foo'),
- 'expected' => 'foo',
- ),
// Theme suggestion is not implemented, #markup should be rendered.
array(
'name' => '#markup fallback for #theme suggestion not implemented',
@@ -218,34 +77,6 @@ function testDrupalRenderBasics() {
),
// Test handling of #children and child renderable elements.
- // #theme is not set, #children is not set and the array has children.
- array(
- 'name' => '#theme is not set, #children is not set and array has children',
- 'value' => array(
- 'child' => array('#markup' => 'bar'),
- ),
- 'expected' => 'bar',
- ),
- // #theme is not set, #children is set but empty and the array has
- // children.
- array(
- 'name' => '#theme is not set, #children is an empty string and array has children',
- 'value' => array(
- '#children' => '',
- 'child' => array('#markup' => 'bar'),
- ),
- 'expected' => 'bar',
- ),
- // #theme is not set, #children is not empty and will be assumed to be the
- // rendered child elements even though the #markup for 'child' differs.
- array(
- 'name' => '#theme is not set, #children is set and array has children',
- 'value' => array(
- '#children' => 'foo',
- 'child' => array('#markup' => 'bar'),
- ),
- 'expected' => 'foo',
- ),
// #theme is implemented so the values of both #children and 'child' will
// be ignored - it is the responsibility of the theme hook to render these
// if appropriate.
@@ -295,57 +126,6 @@ function testDrupalRenderBasics() {
}
/**
- * Tests sorting by weight.
- */
- function testDrupalRenderSorting() {
- $first = $this->randomMachineName();
- $second = $this->randomMachineName();
- // Build an array with '#weight' set for each element.
- $elements = array(
- 'second' => array(
- '#weight' => 10,
- '#markup' => $second,
- ),
- 'first' => array(
- '#weight' => 0,
- '#markup' => $first,
- ),
- );
- $output = drupal_render($elements);
-
- // The lowest weight element should appear last in $output.
- $this->assertTrue(strpos($output, $second) > strpos($output, $first), 'Elements were sorted correctly by weight.');
-
- // Confirm that the $elements array has '#sorted' set to TRUE.
- $this->assertTrue($elements['#sorted'], "'#sorted' => TRUE was added to the array");
-
- // Pass $elements through \Drupal\Core\Render\Element::children() and
- // ensure it remains sorted in the correct order. drupal_render() will
- // return an empty string if used on the same array in the same request.
- $children = Element::children($elements);
- $this->assertTrue(array_shift($children) == 'first', 'Child found in the correct order.');
- $this->assertTrue(array_shift($children) == 'second', 'Child found in the correct order.');
-
-
- // The same array structure again, but with #sorted set to TRUE.
- $elements = array(
- 'second' => array(
- '#weight' => 10,
- '#markup' => $second,
- ),
- 'first' => array(
- '#weight' => 0,
- '#markup' => $first,
- ),
- '#sorted' => TRUE,
- );
- $output = drupal_render($elements);
-
- // The elements should appear in output in the same order as the array.
- $this->assertTrue(strpos($output, $second) < strpos($output, $first), 'Elements were not sorted.');
- }
-
- /**
* Tests #attached functionality in children elements.
*/
function testDrupalRenderChildrenAttached() {
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index fb40f45..7f3ba29 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -10,6 +10,8 @@
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Renderer;
+use Drupal\Core\Template\Attribute;
+use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
/**
@@ -25,6 +27,30 @@ class RendererTest extends UnitTestCase {
*/
protected $renderer;
+ /**
+ * The mocked controller resolver.
+ *
+ * @var \Drupal\Core\Controller\ControllerResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $controllerResolver;
+
+ /**
+ * The mocked theme manager.
+ *
+ * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $themeManager;
+
+ /**
+ * The mocked element info.
+ *
+ * @var \Drupal\Core\Render\ElementInfoManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $elementInfo;
+
+ /**
+ * {@inheritdoc}
+ */
protected function setUp() {
parent::setUp();
@@ -34,6 +60,167 @@ protected function setUp() {
$this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo);
}
+ /**
+ * @dataProvider providerTestRenderBasic
+ */
+ public function testRenderBasic($build, $expected, callable $setup_code = NULL) {
+
+ if (isset($setup_code)) {
+ $setup_code = $setup_code->bindTo($this);
+ $setup_code();
+ }
+
+ $this->assertSame($expected, $this->renderer->render($build));
+ }
+
+ public function providerTestRenderBasic() {
+ $data = [];
+
+ // Pass a NULL.
+// $data[] = [NULL, ''];
+ // Pass an empty string.
+// $data[] = ['', ''];
+ // Previously printed, see ::renderTwice for a more integration like test.
+// $data[] = [[
+// '#markup' => 'foo',
+// '#printed' => TRUE,
+// ], ''];
+ // Printed in pre_render.
+// $data[] = [[
+// '#markup' => 'foo',
+// '#pre_render' => [[new TestCallables(), 'preRenderPrinted']]
+// ], ''];
+
+ // Test that #theme and #theme_wrappers can co-exist on an element.
+ // #theme and #theme_wrappers basic
+ $build = [
+ '#theme' => 'common_test_foo',
+ '#foo' => 'foo',
+ '#bar' => 'bar',
+ '#theme_wrappers' => ['container'],
+ '#attributes' => ['class' => ['baz']],
+ ];
+ $setup_code_type_link = function() {
+ $this->setupThemeContainer();
+ $this->themeManager->expects($this->at(0))
+ ->method('render')
+ ->with('common_test_foo', $this->anything())
+ ->willReturnCallback(function($theme, $vars) {
+ return $vars['#foo'] . $vars['#bar'];
+ });
+ };
+ $data[] = [$build, 'foobar
' . "\n", $setup_code_type_link];
+
+ // Test that #theme_wrappers can disambiguate element attributes shared
+ // with rendering methods that build #children by using the alternate
+ // #theme_wrappers attribute override syntax.
+ // '#theme and #theme_wrappers attribute disambiguation'.
+
+ $build = [
+ '#type' => 'link',
+ '#theme_wrappers' => [
+ 'container' => [
+ '#attributes' => ['class' => ['baz']],
+ ],
+ ],
+ '#attributes' => ['id' => 'foo'],
+ '#url' => 'http://drupal.org',
+ '#title' => 'bar',
+ ];
+ $setup_code_type_link = function() {
+ $this->setupThemeContainer();
+ $this->themeManager->expects($this->at(0))
+ ->method('render')
+ ->with('link', $this->anything())
+ ->willReturnCallback(function($theme, $vars) {
+ $attributes = new Attribute(['href' => $vars['#url']] + (isset($vars['#attributes']) ? $vars['#attributes'] : []));
+ return '' . $vars['#title'] . '';
+ });
+ $this->elementInfo->expects($this->atLeastOnce())
+ ->method('getInfo')
+ ->with('link')
+ ->willReturn(['#theme' => 'link']);
+ };
+ $data[] = [$build, '' . "\n", $setup_code_type_link];
+
+ // Test that #theme_wrappers can disambiguate element attributes when the
+ // "base" attribute is not set for #theme.
+ // #theme_wrappers attribute disambiguation with undefined #theme attribute.
+ $build = [
+ '#type' => 'link',
+ '#url' => 'http://drupal.org',
+ '#title' => 'foo',
+ '#theme_wrappers' => [
+ 'container' => [
+ '#attributes' => ['class' => ['baz']],
+ ],
+ ],
+ ];
+ $data[] = [$build, '' . "\n", $setup_code_type_link];
+
+ // Two 'container' #theme_wrappers, one using the "base" attributes and
+ // one using an override.
+ $build = [
+ '#attributes' => ['class' => ['foo']],
+ '#theme_wrappers' => [
+ 'container' => [
+ '#attributes' => ['class' => ['bar']],
+ ],
+ 'container',
+ ],
+ ];
+ $setup_code = function() {
+ $this->setupThemeContainer($this->any());
+ };
+ $data[] = [$build, '' . "\n", $setup_code];
+
+ // Array syntax theme hook suggestion in #theme_wrappers.
+ $build = [
+ '#theme_wrappers' => [['container']],
+ '#attributes' => ['class' => ['foo']],
+ ];
+ $setup_code = function() {
+ $this->setupThemeContainerMultiSuggestion($this->any());
+ };
+ $data[] = [$build, '' . "\n", $setup_code];
+
+ // Test handling of #markup as a fallback for #theme hooks.
+ // Simple #markup with no theme.
+
+ // Basic #markup based renderable array.
+// $data[] = [
+// ['#markup' => 'foo']
+// , 'foo'];
+
+ // Test handling of #children and child renderable elements.
+ // #theme is not set, #children is not set and the array has children.
+// $data[] = [
+// [
+// 'child' => ['#markup' => 'bar'],
+// ], 'bar'];
+ // #theme is not set, #children is set but empty and the array has
+ // children.
+// $data[] = [
+// [
+// '#children' => '',
+// 'child' => ['#markup' => 'bar'],
+// ], 'bar'
+// ];
+ // #theme is not set, #children is not empty and will be assumed to be the
+ // rendered child elements even though the #markup for 'child' differs.
+// $data[] = [
+// [
+// '#children' => 'foo',
+// 'child' => ['#markup' => 'bar'],
+// ], 'foo'
+// ];
+
+ return $data;
+ }
+
+ protected function setupThemeCommonTestFoo() {
+
+ }
/**
* @covers ::render
@@ -89,4 +276,133 @@ public function testRenderSortingWithSetHashSorted() {
$this->assertTrue(strpos($output, $second) < strpos($output, $first), 'Elements were not sorted.');
}
+ /**
+ * @dataProvider providerBoolean
+ */
+ public function testRenderWithPresetAccess($access) {
+ $build = [
+ '#markup' => 'test',
+ '#access' => $access,
+ ];
+
+ $this->assertAccess($build, $access);
+ }
+
+ /**
+ * @dataProvider providerBoolean
+ */
+ public function testRenderWithAccessCallbackCallable($access) {
+ $build = [
+ '#markup' => 'test',
+ '#access_callback' => function() use ($access) {
+ return $access;
+ }
+ ];
+
+ $this->assertAccess($build, $access);
+ }
+
+ /**
+ * @dataProvider providerBoolean
+ *
+ * Ensure that the #access property wins over the callable.
+ */
+ public function testRenderWithAccessPropertyAndCallback($access) {
+ $build = [
+ '#markup' => 'test',
+ '#access' => $access,
+ '#access_callback' => function() {
+ return TRUE;
+ }
+ ];
+
+ $this->assertAccess($build, $access);
+ }
+
+ /**
+ * @dataProvider providerBoolean
+ */
+ public function testRenderWithAccessControllerResolved($access) {
+ $build = [
+ '#markup' => 'test',
+ '#access_callback' => 'Drupal\Tests\Core\Render\TestAccessClass::' . ($access ? 'accessTrue' : 'accessFalse'),
+ ];
+
+ $this->controllerResolver->expects($this->atLeastOnce())
+ ->method('getControllerFromDefinition')
+ ->willReturnMap([
+ ['Drupal\Tests\Core\Render\TestAccessClass::accessTrue', [new TestAccessClass(), 'accessTrue']],
+ ['Drupal\Tests\Core\Render\TestAccessClass::accessFalse', [new TestAccessClass(), 'accessFalse']],
+ ]);
+
+ $this->assertAccess($build, $access);
+ }
+
+ public function testRenderTwice() {
+ $build = [
+ '#markup' => 'test',
+ ];
+
+ $this->assertEquals('test', $this->renderer->render($build));
+ // @todo This behaviour is odd ...
+ $this->assertEquals('', $this->renderer->render($build));
+ }
+
+
+
+ public function providerBoolean() {
+ return [
+ [FALSE],
+ [TRUE]
+ ];
+ }
+
+ protected function assertAccess($build, $access) {
+ if ($access) {
+ $this->assertSame('test', $this->renderer->render($build));
+ }
+ else {
+ $this->assertSame('', $this->renderer->render($build));
+ }
+ }
+
+ protected function setupThemeContainer($matcher = NULL) {
+ $this->themeManager->expects($matcher ?: $this->at(1))
+ ->method('render')
+ ->with('container', $this->anything())
+ ->willReturnCallback(function($theme, $vars) {
+ return '' . $vars['#children'] . "
\n";
+ });
+ }
+
+ protected function setupThemeContainerMultiSuggestion($matcher = NULL) {
+ $this->themeManager->expects($matcher ?: $this->at(1))
+ ->method('render')
+ ->with(['container'], $this->anything())
+ ->willReturnCallback(function($theme, $vars) {
+ return '' . $vars['#children'] . "
\n";
+ });
+ }
+
+}
+
+class TestAccessClass {
+
+ public function accessTrue() {
+ return TRUE;
+ }
+
+ public function accessFalse() {
+ return FALSE;
+ }
+
+}
+
+class TestCallables {
+
+ public function preRenderPrinted($elements) {
+ $elements['#printed'] = TRUE;
+ return $elements;
+ }
+
}
\ No newline at end of file