You are here

public function RendererTest::providerTestRenderBasic in Drupal 10

Same name and namespace in other branches
  1. 8 core/tests/Drupal/Tests/Core/Render/RendererTest.php \Drupal\Tests\Core\Render\RendererTest::providerTestRenderBasic()
  2. 9 core/tests/Drupal/Tests/Core/Render/RendererTest.php \Drupal\Tests\Core\Render\RendererTest::providerTestRenderBasic()

Provides a list of render arrays to test basic rendering.

Return value

array

File

core/tests/Drupal/Tests/Core/Render/RendererTest.php, line 66
Contains \Drupal\Tests\Core\Render\RendererTest.

Class

RendererTest
@coversDefaultClass \Drupal\Core\Render\Renderer @group Render

Namespace

Drupal\Tests\Core\Render

Code

public function providerTestRenderBasic() {
  $data = [];

  // Part 1: the most simplistic render arrays possible, none using #theme.
  // 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',
        ],
      ],
    ],
    '',
  ];

  // Basic #markup based renderable array.
  $data[] = [
    [
      '#markup' => 'foo',
    ],
    'foo',
  ];

  // Basic #markup based renderable array with value '0'.
  $data[] = [
    [
      '#markup' => '0',
    ],
    '0',
  ];

  // Basic #markup based renderable array with value 0.
  $data[] = [
    [
      '#markup' => 0,
    ],
    '0',
  ];

  // Basic #markup based renderable array with value ''.
  $data[] = [
    [
      '#markup' => '',
    ],
    '',
  ];

  // Basic #markup based renderable array with value NULL.
  $data[] = [
    [
      '#markup' => NULL,
    ],
    '',
  ];

  // Basic #plain_text based renderable array.
  $data[] = [
    [
      '#plain_text' => 'foo',
    ],
    'foo',
  ];

  // Mixing #plain_text and #markup based renderable array.
  $data[] = [
    [
      '#plain_text' => '<em>foo</em>',
      '#markup' => 'bar',
    ],
    '&lt;em&gt;foo&lt;/em&gt;',
  ];

  // Safe strings in #plain_text are still escaped.
  $data[] = [
    [
      '#plain_text' => Markup::create('<em>foo</em>'),
    ],
    '&lt;em&gt;foo&lt;/em&gt;',
  ];

  // #plain_text based renderable array with value '0'.
  $data[] = [
    [
      '#plain_text' => '0',
    ],
    '0',
  ];

  // #plain_text based renderable array with value 0.
  $data[] = [
    [
      '#plain_text' => 0,
    ],
    '0',
  ];

  // #plain_text based renderable array with value ''.
  $data[] = [
    [
      '#plain_text' => '',
    ],
    '',
  ];

  // #plain_text based renderable array with value NULL.
  $data[] = [
    [
      '#plain_text' => NULL,
    ],
    '',
  ];

  // Renderable child element.
  $data[] = [
    [
      'child' => [
        '#markup' => 'bar',
      ],
    ],
    'bar',
  ];

  // XSS filtering test.
  $data[] = [
    [
      'child' => [
        '#markup' => "This is <script>alert('XSS')</script> test",
      ],
    ],
    "This is alert('XSS') test",
  ];

  // XSS filtering test.
  $data[] = [
    [
      'child' => [
        '#markup' => "This is <script>alert('XSS')</script> test",
        '#allowed_tags' => [
          'script',
        ],
      ],
    ],
    "This is <script>alert('XSS')</script> test",
  ];

  // XSS filtering test.
  $data[] = [
    [
      'child' => [
        '#markup' => "This is <script><em>alert('XSS')</em></script> <strong>test</strong>",
        '#allowed_tags' => [
          'em',
          'strong',
        ],
      ],
    ],
    "This is <em>alert('XSS')</em> <strong>test</strong>",
  ];

  // Html escaping test.
  $data[] = [
    [
      'child' => [
        '#plain_text' => "This is <script><em>alert('XSS')</em></script> <strong>test</strong>",
      ],
    ],
    "This is &lt;script&gt;&lt;em&gt;alert(&#039;XSS&#039;)&lt;/em&gt;&lt;/script&gt; &lt;strong&gt;test&lt;/strong&gt;",
  ];

  // XSS filtering by default test.
  $data[] = [
    [
      'child' => [
        '#markup' => "This is <script><em>alert('XSS')</em></script> <strong>test</strong>",
      ],
    ],
    "This is <em>alert('XSS')</em> <strong>test</strong>",
  ];

  // Ensure non-XSS tags are not filtered out.
  $data[] = [
    [
      'child' => [
        '#markup' => "This is <strong><script>alert('not a giraffe')</script></strong> test",
      ],
    ],
    "This is <strong>alert('not a giraffe')</strong> test",
  ];

  // #children set but empty, and renderable children.
  $data[] = [
    [
      '#children' => '',
      'child' => [
        '#markup' => 'bar',
      ],
    ],
    'bar',
  ];

  // #children set, not empty, and renderable children. #children will be
  // assumed oto be the rendered child elements, even though the #markup for
  // 'child' differs.
  $data[] = [
    [
      '#children' => 'foo',
      'child' => [
        '#markup' => 'bar',
      ],
    ],
    'foo',
  ];

  // Ensure that content added to #markup via a #pre_render callback is safe.
  $data[] = [
    [
      '#markup' => 'foo',
      '#pre_render' => [
        function ($elements) {
          $elements['#markup'] .= '<script>alert("bar");</script>';
          return $elements;
        },
      ],
    ],
    'fooalert("bar");',
  ];

  // Test #allowed_tags in combination with #markup and #pre_render.
  $data[] = [
    [
      '#markup' => 'foo',
      '#allowed_tags' => [
        'script',
      ],
      '#pre_render' => [
        function ($elements) {
          $elements['#markup'] .= '<script>alert("bar");</script>';
          return $elements;
        },
      ],
    ],
    'foo<script>alert("bar");</script>',
  ];

  // Ensure output is escaped when adding content to #check_plain through
  // a #pre_render callback.
  $data[] = [
    [
      '#plain_text' => 'foo',
      '#pre_render' => [
        function ($elements) {
          $elements['#plain_text'] .= '<script>alert("bar");</script>';
          return $elements;
        },
      ],
    ],
    'foo&lt;script&gt;alert(&quot;bar&quot;);&lt;/script&gt;',
  ];

  // Part 2: render arrays using #theme and #theme_wrappers.
  // Tests that #theme and #theme_wrappers can co-exist on an element.
  $build = [
    '#theme' => 'common_test_foo',
    '#foo' => 'foo',
    '#bar' => 'bar',
    '#theme_wrappers' => [
      'container',
    ],
    '#attributes' => [
      'class' => [
        'baz',
      ],
    ],
  ];
  $setup_code_type_link = function () {
    $this->themeManager
      ->expects($this
      ->exactly(2))
      ->method('render')
      ->with($this
      ->logicalOr('common_test_foo', 'container'))
      ->willReturnCallback(function ($theme, $vars) {
      if ($theme == 'container') {
        return '<div' . (string) new Attribute($vars['#attributes']) . '>' . $vars['#children'] . "</div>\n";
      }
      return $vars['#foo'] . $vars['#bar'];
    });
  };
  $data[] = [
    $build,
    '<div class="baz">foobar</div>' . "\n",
    $setup_code_type_link,
  ];

  // Tests that #theme_wrappers can disambiguate element attributes shared
  // with rendering methods that build #children by using the alternate
  // #theme_wrappers attribute override syntax.
  $build = [
    '#type' => 'link',
    '#theme_wrappers' => [
      'container' => [
        '#attributes' => [
          'class' => [
            'baz',
          ],
        ],
      ],
    ],
    '#attributes' => [
      'id' => 'foo',
    ],
    '#url' => 'https://www.drupal.org',
    '#title' => 'bar',
  ];
  $setup_code_type_link = function () {
    $this->themeManager
      ->expects($this
      ->exactly(2))
      ->method('render')
      ->with($this
      ->logicalOr('link', 'container'))
      ->willReturnCallback(function ($theme, $vars) {
      if ($theme == 'container') {
        return '<div' . (string) new Attribute($vars['#attributes']) . '>' . $vars['#children'] . "</div>\n";
      }
      $attributes = new Attribute([
        'href' => $vars['#url'],
      ] + ($vars['#attributes'] ?? []));
      return '<a' . (string) $attributes . '>' . $vars['#title'] . '</a>';
    });
  };
  $data[] = [
    $build,
    '<div class="baz"><a href="https://www.drupal.org" id="foo">bar</a></div>' . "\n",
    $setup_code_type_link,
  ];

  // Tests that #theme_wrappers can disambiguate element attributes when the
  // "base" attribute is not set for #theme.
  $build = [
    '#type' => 'link',
    '#url' => 'https://www.drupal.org',
    '#title' => 'foo',
    '#theme_wrappers' => [
      'container' => [
        '#attributes' => [
          'class' => [
            'baz',
          ],
        ],
      ],
    ],
  ];
  $data[] = [
    $build,
    '<div class="baz"><a href="https://www.drupal.org">foo</a></div>' . "\n",
    $setup_code_type_link,
  ];

  // Tests 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->themeManager
      ->expects($this
      ->exactly(2))
      ->method('render')
      ->with('container')
      ->willReturnCallback(function ($theme, $vars) {
      return '<div' . (string) new Attribute($vars['#attributes']) . '>' . $vars['#children'] . "</div>\n";
    });
  };
  $data[] = [
    $build,
    '<div class="foo"><div class="bar"></div>' . "\n" . '</div>' . "\n",
    $setup_code,
  ];

  // Tests array syntax theme hook suggestion in #theme_wrappers.
  $build = [
    '#theme_wrappers' => [
      [
        'container',
      ],
    ],
    '#attributes' => [
      'class' => [
        'foo',
      ],
    ],
  ];
  $setup_code = function () {
    $this->themeManager
      ->expects($this
      ->once())
      ->method('render')
      ->with([
      'container',
    ])
      ->willReturnCallback(function ($theme, $vars) {
      return '<div' . (string) new Attribute($vars['#attributes']) . '>' . $vars['#children'] . "</div>\n";
    });
  };
  $data[] = [
    $build,
    '<div class="foo"></div>' . "\n",
    $setup_code,
  ];

  // Part 3: render arrays using #markup as a fallback for #theme hooks.
  // Theme suggestion is not implemented, #markup should be rendered.
  $build = [
    '#theme' => [
      'suggestionnotimplemented',
    ],
    '#markup' => 'foo',
  ];
  $setup_code = function () {
    $this->themeManager
      ->expects($this
      ->once())
      ->method('render')
      ->with([
      'suggestionnotimplemented',
    ], $this
      ->anything())
      ->willReturn(FALSE);
  };
  $data[] = [
    $build,
    'foo',
    $setup_code,
  ];

  // Tests unimplemented theme suggestion, child #markup should be rendered.
  $build = [
    '#theme' => [
      'suggestionnotimplemented',
    ],
    'child' => [
      '#markup' => 'foo',
    ],
  ];
  $setup_code = function () {
    $this->themeManager
      ->expects($this
      ->once())
      ->method('render')
      ->with([
      'suggestionnotimplemented',
    ], $this
      ->anything())
      ->willReturn(FALSE);
  };
  $data[] = [
    $build,
    'foo',
    $setup_code,
  ];

  // Tests implemented theme suggestion: #markup should not be rendered.
  $build = [
    '#theme' => [
      'common_test_empty',
    ],
    '#markup' => 'foo',
  ];
  $theme_function_output = $this
    ->randomContextValue();
  $setup_code = function () use ($theme_function_output) {
    $this->themeManager
      ->expects($this
      ->once())
      ->method('render')
      ->with([
      'common_test_empty',
    ], $this
      ->anything())
      ->willReturn($theme_function_output);
  };
  $data[] = [
    $build,
    $theme_function_output,
    $setup_code,
  ];

  // Tests implemented theme suggestion: children should not be rendered.
  $build = [
    '#theme' => [
      'common_test_empty',
    ],
    'child' => [
      '#markup' => 'foo',
    ],
  ];
  $data[] = [
    $build,
    $theme_function_output,
    $setup_code,
  ];

  // Part 4: handling of #children and child renderable elements.
  // #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.
  $build = [
    '#theme' => 'common_test_foo',
    '#children' => 'baz',
    'child' => [
      '#markup' => 'boo',
    ],
  ];
  $setup_code = function () {
    $this->themeManager
      ->expects($this
      ->once())
      ->method('render')
      ->with('common_test_foo', $this
      ->anything())
      ->willReturn('foobar');
  };
  $data[] = [
    $build,
    'foobar',
    $setup_code,
  ];

  // #theme is implemented but #render_children is TRUE. As in the case where
  // #theme is not set, empty #children means child elements are rendered
  // recursively.
  $build = [
    '#theme' => 'common_test_foo',
    '#children' => '',
    '#render_children' => TRUE,
    'child' => [
      '#markup' => 'boo',
    ],
  ];
  $setup_code = function () {
    $this->themeManager
      ->expects($this
      ->never())
      ->method('render');
  };
  $data[] = [
    $build,
    'boo',
    $setup_code,
  ];

  // #theme is implemented but #render_children is TRUE. As in the case where
  // #theme is not set, #children will take precedence over 'child'.
  $build = [
    '#theme' => 'common_test_foo',
    '#children' => 'baz',
    '#render_children' => TRUE,
    'child' => [
      '#markup' => 'boo',
    ],
  ];
  $setup_code = function () {
    $this->themeManager
      ->expects($this
      ->never())
      ->method('render');
  };
  $data[] = [
    $build,
    'baz',
    $setup_code,
  ];

  // #theme is implemented but #render_children is TRUE. In this case the
  // calling code is expecting only the children to be rendered. #prefix and
  // #suffix should not be inherited for the children.
  $build = [
    '#theme' => 'common_test_foo',
    '#children' => '',
    '#prefix' => 'kangaroo',
    '#suffix' => 'unicorn',
    '#render_children' => TRUE,
    'child' => [
      '#markup' => 'kitten',
    ],
  ];
  $setup_code = function () {
    $this->themeManager
      ->expects($this
      ->never())
      ->method('render');
  };
  $data[] = [
    $build,
    'kitten',
    $setup_code,
  ];
  return $data;
}