You are here

class TwigExtensionTest in Drupal 9

Same name in this branch
  1. 9 core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php \Drupal\Tests\Core\Template\TwigExtensionTest
  2. 9 core/modules/system/tests/src/Functional/Theme/TwigExtensionTest.php \Drupal\Tests\system\Functional\Theme\TwigExtensionTest
Same name and namespace in other branches
  1. 8 core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php \Drupal\Tests\Core\Template\TwigExtensionTest

Tests the twig extension.

@group Template

@coversDefaultClass \Drupal\Core\Template\TwigExtension

Hierarchy

Expanded class hierarchy of TwigExtensionTest

File

core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php, line 27

Namespace

Drupal\Tests\Core\Template
View source
class TwigExtensionTest extends UnitTestCase {

  /**
   * The renderer.
   *
   * @var \Drupal\Core\Render\RendererInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $renderer;

  /**
   * The url generator.
   *
   * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $urlGenerator;

  /**
   * The theme manager.
   *
   * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $themeManager;

  /**
   * The date formatter.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $dateFormatter;

  /**
   * The system under test.
   *
   * @var \Drupal\Core\Template\TwigExtension
   */
  protected $systemUnderTest;

  /**
   * The file URL generator mock.
   *
   * @var \Drupal\Core\File\FileUrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
   */
  protected $fileUrlGenerator;

  /**
   * {@inheritdoc}
   */
  public function setUp() : void {
    parent::setUp();
    $this->renderer = $this
      ->createMock('\\Drupal\\Core\\Render\\RendererInterface');
    $this->urlGenerator = $this
      ->createMock('\\Drupal\\Core\\Routing\\UrlGeneratorInterface');
    $this->themeManager = $this
      ->createMock('\\Drupal\\Core\\Theme\\ThemeManagerInterface');
    $this->dateFormatter = $this
      ->createMock('\\Drupal\\Core\\Datetime\\DateFormatterInterface');
    $this->fileUrlGenerator = $this
      ->createMock(FileUrlGeneratorInterface::class);
    $this->systemUnderTest = new TwigExtension($this->renderer, $this->urlGenerator, $this->themeManager, $this->dateFormatter, $this->fileUrlGenerator);
  }

  /**
   * Tests the escaping.
   *
   * @dataProvider providerTestEscaping
   */
  public function testEscaping($template, $expected) {
    $loader = new FilesystemLoader();
    $twig = new Environment($loader, [
      'debug' => TRUE,
      'cache' => FALSE,
      'autoescape' => 'html',
      'optimizations' => 0,
    ]);
    $twig
      ->addExtension($this->systemUnderTest);
    $name = '__string_template_test__';
    $nodes = $twig
      ->parse($twig
      ->tokenize(new Source($template, $name)));
    $this
      ->assertSame($expected, $nodes
      ->getNode('body')
      ->getNode(0)
      ->getNode('expr') instanceof FilterExpression);
  }

  /**
   * Provides tests data for testEscaping.
   *
   * @return array
   *   An array of test data each containing of a twig template string and
   *   a boolean expecting whether the path will be safe.
   */
  public function providerTestEscaping() {
    return [
      [
        '{{ path("foo") }}',
        FALSE,
      ],
      [
        '{{ path("foo", {}) }}',
        FALSE,
      ],
      [
        '{{ path("foo", { foo: "foo" }) }}',
        FALSE,
      ],
      [
        '{{ path("foo", foo) }}',
        TRUE,
      ],
      [
        '{{ path("foo", { foo: foo }) }}',
        TRUE,
      ],
      [
        '{{ path("foo", { foo: ["foo", "bar"] }) }}',
        TRUE,
      ],
      [
        '{{ path("foo", { foo: "foo", bar: "bar" }) }}',
        TRUE,
      ],
      [
        '{{ path(name = "foo", parameters = {}) }}',
        FALSE,
      ],
      [
        '{{ path(name = "foo", parameters = { foo: "foo" }) }}',
        FALSE,
      ],
      [
        '{{ path(name = "foo", parameters = foo) }}',
        TRUE,
      ],
      [
        '{{ path(name = "foo", parameters = { foo: ["foo", "bar"] }) }}',
        TRUE,
      ],
      [
        '{{ path(name = "foo", parameters = { foo: foo }) }}',
        TRUE,
      ],
      [
        '{{ path(name = "foo", parameters = { foo: "foo", bar: "bar" }) }}',
        TRUE,
      ],
    ];
  }

  /**
   * Tests the active_theme function.
   */
  public function testActiveTheme() {
    $active_theme = $this
      ->getMockBuilder('\\Drupal\\Core\\Theme\\ActiveTheme')
      ->disableOriginalConstructor()
      ->getMock();
    $active_theme
      ->expects($this
      ->once())
      ->method('getName')
      ->willReturn('test_theme');
    $this->themeManager
      ->expects($this
      ->once())
      ->method('getActiveTheme')
      ->willReturn($active_theme);
    $loader = new StringLoader();
    $twig = new Environment($loader);
    $twig
      ->addExtension($this->systemUnderTest);
    $result = $twig
      ->render('{{ active_theme() }}');
    $this
      ->assertEquals('test_theme', $result);
  }

  /**
   * Tests the format_date filter.
   */
  public function testFormatDate() {
    $this->dateFormatter
      ->expects($this
      ->exactly(1))
      ->method('format')
      ->willReturnCallback(function ($timestamp) {
      return date('Y-m-d', $timestamp);
    });
    $loader = new StringLoader();
    $twig = new Environment($loader);
    $twig
      ->addExtension($this->systemUnderTest);
    $timestamp = strtotime('1978-11-19');
    $result = $twig
      ->render('{{ time|format_date("html_date") }}', [
      'time' => $timestamp,
    ]);
    $this
      ->assertEquals('1978-11-19', $result);
  }

  /**
   * Tests the file_url filter.
   */
  public function testFileUrl() {
    $this->fileUrlGenerator
      ->expects($this
      ->once())
      ->method('generateString')
      ->with('public://picture.jpg')
      ->willReturn('sites/default/files/picture.jpg');
    $loader = new StringLoader();
    $twig = new Environment($loader);
    $twig
      ->addExtension($this->systemUnderTest);
    $result = $twig
      ->render('{{ file_url(file) }}', [
      'file' => 'public://picture.jpg',
    ]);
    $this
      ->assertEquals('sites/default/files/picture.jpg', $result);
  }

  /**
   * Tests the active_theme_path function.
   */
  public function testActiveThemePath() {
    $active_theme = $this
      ->getMockBuilder('\\Drupal\\Core\\Theme\\ActiveTheme')
      ->disableOriginalConstructor()
      ->getMock();
    $active_theme
      ->expects($this
      ->once())
      ->method('getPath')
      ->willReturn('foo/bar');
    $this->themeManager
      ->expects($this
      ->once())
      ->method('getActiveTheme')
      ->willReturn($active_theme);
    $loader = new StringLoader();
    $twig = new Environment($loader);
    $twig
      ->addExtension($this->systemUnderTest);
    $result = $twig
      ->render('{{ active_theme_path() }}');
    $this
      ->assertEquals('foo/bar', $result);
  }

  /**
   * Tests the escaping of objects implementing MarkupInterface.
   *
   * @covers ::escapeFilter
   */
  public function testSafeStringEscaping() {
    $loader = new FilesystemLoader();
    $twig = new Environment($loader, [
      'debug' => TRUE,
      'cache' => FALSE,
      'autoescape' => 'html',
      'optimizations' => 0,
    ]);

    // By default, TwigExtension will attempt to cast objects to strings.
    // Ensure objects that implement MarkupInterface are unchanged.
    $safe_string = $this
      ->createMock('\\Drupal\\Component\\Render\\MarkupInterface');
    $this
      ->assertSame($safe_string, $this->systemUnderTest
      ->escapeFilter($twig, $safe_string, 'html', 'UTF-8', TRUE));

    // Ensure objects that do not implement MarkupInterface are escaped.
    $string_object = new TwigExtensionTestString("<script>alert('here');</script>");
    $this
      ->assertSame('&lt;script&gt;alert(&#039;here&#039;);&lt;/script&gt;', $this->systemUnderTest
      ->escapeFilter($twig, $string_object, 'html', 'UTF-8', TRUE));
  }

  /**
   * @covers ::safeJoin
   */
  public function testSafeJoin() {
    $this->renderer
      ->expects($this
      ->any())
      ->method('render')
      ->with([
      '#markup' => '<strong>will be rendered</strong>',
      '#printed' => FALSE,
    ])
      ->willReturn('<strong>will be rendered</strong>');
    $twig_environment = $this
      ->prophesize(TwigEnvironment::class)
      ->reveal();

    // Simulate t().
    $markup = $this
      ->prophesize(TranslatableMarkup::class);
    $markup
      ->__toString()
      ->willReturn('<em>will be markup</em>');
    $markup = $markup
      ->reveal();
    $items = [
      '<em>will be escaped</em>',
      $markup,
      [
        '#markup' => '<strong>will be rendered</strong>',
      ],
    ];
    $result = $this->systemUnderTest
      ->safeJoin($twig_environment, $items, '<br/>');
    $this
      ->assertEquals('&lt;em&gt;will be escaped&lt;/em&gt;<br/><em>will be markup</em><br/><strong>will be rendered</strong>', $result);

    // Ensure safe_join Twig filter supports Traversable variables.
    $items = new \ArrayObject([
      '<em>will be escaped</em>',
      $markup,
      [
        '#markup' => '<strong>will be rendered</strong>',
      ],
    ]);
    $result = $this->systemUnderTest
      ->safeJoin($twig_environment, $items, ', ');
    $this
      ->assertEquals('&lt;em&gt;will be escaped&lt;/em&gt;, <em>will be markup</em>, <strong>will be rendered</strong>', $result);

    // Ensure safe_join Twig filter supports empty variables.
    $items = NULL;
    $result = $this->systemUnderTest
      ->safeJoin($twig_environment, $items, '<br>');
    $this
      ->assertEmpty($result);
  }

  /**
   * @dataProvider providerTestRenderVar
   */
  public function testRenderVar($result, $input) {
    $this->renderer
      ->expects($this
      ->any())
      ->method('render')
      ->with($result += [
      '#printed' => FALSE,
    ])
      ->willReturn('Rendered output');
    $this
      ->assertEquals('Rendered output', $this->systemUnderTest
      ->renderVar($input));
  }
  public function providerTestRenderVar() {
    $data = [];
    $renderable = $this
      ->prophesize(RenderableInterface::class);
    $render_array = [
      '#type' => 'test',
      '#var' => 'giraffe',
    ];
    $renderable
      ->toRenderable()
      ->willReturn($render_array);
    $data['renderable'] = [
      $render_array,
      $renderable
        ->reveal(),
    ];
    return $data;
  }

  /**
   * @covers ::escapeFilter
   * @covers ::bubbleArgMetadata
   */
  public function testEscapeWithGeneratedLink() {
    $loader = new FilesystemLoader();
    $twig = new Environment($loader, [
      'debug' => TRUE,
      'cache' => FALSE,
      'autoescape' => 'html',
      'optimizations' => 0,
    ]);
    $twig
      ->addExtension($this->systemUnderTest);
    $link = new GeneratedLink();
    $link
      ->setGeneratedLink('<a href="http://example.com"></a>');
    $link
      ->addCacheTags([
      'foo',
    ]);
    $link
      ->addAttachments([
      'library' => [
        'system/base',
      ],
    ]);
    $this->renderer
      ->expects($this
      ->atLeastOnce())
      ->method('render')
      ->with([
      "#cache" => [
        "contexts" => [],
        "tags" => [
          "foo",
        ],
        "max-age" => -1,
      ],
      "#attached" => [
        'library' => [
          'system/base',
        ],
      ],
    ]);
    $result = $this->systemUnderTest
      ->escapeFilter($twig, $link, 'html', NULL, TRUE);
    $this
      ->assertEquals('<a href="http://example.com"></a>', $result);
  }

  /**
   * @covers ::renderVar
   * @covers ::bubbleArgMetadata
   */
  public function testRenderVarWithGeneratedLink() {
    $link = new GeneratedLink();
    $link
      ->setGeneratedLink('<a href="http://example.com"></a>');
    $link
      ->addCacheTags([
      'foo',
    ]);
    $link
      ->addAttachments([
      'library' => [
        'system/base',
      ],
    ]);
    $this->renderer
      ->expects($this
      ->atLeastOnce())
      ->method('render')
      ->with([
      "#cache" => [
        "contexts" => [],
        "tags" => [
          "foo",
        ],
        "max-age" => -1,
      ],
      "#attached" => [
        'library' => [
          'system/base',
        ],
      ],
    ]);
    $result = $this->systemUnderTest
      ->renderVar($link);
    $this
      ->assertEquals('<a href="http://example.com"></a>', $result);
  }

  /**
   * Tests creating attributes within a Twig template.
   *
   * @covers ::createAttribute
   */
  public function testCreateAttribute() {
    $name = '__string_template_test_1__';
    $loader = new ArrayLoader([
      $name => "{% for iteration in iterations %}<div{{ create_attribute(iteration) }}></div>{% endfor %}",
    ]);
    $twig = new Environment($loader);
    $twig
      ->addExtension($this->systemUnderTest);
    $iterations = [
      [
        'class' => [
          'kittens',
        ],
        'data-toggle' => 'modal',
        'data-lang' => 'es',
      ],
      [
        'id' => 'puppies',
        'data-value' => 'foo',
        'data-lang' => 'en',
      ],
      [],
    ];
    $result = $twig
      ->render($name, [
      'iterations' => $iterations,
    ]);
    $expected = '<div class="kittens" data-toggle="modal" data-lang="es"></div><div id="puppies" data-value="foo" data-lang="en"></div><div></div>';
    $this
      ->assertEquals($expected, $result);

    // Test default creation of empty attribute object and using its method.
    $name = '__string_template_test_2__';
    $loader = new ArrayLoader([
      $name => "<div{{ create_attribute().addClass('meow') }}></div>",
    ]);
    $twig
      ->setLoader($loader);
    $result = $twig
      ->render($name);
    $expected = '<div class="meow"></div>';
    $this
      ->assertEquals($expected, $result);
  }

  /**
   * @covers ::getLink
   */
  public function testLinkWithOverriddenAttributes() {
    $url = Url::fromRoute('<front>', [], [
      'attributes' => [
        'class' => [
          'foo',
        ],
      ],
    ]);
    $build = $this->systemUnderTest
      ->getLink('test', $url, [
      'class' => [
        'bar',
      ],
    ]);
    $this
      ->assertEquals([
      'foo',
      'bar',
    ], $build['#url']
      ->getOption('attributes')['class']);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
PhpUnitWarnings::$deprecationWarnings private static property Deprecation warnings from PHPUnit to raise with @trigger_error().
PhpUnitWarnings::addWarning public function Converts PHPUnit deprecation warnings to E_USER_DEPRECATED.
TwigExtensionTest::$dateFormatter protected property The date formatter.
TwigExtensionTest::$fileUrlGenerator protected property The file URL generator mock.
TwigExtensionTest::$renderer protected property The renderer.
TwigExtensionTest::$systemUnderTest protected property The system under test.
TwigExtensionTest::$themeManager protected property The theme manager.
TwigExtensionTest::$urlGenerator protected property The url generator.
TwigExtensionTest::providerTestEscaping public function Provides tests data for testEscaping.
TwigExtensionTest::providerTestRenderVar public function
TwigExtensionTest::setUp public function Overrides UnitTestCase::setUp
TwigExtensionTest::testActiveTheme public function Tests the active_theme function.
TwigExtensionTest::testActiveThemePath public function Tests the active_theme_path function.
TwigExtensionTest::testCreateAttribute public function Tests creating attributes within a Twig template.
TwigExtensionTest::testEscapeWithGeneratedLink public function @covers ::escapeFilter @covers ::bubbleArgMetadata
TwigExtensionTest::testEscaping public function Tests the escaping.
TwigExtensionTest::testFileUrl public function Tests the file_url filter.
TwigExtensionTest::testFormatDate public function Tests the format_date filter.
TwigExtensionTest::testLinkWithOverriddenAttributes public function @covers ::getLink
TwigExtensionTest::testRenderVar public function @dataProvider providerTestRenderVar
TwigExtensionTest::testRenderVarWithGeneratedLink public function @covers ::renderVar @covers ::bubbleArgMetadata
TwigExtensionTest::testSafeJoin public function @covers ::safeJoin
TwigExtensionTest::testSafeStringEscaping public function Tests the escaping of objects implementing MarkupInterface.
UnitTestCase::$randomGenerator protected property The random generator.
UnitTestCase::$root protected property The app root. 1
UnitTestCase::assertArrayEquals Deprecated protected function Asserts if two arrays are equal by sorting them first.
UnitTestCase::getClassResolverStub protected function Returns a stub class resolver.
UnitTestCase::getConfigFactoryStub public function Returns a stub config factory that behaves according to the passed array.
UnitTestCase::getConfigStorageStub public function Returns a stub config storage that returns the supplied configuration.
UnitTestCase::getContainerWithCacheTagsInvalidator protected function Sets up a container with a cache tags invalidator.
UnitTestCase::getRandomGenerator protected function Gets the random generator for the utility methods.
UnitTestCase::getStringTranslationStub public function Returns a stub translation manager that just returns the passed string.
UnitTestCase::randomMachineName public function Generates a unique random string containing letters and numbers.
UnitTestCase::setUpBeforeClass public static function